@xia-sc/dsh-git 0.5.1 → 0.5.3
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/LICENSE +21 -0
- package/README.en.md +308 -284
- package/README.md +241 -225
- package/cordis.patch.yml +15 -15
- package/lib/client.js +2479 -2286
- package/lib/index.js +1292 -1272
- package/package.json +3 -2
package/lib/client.js
CHANGED
|
@@ -1,2286 +1,2479 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @xia-sc/dsh-git — browser half (dsh.client bundle).
|
|
3
|
-
*
|
|
4
|
-
* Two seats, one shared store:
|
|
5
|
-
*
|
|
6
|
-
* - `conversation.input.dock` → GitDockLine: a compact status PILL rendered
|
|
7
|
-
* LEFT-ALIGNED in the row band above the composer card (the textarea's
|
|
8
|
-
* top-left, no full-width banner): branch summary, or "not a git
|
|
9
|
-
* repository". Clicking it toggles the workbench panel. It follows the
|
|
10
|
-
* CURRENT session's workspace directory — clicking different sessions /
|
|
11
|
-
* workspaces in the sidebar re-binds it in real time.
|
|
12
|
-
* - `shell.overlay` → GitFloatingPanel: renders NOTHING while collapsed (no
|
|
13
|
-
* floating bubble that could cover the input box) and the full Git
|
|
14
|
-
* workbench when expanded, positioned above the composer card.
|
|
15
|
-
*
|
|
16
|
-
* All Git work happens on the host through `/dsh-git-rpc` (mounted by
|
|
17
|
-
* lib/index.js); this bundle only renders and calls `ctx.connection.rpc`.
|
|
18
|
-
* The bundle is hand-written against the client module table (seed words
|
|
19
|
-
* only: `react`), so it ships without a build step.
|
|
20
|
-
*/
|
|
21
|
-
window.__ModuleLoader__.load({
|
|
22
|
-
id: "@xia-sc/dsh-git",
|
|
23
|
-
factory: (require) => {
|
|
24
|
-
var module = { exports: {} };
|
|
25
|
-
var exports = module.exports;
|
|
26
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
27
|
-
|
|
28
|
-
var React = require("react");
|
|
29
|
-
|
|
30
|
-
var name = "dsh-git";
|
|
31
|
-
/** Required client services (activation gating for the loader entry). */
|
|
32
|
-
var inject = ["slots", "connection", "locale"];
|
|
33
|
-
|
|
34
|
-
/** Locale namespace owned by this plugin. */
|
|
35
|
-
var NS = "dshGit";
|
|
36
|
-
|
|
37
|
-
var zh = {
|
|
38
|
-
"panel.title": "Git",
|
|
39
|
-
"panel.collapse": "收起",
|
|
40
|
-
"refresh.aria": "刷新 Git 状态",
|
|
41
|
-
"branch.aria": "当前 Git 分支",
|
|
42
|
-
"switch.aria": "切换分支",
|
|
43
|
-
"switch.dirtyWarn": "有 {count} 个未提交文件(如 {paths}),切换分支可能被 git 拒绝,请先提交或暂存",
|
|
44
|
-
"switch.force": "仍要切换",
|
|
45
|
-
"branch.new": "新建分支",
|
|
46
|
-
"branch.newTitle": "从某个分支创建新分支",
|
|
47
|
-
"newBranch.placeholder": "新分支名…",
|
|
48
|
-
"newBranch.base": "基于分支",
|
|
49
|
-
"newBranch.create": "创建并切换",
|
|
50
|
-
"newBranch.cancel": "取消",
|
|
51
|
-
"newBranch.empty": "请输入新分支名",
|
|
52
|
-
"group.local": "本地分支",
|
|
53
|
-
"group.remote": "远程分支",
|
|
54
|
-
"loading": "读取中…",
|
|
55
|
-
"notRepo": "当前工作区不是 Git 仓库",
|
|
56
|
-
"detached": "游离 HEAD",
|
|
57
|
-
"dirty": "{count} 个未提交更改",
|
|
58
|
-
"aheadBehind": "领先 {ahead} / 落后 {behind}",
|
|
59
|
-
"action.fetch": "拉取更新",
|
|
60
|
-
"action.pull": "拉取合并",
|
|
61
|
-
"action.stage": "暂存全部",
|
|
62
|
-
"action.generate": "AI 生成",
|
|
63
|
-
"action.commit": "提交",
|
|
64
|
-
"action.push": "推送",
|
|
65
|
-
"generate.mode.staged": "已暂存",
|
|
66
|
-
"generate.mode.unstaged": "未暂存",
|
|
67
|
-
"generate.mode.all": "全部",
|
|
68
|
-
"generate.mode.title": "AI 生成的依据",
|
|
69
|
-
"generate.noChanges": "没有可用的改动",
|
|
70
|
-
"generate.done": "已生成提交信息",
|
|
71
|
-
"generate.noStaged": "没有已暂存的改动,请先点「暂存全部」,或把生成依据改为「未暂存」「全部」",
|
|
72
|
-
"generate.noProvider": "尚未配置模型,请先在 设置 > 模型 中添加",
|
|
73
|
-
"generate.noModel": "该提供方没有可用模型",
|
|
74
|
-
"generate.empty": "模型没有返回提交信息",
|
|
75
|
-
"generate.
|
|
76
|
-
"generate.
|
|
77
|
-
"
|
|
78
|
-
"commit.
|
|
79
|
-
"
|
|
80
|
-
"changes.
|
|
81
|
-
"
|
|
82
|
-
"diff.
|
|
83
|
-
"diff.
|
|
84
|
-
"diff.
|
|
85
|
-
"diff.
|
|
86
|
-
"diff.scope.
|
|
87
|
-
"diff.scope.
|
|
88
|
-
"diff.
|
|
89
|
-
"diff.
|
|
90
|
-
"diff.
|
|
91
|
-
"diff.
|
|
92
|
-
"diff.
|
|
93
|
-
"diff.
|
|
94
|
-
"diff.
|
|
95
|
-
"diff.
|
|
96
|
-
"diff.
|
|
97
|
-
"diff.
|
|
98
|
-
"diff.
|
|
99
|
-
"diff.
|
|
100
|
-
"diff.
|
|
101
|
-
"
|
|
102
|
-
"log.
|
|
103
|
-
"
|
|
104
|
-
"busy.
|
|
105
|
-
"busy.
|
|
106
|
-
"busy.
|
|
107
|
-
"busy.
|
|
108
|
-
"busy.
|
|
109
|
-
"busy.
|
|
110
|
-
"busy.
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
"
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
"
|
|
144
|
-
"
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"generate
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
"
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
169
|
-
"
|
|
170
|
-
"
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"
|
|
181
|
-
"diff.
|
|
182
|
-
"diff.
|
|
183
|
-
"diff.
|
|
184
|
-
"diff.
|
|
185
|
-
"diff.
|
|
186
|
-
"diff.
|
|
187
|
-
"diff.
|
|
188
|
-
"
|
|
189
|
-
"
|
|
190
|
-
"
|
|
191
|
-
"
|
|
192
|
-
"
|
|
193
|
-
"
|
|
194
|
-
"
|
|
195
|
-
"
|
|
196
|
-
"
|
|
197
|
-
"
|
|
198
|
-
"
|
|
199
|
-
"
|
|
200
|
-
"
|
|
201
|
-
"
|
|
202
|
-
"
|
|
203
|
-
"
|
|
204
|
-
"
|
|
205
|
-
"
|
|
206
|
-
"
|
|
207
|
-
"
|
|
208
|
-
"
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
overflow: "hidden",
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
padding: "
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
color: "#
|
|
332
|
-
|
|
333
|
-
fontSize: 12
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
flex: "1",
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
},
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
minWidth: 0,
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
},
|
|
526
|
-
|
|
527
|
-
flex: "
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
},
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
color: "var(--dsw-alias-label-quaternary, #6b7280)"
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
},
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
},
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
},
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
})
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
var
|
|
945
|
-
|
|
946
|
-
if (
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
if (
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
var
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
var
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
"
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
h("
|
|
1066
|
-
|
|
1067
|
-
]
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
*
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
var
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
});
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
},
|
|
1248
|
-
h("
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
var
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
var
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
var
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
if (
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
if (
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
var
|
|
1504
|
-
var
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
var
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
function
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
var
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
var
|
|
1542
|
-
|
|
1543
|
-
var
|
|
1544
|
-
if (
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
React.
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
if (!
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
"
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
key: "
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @xia-sc/dsh-git — browser half (dsh.client bundle).
|
|
3
|
+
*
|
|
4
|
+
* Two seats, one shared store:
|
|
5
|
+
*
|
|
6
|
+
* - `conversation.input.dock` → GitDockLine: a compact status PILL rendered
|
|
7
|
+
* LEFT-ALIGNED in the row band above the composer card (the textarea's
|
|
8
|
+
* top-left, no full-width banner): branch summary, or "not a git
|
|
9
|
+
* repository". Clicking it toggles the workbench panel. It follows the
|
|
10
|
+
* CURRENT session's workspace directory — clicking different sessions /
|
|
11
|
+
* workspaces in the sidebar re-binds it in real time.
|
|
12
|
+
* - `shell.overlay` → GitFloatingPanel: renders NOTHING while collapsed (no
|
|
13
|
+
* floating bubble that could cover the input box) and the full Git
|
|
14
|
+
* workbench when expanded, positioned above the composer card.
|
|
15
|
+
*
|
|
16
|
+
* All Git work happens on the host through `/dsh-git-rpc` (mounted by
|
|
17
|
+
* lib/index.js); this bundle only renders and calls `ctx.connection.rpc`.
|
|
18
|
+
* The bundle is hand-written against the client module table (seed words
|
|
19
|
+
* only: `react`), so it ships without a build step.
|
|
20
|
+
*/
|
|
21
|
+
window.__ModuleLoader__.load({
|
|
22
|
+
id: "@xia-sc/dsh-git",
|
|
23
|
+
factory: (require) => {
|
|
24
|
+
var module = { exports: {} };
|
|
25
|
+
var exports = module.exports;
|
|
26
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
27
|
+
|
|
28
|
+
var React = require("react");
|
|
29
|
+
|
|
30
|
+
var name = "dsh-git";
|
|
31
|
+
/** Required client services (activation gating for the loader entry). */
|
|
32
|
+
var inject = ["slots", "connection", "locale"];
|
|
33
|
+
|
|
34
|
+
/** Locale namespace owned by this plugin. */
|
|
35
|
+
var NS = "dshGit";
|
|
36
|
+
|
|
37
|
+
var zh = {
|
|
38
|
+
"panel.title": "Git",
|
|
39
|
+
"panel.collapse": "收起",
|
|
40
|
+
"refresh.aria": "刷新 Git 状态",
|
|
41
|
+
"branch.aria": "当前 Git 分支",
|
|
42
|
+
"switch.aria": "切换分支",
|
|
43
|
+
"switch.dirtyWarn": "有 {count} 个未提交文件(如 {paths}),切换分支可能被 git 拒绝,请先提交或暂存",
|
|
44
|
+
"switch.force": "仍要切换",
|
|
45
|
+
"branch.new": "新建分支",
|
|
46
|
+
"branch.newTitle": "从某个分支创建新分支",
|
|
47
|
+
"newBranch.placeholder": "新分支名…",
|
|
48
|
+
"newBranch.base": "基于分支",
|
|
49
|
+
"newBranch.create": "创建并切换",
|
|
50
|
+
"newBranch.cancel": "取消",
|
|
51
|
+
"newBranch.empty": "请输入新分支名",
|
|
52
|
+
"group.local": "本地分支",
|
|
53
|
+
"group.remote": "远程分支",
|
|
54
|
+
"loading": "读取中…",
|
|
55
|
+
"notRepo": "当前工作区不是 Git 仓库",
|
|
56
|
+
"detached": "游离 HEAD",
|
|
57
|
+
"dirty": "{count} 个未提交更改",
|
|
58
|
+
"aheadBehind": "领先 {ahead} / 落后 {behind}",
|
|
59
|
+
"action.fetch": "拉取更新",
|
|
60
|
+
"action.pull": "拉取合并",
|
|
61
|
+
"action.stage": "暂存全部",
|
|
62
|
+
"action.generate": "AI 生成",
|
|
63
|
+
"action.commit": "提交",
|
|
64
|
+
"action.push": "推送",
|
|
65
|
+
"generate.mode.staged": "已暂存",
|
|
66
|
+
"generate.mode.unstaged": "未暂存",
|
|
67
|
+
"generate.mode.all": "全部",
|
|
68
|
+
"generate.mode.title": "AI 生成的依据",
|
|
69
|
+
"generate.noChanges": "没有可用的改动",
|
|
70
|
+
"generate.done": "已生成提交信息",
|
|
71
|
+
"generate.noStaged": "没有已暂存的改动,请先点「暂存全部」,或把生成依据改为「未暂存」「全部」",
|
|
72
|
+
"generate.noProvider": "尚未配置模型,请先在 设置 > 模型 中添加",
|
|
73
|
+
"generate.noModel": "该提供方没有可用模型",
|
|
74
|
+
"generate.empty": "模型没有返回提交信息",
|
|
75
|
+
"generate.truncated": "模型还没写出提交信息就用完了 token 上限(思考模型会先花掉这份预算),再点一次通常即可",
|
|
76
|
+
"generate.cancelled": "生成已取消",
|
|
77
|
+
"generate.failed": "生成失败",
|
|
78
|
+
"commit.placeholder": "提交信息(可多行,Ctrl+Enter 提交)…",
|
|
79
|
+
"commit.empty": "请输入提交信息",
|
|
80
|
+
"changes.title": "变更({count})",
|
|
81
|
+
"changes.none": "工作区干净",
|
|
82
|
+
"diff.title": "差异",
|
|
83
|
+
"diff.open": "查看差异",
|
|
84
|
+
"diff.close": "关闭差异",
|
|
85
|
+
"diff.resize": "拖动面板右边缘调整差异宽度(双击复位)",
|
|
86
|
+
"diff.scope.worktree": "未暂存",
|
|
87
|
+
"diff.scope.index": "已暂存",
|
|
88
|
+
"diff.scope.title": "对比哪一侧",
|
|
89
|
+
"diff.empty": "这一侧没有改动",
|
|
90
|
+
"diff.emptyHint": "点「{other}」看另一侧",
|
|
91
|
+
"diff.untracked": "未跟踪文件:与空文件对比",
|
|
92
|
+
"diff.binary": "二进制文件,没有可显示的文本差异",
|
|
93
|
+
"diff.truncated": "差异过大,只显示了开头一段",
|
|
94
|
+
"diff.skipped": "另有 {count} 个未跟踪文件未展开",
|
|
95
|
+
"diff.showAll": "显示全部({count} 行)",
|
|
96
|
+
"diff.wrap": "自动换行",
|
|
97
|
+
"diff.copy": "复制差异",
|
|
98
|
+
"diff.copied": "已复制",
|
|
99
|
+
"diff.refresh": "重新读取差异",
|
|
100
|
+
"diff.loading": "读取差异中…",
|
|
101
|
+
"diff.failed": "读取差异失败",
|
|
102
|
+
"log.title": "最近提交",
|
|
103
|
+
"log.none": "暂无提交",
|
|
104
|
+
"busy.fetch": "拉取中…",
|
|
105
|
+
"busy.pull": "拉取合并中…",
|
|
106
|
+
"busy.stage": "暂存中…",
|
|
107
|
+
"busy.generate": "生成中…",
|
|
108
|
+
"busy.commit": "提交中…",
|
|
109
|
+
"busy.push": "推送中…",
|
|
110
|
+
"busy.checkout": "切换中…",
|
|
111
|
+
"busy.createBranch": "创建分支中…",
|
|
112
|
+
"output.ok": "操作成功",
|
|
113
|
+
"output.failed": "操作失败",
|
|
114
|
+
"output.detail": "详情",
|
|
115
|
+
"output.detailHide": "收起",
|
|
116
|
+
"output.fetch": "已拉取更新",
|
|
117
|
+
"output.pull": "已拉取合并",
|
|
118
|
+
"output.push": "已推送",
|
|
119
|
+
"output.stage": "已暂存全部",
|
|
120
|
+
"output.commit": "已提交",
|
|
121
|
+
"output.checkout": "已切换到 {branch}",
|
|
122
|
+
"output.createBranch": "已创建并切换到 {branch}",
|
|
123
|
+
"output.generate": "已生成提交信息",
|
|
124
|
+
"dock.aria": "打开 Git 面板",
|
|
125
|
+
"status.modified": "修改",
|
|
126
|
+
"status.added": "新增",
|
|
127
|
+
"status.deleted": "删除",
|
|
128
|
+
"status.renamed": "重命名",
|
|
129
|
+
"status.copied": "复制",
|
|
130
|
+
"status.untracked": "未跟踪",
|
|
131
|
+
"status.conflict": "冲突",
|
|
132
|
+
"status.type-changed": "类型变更",
|
|
133
|
+
"status.changed": "变更"
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
var en = {
|
|
137
|
+
"panel.title": "Git",
|
|
138
|
+
"panel.collapse": "Collapse",
|
|
139
|
+
"refresh.aria": "Refresh Git status",
|
|
140
|
+
"branch.aria": "Current git branch",
|
|
141
|
+
"switch.aria": "Switch branch",
|
|
142
|
+
"switch.dirtyWarn": "{count} uncommitted file(s) (e.g. {paths}) may block the switch — commit or stash first",
|
|
143
|
+
"switch.force": "Switch anyway",
|
|
144
|
+
"branch.new": "New branch",
|
|
145
|
+
"branch.newTitle": "Create a new branch from a branch",
|
|
146
|
+
"newBranch.placeholder": "New branch name…",
|
|
147
|
+
"newBranch.base": "Base branch",
|
|
148
|
+
"newBranch.create": "Create & switch",
|
|
149
|
+
"newBranch.cancel": "Cancel",
|
|
150
|
+
"newBranch.empty": "Enter a new branch name",
|
|
151
|
+
"group.local": "Local",
|
|
152
|
+
"group.remote": "Remote",
|
|
153
|
+
"loading": "Loading…",
|
|
154
|
+
"notRepo": "This workspace is not a git repository",
|
|
155
|
+
"detached": "detached HEAD",
|
|
156
|
+
"dirty": "{count} uncommitted change(s)",
|
|
157
|
+
"aheadBehind": "{ahead} ahead / {behind} behind",
|
|
158
|
+
"action.fetch": "Fetch",
|
|
159
|
+
"action.pull": "Pull",
|
|
160
|
+
"action.stage": "Stage all",
|
|
161
|
+
"action.generate": "AI draft",
|
|
162
|
+
"action.commit": "Commit",
|
|
163
|
+
"action.push": "Push",
|
|
164
|
+
"generate.mode.staged": "Staged",
|
|
165
|
+
"generate.mode.unstaged": "Unstaged",
|
|
166
|
+
"generate.mode.all": "Everything",
|
|
167
|
+
"generate.mode.title": "What the draft is based on",
|
|
168
|
+
"generate.noChanges": "No changes to draft from",
|
|
169
|
+
"generate.done": "Draft ready",
|
|
170
|
+
"generate.noStaged": "Nothing is staged yet — use Stage all first, or switch the basis to Unstaged / Everything",
|
|
171
|
+
"generate.noProvider": "No model is configured — add one in Settings > Models",
|
|
172
|
+
"generate.noModel": "That provider advertises no model",
|
|
173
|
+
"generate.empty": "The model returned no commit message",
|
|
174
|
+
"generate.truncated": "The model hit its token cap before writing anything (a reasoning model spends that budget thinking first) — trying again usually works",
|
|
175
|
+
"generate.cancelled": "Draft cancelled",
|
|
176
|
+
"generate.failed": "Draft failed",
|
|
177
|
+
"commit.placeholder": "Commit message (multi-line allowed, Ctrl+Enter commits)…",
|
|
178
|
+
"commit.empty": "Enter a commit message",
|
|
179
|
+
"changes.title": "Changes ({count})",
|
|
180
|
+
"changes.none": "Working tree clean",
|
|
181
|
+
"diff.title": "Diff",
|
|
182
|
+
"diff.open": "View diff",
|
|
183
|
+
"diff.close": "Close diff",
|
|
184
|
+
"diff.resize": "Drag the panel's right edge to resize the diff (double-click resets)",
|
|
185
|
+
"diff.scope.worktree": "Unstaged",
|
|
186
|
+
"diff.scope.index": "Staged",
|
|
187
|
+
"diff.scope.title": "Which side to compare",
|
|
188
|
+
"diff.empty": "Nothing changed on this side",
|
|
189
|
+
"diff.emptyHint": "Pick “{other}” to see the other side",
|
|
190
|
+
"diff.untracked": "Untracked file: compared against the empty blob",
|
|
191
|
+
"diff.binary": "Binary file — there is no text diff to show",
|
|
192
|
+
"diff.truncated": "Diff too large — only the beginning is shown",
|
|
193
|
+
"diff.skipped": "{count} more untracked file(s) were not expanded",
|
|
194
|
+
"diff.showAll": "Show all ({count} lines)",
|
|
195
|
+
"diff.wrap": "Wrap lines",
|
|
196
|
+
"diff.copy": "Copy diff",
|
|
197
|
+
"diff.copied": "Copied",
|
|
198
|
+
"diff.refresh": "Reload the diff",
|
|
199
|
+
"diff.loading": "Loading the diff…",
|
|
200
|
+
"diff.failed": "Could not read the diff",
|
|
201
|
+
"log.title": "Recent commits",
|
|
202
|
+
"log.none": "No commits yet",
|
|
203
|
+
"busy.fetch": "Fetching…",
|
|
204
|
+
"busy.pull": "Pulling…",
|
|
205
|
+
"busy.stage": "Staging…",
|
|
206
|
+
"busy.generate": "Drafting…",
|
|
207
|
+
"busy.commit": "Committing…",
|
|
208
|
+
"busy.push": "Pushing…",
|
|
209
|
+
"busy.checkout": "Switching…",
|
|
210
|
+
"busy.createBranch": "Creating branch…",
|
|
211
|
+
"output.ok": "Operation succeeded",
|
|
212
|
+
"output.failed": "Operation failed",
|
|
213
|
+
"output.detail": "Details",
|
|
214
|
+
"output.detailHide": "Hide",
|
|
215
|
+
"output.fetch": "Fetched",
|
|
216
|
+
"output.pull": "Pulled",
|
|
217
|
+
"output.push": "Pushed",
|
|
218
|
+
"output.stage": "Staged everything",
|
|
219
|
+
"output.commit": "Committed",
|
|
220
|
+
"output.checkout": "Switched to {branch}",
|
|
221
|
+
"output.createBranch": "Created and switched to {branch}",
|
|
222
|
+
"output.generate": "Draft ready",
|
|
223
|
+
"dock.aria": "Open Git panel",
|
|
224
|
+
"status.modified": "modified",
|
|
225
|
+
"status.added": "added",
|
|
226
|
+
"status.deleted": "deleted",
|
|
227
|
+
"status.renamed": "renamed",
|
|
228
|
+
"status.copied": "copied",
|
|
229
|
+
"status.untracked": "untracked",
|
|
230
|
+
"status.conflict": "conflict",
|
|
231
|
+
"status.type-changed": "type changed",
|
|
232
|
+
"status.changed": "changed"
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/** React.createElement shorthand. */
|
|
236
|
+
function h(type, props) {
|
|
237
|
+
var children = Array.prototype.slice.call(arguments, 2);
|
|
238
|
+
return React.createElement.apply(React, [type, props].concat(children));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Compact inline styles tuned to the shell overlay / composer dock. */
|
|
242
|
+
var S = {
|
|
243
|
+
panel: {
|
|
244
|
+
position: "fixed",
|
|
245
|
+
// Sit above the composer card so the expanded panel never covers
|
|
246
|
+
// the input box (the input.left pill sits in the tool row).
|
|
247
|
+
bottom: 168,
|
|
248
|
+
left: "50%",
|
|
249
|
+
transform: "translateX(-50%)",
|
|
250
|
+
zIndex: 30,
|
|
251
|
+
// border-box so `width` below is the OUTER width: the two-pane math
|
|
252
|
+
// (workbench + splitter + diff) and the viewport clamp both count
|
|
253
|
+
// real pixels, and a content-box width silently added the border on
|
|
254
|
+
// top of them.
|
|
255
|
+
boxSizing: "border-box",
|
|
256
|
+
width: 400,
|
|
257
|
+
maxWidth: "calc(100vw - 24px)",
|
|
258
|
+
maxHeight: "60vh",
|
|
259
|
+
display: "flex",
|
|
260
|
+
flexDirection: "column",
|
|
261
|
+
border: "1px solid var(--dsw-alias-border-l1, rgba(127,127,127,0.3))",
|
|
262
|
+
borderRadius: 12,
|
|
263
|
+
background: "var(--dsw-alias-bg-base, #1e1e1e)",
|
|
264
|
+
boxShadow: "var(--dsw-shadow-lv2, 0 8px 32px rgba(0,0,0,0.45))",
|
|
265
|
+
overflow: "hidden",
|
|
266
|
+
pointerEvents: "auto"
|
|
267
|
+
},
|
|
268
|
+
header: {
|
|
269
|
+
display: "flex",
|
|
270
|
+
alignItems: "center",
|
|
271
|
+
gap: 8,
|
|
272
|
+
padding: "8px 12px",
|
|
273
|
+
borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.2))",
|
|
274
|
+
flex: "none",
|
|
275
|
+
cursor: "move",
|
|
276
|
+
userSelect: "none",
|
|
277
|
+
touchAction: "none",
|
|
278
|
+
// Painted above the right-edge resize handle, which spans the panel's
|
|
279
|
+
// full height: the header's drag gesture and its buttons own that band.
|
|
280
|
+
position: "relative",
|
|
281
|
+
zIndex: 1
|
|
282
|
+
},
|
|
283
|
+
title: {
|
|
284
|
+
fontSize: 13,
|
|
285
|
+
fontWeight: 600,
|
|
286
|
+
color: "var(--dsw-alias-label-primary, #e5e7eb)",
|
|
287
|
+
flex: "1",
|
|
288
|
+
minWidth: 0,
|
|
289
|
+
overflow: "hidden",
|
|
290
|
+
textOverflow: "ellipsis",
|
|
291
|
+
whiteSpace: "nowrap"
|
|
292
|
+
},
|
|
293
|
+
body: {
|
|
294
|
+
flex: "1",
|
|
295
|
+
minHeight: 0,
|
|
296
|
+
padding: "10px 12px 12px",
|
|
297
|
+
overflowY: "auto"
|
|
298
|
+
},
|
|
299
|
+
row: {
|
|
300
|
+
display: "flex",
|
|
301
|
+
alignItems: "center",
|
|
302
|
+
gap: 8,
|
|
303
|
+
margin: "6px 0"
|
|
304
|
+
},
|
|
305
|
+
warn: {
|
|
306
|
+
display: "flex",
|
|
307
|
+
alignItems: "center",
|
|
308
|
+
gap: 8,
|
|
309
|
+
margin: "6px 0",
|
|
310
|
+
padding: "6px 8px",
|
|
311
|
+
borderRadius: 8,
|
|
312
|
+
background: "var(--dsw-alias-state-warning-tertiary, rgba(245,158,11,0.14))",
|
|
313
|
+
border: "1px solid var(--dsw-alias-state-warning-secondary, rgba(245,158,11,0.4))",
|
|
314
|
+
color: "var(--dsw-alias-state-warning-primary, #f59e0b)",
|
|
315
|
+
fontSize: 12,
|
|
316
|
+
lineHeight: "16px"
|
|
317
|
+
},
|
|
318
|
+
chip: {
|
|
319
|
+
background: "var(--dsw-alias-surface-tertiary, rgba(127,127,127,0.12))",
|
|
320
|
+
color: "var(--dsw-alias-label-secondary, #c8ccd2)",
|
|
321
|
+
borderRadius: 999,
|
|
322
|
+
padding: "0 8px",
|
|
323
|
+
fontWeight: 500,
|
|
324
|
+
fontSize: 12,
|
|
325
|
+
lineHeight: "20px",
|
|
326
|
+
overflow: "hidden",
|
|
327
|
+
textOverflow: "ellipsis",
|
|
328
|
+
whiteSpace: "nowrap"
|
|
329
|
+
},
|
|
330
|
+
dirty: {
|
|
331
|
+
color: "var(--dsw-alias-state-warn-primary, #f0a020)",
|
|
332
|
+
fontWeight: 600,
|
|
333
|
+
fontSize: 12
|
|
334
|
+
},
|
|
335
|
+
meta: {
|
|
336
|
+
color: "var(--dsw-alias-label-quaternary, #6b7280)",
|
|
337
|
+
fontSize: 12
|
|
338
|
+
},
|
|
339
|
+
button: {
|
|
340
|
+
background: "var(--dsw-alias-interactive-bg-hover-solid, rgba(127,127,127,0.14))",
|
|
341
|
+
border: "1px solid var(--dsw-alias-border-l1, rgba(127,127,127,0.3))",
|
|
342
|
+
borderRadius: 8,
|
|
343
|
+
color: "var(--dsw-alias-label-secondary, #c8ccd2)",
|
|
344
|
+
font: "inherit",
|
|
345
|
+
fontSize: 12,
|
|
346
|
+
lineHeight: "22px",
|
|
347
|
+
padding: "0 10px",
|
|
348
|
+
cursor: "pointer",
|
|
349
|
+
flex: "none"
|
|
350
|
+
},
|
|
351
|
+
buttonPrimary: {
|
|
352
|
+
background: "var(--dsw-alias-state-business-primary, #4f8cff)",
|
|
353
|
+
border: "1px solid transparent",
|
|
354
|
+
borderRadius: 8,
|
|
355
|
+
color: "#fff",
|
|
356
|
+
font: "inherit",
|
|
357
|
+
fontSize: 12,
|
|
358
|
+
lineHeight: "22px",
|
|
359
|
+
padding: "0 10px",
|
|
360
|
+
cursor: "pointer",
|
|
361
|
+
flex: "none"
|
|
362
|
+
},
|
|
363
|
+
buttonDisabled: {
|
|
364
|
+
opacity: 0.5,
|
|
365
|
+
cursor: "default"
|
|
366
|
+
},
|
|
367
|
+
select: {
|
|
368
|
+
// colorScheme makes the NATIVE dropdown listbox follow the page
|
|
369
|
+
// theme (dark list on dark pages) instead of the OS default
|
|
370
|
+
// white; option/optgroup styles above reinforce it.
|
|
371
|
+
colorScheme: "light dark",
|
|
372
|
+
background: "var(--dsw-alias-bg-layer-1, rgba(127,127,127,0.1))",
|
|
373
|
+
border: "1px solid var(--dsw-alias-separator-primary, rgba(127,127,127,0.25))",
|
|
374
|
+
borderRadius: 8,
|
|
375
|
+
color: "var(--dsw-alias-label-secondary, #c8ccd2)",
|
|
376
|
+
font: "inherit",
|
|
377
|
+
fontSize: 12,
|
|
378
|
+
lineHeight: "20px",
|
|
379
|
+
padding: "2px 8px",
|
|
380
|
+
maxWidth: 220,
|
|
381
|
+
cursor: "pointer",
|
|
382
|
+
flex: "1",
|
|
383
|
+
minWidth: 0,
|
|
384
|
+
outline: "none"
|
|
385
|
+
},
|
|
386
|
+
input: {
|
|
387
|
+
background: "transparent",
|
|
388
|
+
border: "1px solid var(--dsw-alias-separator-primary, rgba(127,127,127,0.25))",
|
|
389
|
+
borderRadius: 8,
|
|
390
|
+
color: "var(--dsw-alias-label-primary, #e5e7eb)",
|
|
391
|
+
font: "inherit",
|
|
392
|
+
fontSize: 12,
|
|
393
|
+
lineHeight: "20px",
|
|
394
|
+
padding: "2px 8px",
|
|
395
|
+
flex: "1",
|
|
396
|
+
minWidth: 0,
|
|
397
|
+
outline: "none"
|
|
398
|
+
},
|
|
399
|
+
// The commit message is a real multi-line field: an <input type=text>
|
|
400
|
+
// silently strips every line feed, so a drafted subject+body would lose
|
|
401
|
+
// its body before the request was ever sent.
|
|
402
|
+
textarea: {
|
|
403
|
+
background: "transparent",
|
|
404
|
+
border: "1px solid var(--dsw-alias-separator-primary, rgba(127,127,127,0.25))",
|
|
405
|
+
borderRadius: 8,
|
|
406
|
+
color: "var(--dsw-alias-label-primary, #e5e7eb)",
|
|
407
|
+
font: "inherit",
|
|
408
|
+
fontSize: 12,
|
|
409
|
+
lineHeight: "18px",
|
|
410
|
+
padding: "4px 8px",
|
|
411
|
+
flex: "1",
|
|
412
|
+
minWidth: 0,
|
|
413
|
+
outline: "none",
|
|
414
|
+
resize: "vertical",
|
|
415
|
+
overflowY: "auto"
|
|
416
|
+
},
|
|
417
|
+
sectionTitle: {
|
|
418
|
+
fontSize: 12,
|
|
419
|
+
fontWeight: 600,
|
|
420
|
+
color: "var(--dsw-alias-label-tertiary, #9aa0a6)",
|
|
421
|
+
margin: "10px 0 4px",
|
|
422
|
+
cursor: "pointer",
|
|
423
|
+
userSelect: "none"
|
|
424
|
+
},
|
|
425
|
+
changeRow: {
|
|
426
|
+
display: "flex",
|
|
427
|
+
alignItems: "center",
|
|
428
|
+
gap: 8,
|
|
429
|
+
fontSize: 12,
|
|
430
|
+
lineHeight: "20px",
|
|
431
|
+
color: "var(--dsw-alias-label-secondary, #c8ccd2)",
|
|
432
|
+
padding: "1px 6px",
|
|
433
|
+
margin: "0 -6px",
|
|
434
|
+
borderRadius: 6,
|
|
435
|
+
minWidth: 0,
|
|
436
|
+
cursor: "pointer"
|
|
437
|
+
},
|
|
438
|
+
changePath: {
|
|
439
|
+
overflow: "hidden",
|
|
440
|
+
textOverflow: "ellipsis",
|
|
441
|
+
whiteSpace: "nowrap",
|
|
442
|
+
flex: "1",
|
|
443
|
+
minWidth: 0
|
|
444
|
+
},
|
|
445
|
+
// A change row is the diff viewer's navigation: it has to read as a
|
|
446
|
+
// control, and to show which file the right-hand pane is showing.
|
|
447
|
+
changeRowActive: {
|
|
448
|
+
display: "flex",
|
|
449
|
+
alignItems: "center",
|
|
450
|
+
gap: 8,
|
|
451
|
+
fontSize: 12,
|
|
452
|
+
lineHeight: "20px",
|
|
453
|
+
color: "var(--dsw-alias-label-primary, #e5e7eb)",
|
|
454
|
+
padding: "1px 6px",
|
|
455
|
+
margin: "0 -6px",
|
|
456
|
+
borderRadius: 6,
|
|
457
|
+
minWidth: 0,
|
|
458
|
+
cursor: "pointer",
|
|
459
|
+
background: "var(--dsw-alias-interactive-bg-hover-solid, rgba(127,127,127,0.18))"
|
|
460
|
+
},
|
|
461
|
+
// ── two-pane (changes + diff) layout ────────────────────────────
|
|
462
|
+
// `bodySplit` replaces `body` while a file is selected: the left pane
|
|
463
|
+
// keeps the whole workbench, the splitter is the width handle, and the
|
|
464
|
+
// diff pane owns its own header + scrollport.
|
|
465
|
+
bodySplit: {
|
|
466
|
+
flex: "1",
|
|
467
|
+
minHeight: 0,
|
|
468
|
+
display: "flex",
|
|
469
|
+
flexDirection: "row",
|
|
470
|
+
overflow: "hidden"
|
|
471
|
+
},
|
|
472
|
+
leftPane: {
|
|
473
|
+
flex: "none",
|
|
474
|
+
// border-box: DIFF_LEFT_PANE_WIDTH is the room the workbench really
|
|
475
|
+
// takes, so the pane width math and the panel edge agree.
|
|
476
|
+
boxSizing: "border-box",
|
|
477
|
+
width: 300,
|
|
478
|
+
minWidth: 0,
|
|
479
|
+
padding: "10px 12px 12px",
|
|
480
|
+
// The two panes are separated by this edge instead of a draggable
|
|
481
|
+
// divider: the width handle is the panel's own right edge.
|
|
482
|
+
borderRight: "1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.2))",
|
|
483
|
+
overflowY: "auto"
|
|
484
|
+
},
|
|
485
|
+
// The panel's right edge, as the diff's width handle (the whole panel
|
|
486
|
+
// grows with it; the workbench keeps its width).
|
|
487
|
+
resizeHandle: {
|
|
488
|
+
position: "absolute",
|
|
489
|
+
top: 0,
|
|
490
|
+
right: 0,
|
|
491
|
+
bottom: 0,
|
|
492
|
+
width: 6,
|
|
493
|
+
cursor: "col-resize",
|
|
494
|
+
touchAction: "none",
|
|
495
|
+
background: "transparent"
|
|
496
|
+
},
|
|
497
|
+
resizeHandleActive: {
|
|
498
|
+
background: "var(--dsw-alias-interactive-bg-hover-solid, rgba(127,127,127,0.3))"
|
|
499
|
+
},
|
|
500
|
+
diffPane: {
|
|
501
|
+
flex: "1",
|
|
502
|
+
minWidth: 0,
|
|
503
|
+
display: "flex",
|
|
504
|
+
flexDirection: "column",
|
|
505
|
+
overflow: "hidden"
|
|
506
|
+
},
|
|
507
|
+
diffHeader: {
|
|
508
|
+
flex: "none",
|
|
509
|
+
display: "flex",
|
|
510
|
+
alignItems: "center",
|
|
511
|
+
gap: 6,
|
|
512
|
+
flexWrap: "wrap",
|
|
513
|
+
padding: "8px 10px",
|
|
514
|
+
borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.2))"
|
|
515
|
+
},
|
|
516
|
+
diffPath: {
|
|
517
|
+
flex: "1 1 auto",
|
|
518
|
+
minWidth: 0,
|
|
519
|
+
overflow: "hidden",
|
|
520
|
+
textOverflow: "ellipsis",
|
|
521
|
+
whiteSpace: "nowrap",
|
|
522
|
+
fontSize: 12,
|
|
523
|
+
fontWeight: 600,
|
|
524
|
+
color: "var(--dsw-alias-label-primary, #e5e7eb)"
|
|
525
|
+
},
|
|
526
|
+
scopeChip: {
|
|
527
|
+
flex: "none",
|
|
528
|
+
lineHeight: "18px",
|
|
529
|
+
padding: "0 8px",
|
|
530
|
+
borderRadius: 999,
|
|
531
|
+
border: "1px solid var(--dsw-alias-separator-primary, rgba(127,127,127,0.25))",
|
|
532
|
+
background: "transparent",
|
|
533
|
+
color: "var(--dsw-alias-label-tertiary, #9aa0a6)",
|
|
534
|
+
font: "inherit",
|
|
535
|
+
fontSize: 11,
|
|
536
|
+
cursor: "pointer"
|
|
537
|
+
},
|
|
538
|
+
scopeChipActive: {
|
|
539
|
+
flex: "none",
|
|
540
|
+
lineHeight: "18px",
|
|
541
|
+
padding: "0 8px",
|
|
542
|
+
borderRadius: 999,
|
|
543
|
+
border: "1px solid transparent",
|
|
544
|
+
background: "var(--dsw-alias-surface-tertiary, rgba(127,127,127,0.18))",
|
|
545
|
+
color: "var(--dsw-alias-label-primary, #e5e7eb)",
|
|
546
|
+
font: "inherit",
|
|
547
|
+
fontSize: 11,
|
|
548
|
+
cursor: "pointer"
|
|
549
|
+
},
|
|
550
|
+
diffScroll: {
|
|
551
|
+
flex: "1",
|
|
552
|
+
minHeight: 0,
|
|
553
|
+
overflow: "auto",
|
|
554
|
+
padding: "6px 0"
|
|
555
|
+
},
|
|
556
|
+
diffRow: {
|
|
557
|
+
display: "flex",
|
|
558
|
+
alignItems: "flex-start",
|
|
559
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
560
|
+
fontSize: 12,
|
|
561
|
+
lineHeight: "18px",
|
|
562
|
+
// `max-content` keeps a long line's row as wide as its text, so the
|
|
563
|
+
// added/removed background runs the whole length of the horizontal
|
|
564
|
+
// scroll. `diffRowView` drops it to 0 when wrapping is on, where it
|
|
565
|
+
// would otherwise prevent the wrap from ever firing.
|
|
566
|
+
minWidth: "max-content",
|
|
567
|
+
width: "100%"
|
|
568
|
+
},
|
|
569
|
+
diffGutter: {
|
|
570
|
+
flex: "none",
|
|
571
|
+
width: 34,
|
|
572
|
+
textAlign: "right",
|
|
573
|
+
paddingRight: 6,
|
|
574
|
+
color: "var(--dsw-alias-label-quaternary, #6b7280)",
|
|
575
|
+
userSelect: "none",
|
|
576
|
+
WebkitUserSelect: "none"
|
|
577
|
+
},
|
|
578
|
+
diffText: {
|
|
579
|
+
flex: "1",
|
|
580
|
+
minWidth: 0,
|
|
581
|
+
paddingRight: 12,
|
|
582
|
+
tabSize: 4,
|
|
583
|
+
whiteSpace: "pre"
|
|
584
|
+
},
|
|
585
|
+
diffAddRow: {
|
|
586
|
+
background: "rgba(52,199,89,0.14)",
|
|
587
|
+
color: "var(--dsw-alias-state-success-primary, #34c759)"
|
|
588
|
+
},
|
|
589
|
+
diffDelRow: {
|
|
590
|
+
background: "rgba(239,68,68,0.14)",
|
|
591
|
+
color: "var(--dsw-alias-state-error-primary, #ef4444)"
|
|
592
|
+
},
|
|
593
|
+
diffHunkRow: {
|
|
594
|
+
background: "rgba(79,140,255,0.12)",
|
|
595
|
+
color: "var(--dsw-alias-state-business-primary, #4f8cff)"
|
|
596
|
+
},
|
|
597
|
+
diffMetaRow: {
|
|
598
|
+
color: "var(--dsw-alias-label-quaternary, #6b7280)"
|
|
599
|
+
},
|
|
600
|
+
diffNotice: {
|
|
601
|
+
margin: "6px 10px",
|
|
602
|
+
padding: "6px 8px",
|
|
603
|
+
borderRadius: 8,
|
|
604
|
+
fontSize: 12,
|
|
605
|
+
lineHeight: "18px",
|
|
606
|
+
background: "var(--dsw-alias-surface-tertiary, rgba(127,127,127,0.12))",
|
|
607
|
+
color: "var(--dsw-alias-label-secondary, #c8ccd2)"
|
|
608
|
+
},
|
|
609
|
+
statusTag: {
|
|
610
|
+
flex: "none",
|
|
611
|
+
fontSize: 11,
|
|
612
|
+
lineHeight: "16px",
|
|
613
|
+
padding: "0 6px",
|
|
614
|
+
borderRadius: 4,
|
|
615
|
+
background: "var(--dsw-alias-surface-tertiary, rgba(127,127,127,0.12))"
|
|
616
|
+
},
|
|
617
|
+
logRow: {
|
|
618
|
+
display: "flex",
|
|
619
|
+
alignItems: "center",
|
|
620
|
+
gap: 8,
|
|
621
|
+
fontSize: 12,
|
|
622
|
+
lineHeight: "20px",
|
|
623
|
+
color: "var(--dsw-alias-label-secondary, #c8ccd2)",
|
|
624
|
+
padding: "1px 0",
|
|
625
|
+
minWidth: 0
|
|
626
|
+
},
|
|
627
|
+
logSha: {
|
|
628
|
+
flex: "none",
|
|
629
|
+
color: "var(--dsw-alias-state-business-primary, #4f8cff)",
|
|
630
|
+
fontFamily: "monospace"
|
|
631
|
+
},
|
|
632
|
+
output: {
|
|
633
|
+
// Sits at the top of the body, so the breathing room belongs below it.
|
|
634
|
+
// The card itself is one localized summary line tall; git's own words
|
|
635
|
+
// live in `outputDetail`, behind the toggle.
|
|
636
|
+
marginBottom: 8,
|
|
637
|
+
padding: "6px 10px",
|
|
638
|
+
borderRadius: 8,
|
|
639
|
+
fontSize: 12,
|
|
640
|
+
lineHeight: "18px"
|
|
641
|
+
},
|
|
642
|
+
outputLine: {
|
|
643
|
+
display: "flex",
|
|
644
|
+
alignItems: "center",
|
|
645
|
+
gap: 8
|
|
646
|
+
},
|
|
647
|
+
outputSummary: {
|
|
648
|
+
flex: "1",
|
|
649
|
+
minWidth: 0,
|
|
650
|
+
overflow: "hidden",
|
|
651
|
+
textOverflow: "ellipsis",
|
|
652
|
+
whiteSpace: "nowrap"
|
|
653
|
+
},
|
|
654
|
+
outputToggle: {
|
|
655
|
+
flex: "none",
|
|
656
|
+
padding: "0 6px",
|
|
657
|
+
height: 20,
|
|
658
|
+
border: "1px solid currentColor",
|
|
659
|
+
borderRadius: 6,
|
|
660
|
+
background: "transparent",
|
|
661
|
+
color: "inherit",
|
|
662
|
+
font: "inherit",
|
|
663
|
+
fontSize: 11,
|
|
664
|
+
lineHeight: "18px",
|
|
665
|
+
cursor: "pointer"
|
|
666
|
+
},
|
|
667
|
+
outputDetail: {
|
|
668
|
+
marginTop: 6,
|
|
669
|
+
paddingTop: 6,
|
|
670
|
+
borderTop: "1px solid var(--dsw-alias-border-secondary, rgba(127,127,127,0.35))",
|
|
671
|
+
fontFamily: "monospace",
|
|
672
|
+
fontSize: 11,
|
|
673
|
+
lineHeight: "16px",
|
|
674
|
+
wordBreak: "break-word",
|
|
675
|
+
maxHeight: 160,
|
|
676
|
+
overflowY: "auto",
|
|
677
|
+
whiteSpace: "pre-wrap"
|
|
678
|
+
},
|
|
679
|
+
outputOk: {
|
|
680
|
+
background: "var(--dsw-alias-state-success-tertiary, rgba(52,199,89,0.12))",
|
|
681
|
+
color: "var(--dsw-alias-state-success-primary, #34c759)"
|
|
682
|
+
},
|
|
683
|
+
outputError: {
|
|
684
|
+
background: "var(--dsw-alias-state-error-tertiary, rgba(239,68,68,0.12))",
|
|
685
|
+
color: "var(--dsw-alias-state-error-primary, #ef4444)"
|
|
686
|
+
},
|
|
687
|
+
dockWrap: {
|
|
688
|
+
display: "inline-flex",
|
|
689
|
+
alignItems: "center",
|
|
690
|
+
gap: 6,
|
|
691
|
+
maxWidth: "100%",
|
|
692
|
+
fontSize: 12,
|
|
693
|
+
lineHeight: "20px",
|
|
694
|
+
color: "var(--dsw-alias-label-tertiary, #9aa0a6)",
|
|
695
|
+
whiteSpace: "nowrap",
|
|
696
|
+
overflow: "hidden",
|
|
697
|
+
textOverflow: "ellipsis",
|
|
698
|
+
cursor: "pointer"
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
/** Truncate a display string. */
|
|
703
|
+
function shortText(value, max) {
|
|
704
|
+
var text = typeof value === "string" ? value : String(value);
|
|
705
|
+
return text.length > max ? text.slice(0, max - 1) + "…" : text;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Strip ANSI/VT escape sequences from git's human-readable output.
|
|
710
|
+
*
|
|
711
|
+
* `git push` forwards the remote's own sideband banner verbatim — Gitee,
|
|
712
|
+
* for one, decorates "Powered by GITEE.COM" with SGR codes, and its first
|
|
713
|
+
* sequence is even truncated (`ESC[0`) right before a complete one. A DOM
|
|
714
|
+
* is not a terminal: the ESC byte has no glyph, so only the parameters
|
|
715
|
+
* survived and the banner read `Powered by [0[01;33mGITEE.COM [0[m…`.
|
|
716
|
+
*
|
|
717
|
+
* Only `message` text passes through here. Diff bodies and paths must not:
|
|
718
|
+
* a file may legitimately contain ESC bytes and the pane shows it verbatim.
|
|
719
|
+
*
|
|
720
|
+
* @param text - a git message string (or anything else).
|
|
721
|
+
* @returns the text with escape sequences removed.
|
|
722
|
+
*/
|
|
723
|
+
function stripAnsiEscapes(text) {
|
|
724
|
+
return (text === undefined || text === null ? "" : String(text))
|
|
725
|
+
// OSC (window title / hyperlink): ESC ] … BEL or ESC \
|
|
726
|
+
.replace(/\u001B\][^\u0007\u001B]*(?:\u0007|\u001B\\)/g, "")
|
|
727
|
+
// A complete CSI sequence: ESC [ params/intermediates final byte
|
|
728
|
+
.replace(/\u001B\[[0-9;?]*[ -/]*[@-~]/g, "")
|
|
729
|
+
// A truncated CSI (the remote's malformed banner): ESC [ params only
|
|
730
|
+
.replace(/\u001B\[[0-9;?]*/g, "")
|
|
731
|
+
// Any other two-character escape, then any surviving ESC.
|
|
732
|
+
.replace(/\u001B[@-Z\\-_]/g, "")
|
|
733
|
+
.replace(/\u001B/g, "");
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/** Sanitize one RPC result's human-readable text, leaving data untouched. */
|
|
737
|
+
function sanitizeResult(result) {
|
|
738
|
+
if (result === null || typeof result !== "object") return result;
|
|
739
|
+
var message = function (holder) {
|
|
740
|
+
return holder !== null && typeof holder === "object" && typeof holder.message === "string"
|
|
741
|
+
? Object.assign({}, holder, { message: stripAnsiEscapes(holder.message) })
|
|
742
|
+
: holder;
|
|
743
|
+
};
|
|
744
|
+
var value = message(result.value);
|
|
745
|
+
var error = message(result.error);
|
|
746
|
+
if (value === result.value && error === result.error) return result;
|
|
747
|
+
return Object.assign({}, result, { value: value, error: error });
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/** Longest one-line summary an operation card shows before folding. */
|
|
751
|
+
var OUTPUT_SUMMARY_MAX = 160;
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Word one operation result the default way: a summary the panel can
|
|
755
|
+
* localize from `action` (the store holds no translator), plus git's own
|
|
756
|
+
* output as collapsible detail.
|
|
757
|
+
*
|
|
758
|
+
* git's output is often several lines of advice — the LF/CRLF warning on
|
|
759
|
+
* every staged file, a remote's push banner — and showing it as the
|
|
760
|
+
* notification buries the answer to "did it work?". So success keeps git's
|
|
761
|
+
* words behind the toggle, and a failure shows the head of the diagnostic
|
|
762
|
+
* with the whole thing one click away.
|
|
763
|
+
*
|
|
764
|
+
* @param action - the operation name (`push`, `stage`, …).
|
|
765
|
+
* @param res - the raw RPC result.
|
|
766
|
+
* @param params - interpolation for the localized summary (e.g. `{ branch }`).
|
|
767
|
+
* @returns `{ kind, action, params, text, detail }`.
|
|
768
|
+
*/
|
|
769
|
+
function summarizeResult(action, res, params) {
|
|
770
|
+
if (res && res.ok === true) {
|
|
771
|
+
var raw = res.value && typeof res.value.message === "string" ? res.value.message.trim() : "";
|
|
772
|
+
return { kind: "ok", action: action, params: params || null, text: null, detail: raw };
|
|
773
|
+
}
|
|
774
|
+
var message = res && res.error && res.error.message ? String(res.error.message).trim() : "";
|
|
775
|
+
return {
|
|
776
|
+
kind: "error",
|
|
777
|
+
action: action,
|
|
778
|
+
params: params || null,
|
|
779
|
+
text: message === "" ? null : shortText(message, OUTPUT_SUMMARY_MAX),
|
|
780
|
+
detail: message.length > OUTPUT_SUMMARY_MAX ? message : ""
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* One operation-feedback card: the localized summary line and, when git
|
|
786
|
+
* said anything, git's own output behind a toggle. Pure and exported, so
|
|
787
|
+
* the render test can drive both states (SSR cannot click).
|
|
788
|
+
*
|
|
789
|
+
* @param result - `state.lastResult` (`{ kind, action, params, text, detail }`).
|
|
790
|
+
* @param t - the locale translator.
|
|
791
|
+
* @param open - whether the detail block is expanded.
|
|
792
|
+
* @param onToggle - called when the detail toggle is clicked.
|
|
793
|
+
* @returns the card element, or null without a result.
|
|
794
|
+
*/
|
|
795
|
+
function outputView(result, t, open, onToggle) {
|
|
796
|
+
if (!result) return null;
|
|
797
|
+
var summary = result.text !== undefined && result.text !== null && String(result.text) !== ""
|
|
798
|
+
? String(result.text)
|
|
799
|
+
: result.action
|
|
800
|
+
? t("output." + result.action, result.params || undefined)
|
|
801
|
+
: t(result.kind === "error" ? "output.failed" : "output.ok");
|
|
802
|
+
var detail = typeof result.detail === "string" ? result.detail.trim() : "";
|
|
803
|
+
var line = [
|
|
804
|
+
h("span", { key: "text", style: S.outputSummary, title: summary }, summary)
|
|
805
|
+
];
|
|
806
|
+
if (detail !== "") {
|
|
807
|
+
line.push(h("button", {
|
|
808
|
+
key: "toggle",
|
|
809
|
+
type: "button",
|
|
810
|
+
"data-dsh-git": "output-toggle",
|
|
811
|
+
style: S.outputToggle,
|
|
812
|
+
"aria-expanded": open ? "true" : "false",
|
|
813
|
+
onClick: onToggle
|
|
814
|
+
}, (open ? "▴ " : "▾ ") + t(open ? "output.detailHide" : "output.detail")));
|
|
815
|
+
}
|
|
816
|
+
var children = [h("div", { key: "line", style: S.outputLine }, line)];
|
|
817
|
+
if (open && detail !== "") {
|
|
818
|
+
children.push(h("div", { key: "detail", "data-dsh-git": "output-detail", style: S.outputDetail }, detail));
|
|
819
|
+
}
|
|
820
|
+
return h("div", {
|
|
821
|
+
key: "output",
|
|
822
|
+
"data-dsh-git": "output",
|
|
823
|
+
style: Object.assign({}, S.output, result.kind === "ok" ? S.outputOk : S.outputError)
|
|
824
|
+
}, children);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/** Call one /dsh-git-rpc endpoint. */
|
|
828
|
+
function rpc(ctx, endpoint, args) {
|
|
829
|
+
return ctx.connection.rpc.call("/dsh-git-rpc", endpoint, { args: args || {} }).then(sanitizeResult);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Shared Git store: one load per cwd (deduplicated), real-time rebind on
|
|
834
|
+
* session/workspace change, and post-action refresh so every seat (panel
|
|
835
|
+
* + dock) sees the same truth. Module-level singleton per apply().
|
|
836
|
+
*/
|
|
837
|
+
function createGitStore(ctx) {
|
|
838
|
+
var verbs = {
|
|
839
|
+
status: function (cwd) { return rpc(ctx, "status", { cwd: cwd }); },
|
|
840
|
+
branches: function (cwd) { return rpc(ctx, "branches", { cwd: cwd }); },
|
|
841
|
+
checkout: function (cwd, branch) { return rpc(ctx, "checkout", { cwd: cwd, branch: branch }); },
|
|
842
|
+
createBranch: function (cwd, branch, base) { return rpc(ctx, "createBranch", { cwd: cwd, branch: branch, base: base }); },
|
|
843
|
+
fetch: function (cwd) { return rpc(ctx, "fetch", { cwd: cwd }); },
|
|
844
|
+
pull: function (cwd) { return rpc(ctx, "pull", { cwd: cwd }); },
|
|
845
|
+
stage: function (cwd) { return rpc(ctx, "stage", { cwd: cwd }); },
|
|
846
|
+
diff: function (cwd, file, origFile) {
|
|
847
|
+
return rpc(ctx, "diff", { cwd: cwd, path: file, origPath: origFile });
|
|
848
|
+
},
|
|
849
|
+
commit: function (cwd, message) { return rpc(ctx, "commit", { cwd: cwd, message: message }); },
|
|
850
|
+
push: function (cwd) { return rpc(ctx, "push", { cwd: cwd }); },
|
|
851
|
+
log: function (cwd) { return rpc(ctx, "log", { cwd: cwd, count: 10 }); },
|
|
852
|
+
generateMessage: function (cwd, mode, provider, model) {
|
|
853
|
+
return rpc(ctx, "generateMessage", { cwd: cwd, mode: mode, provider: provider, model: model });
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* The Session this store follows. The session-scoped seat (the input
|
|
859
|
+
* dock) binds it; dsh >= 0.1.6 removed `current` from the sessions
|
|
860
|
+
* list snapshot, so the root-scoped panel can no longer find the
|
|
861
|
+
* current Session by itself and follows this field instead.
|
|
862
|
+
*/
|
|
863
|
+
var sessionId = null;
|
|
864
|
+
|
|
865
|
+
function idleState() {
|
|
866
|
+
return {
|
|
867
|
+
sessionId: sessionId,
|
|
868
|
+
cwd: null,
|
|
869
|
+
phase: "idle",
|
|
870
|
+
repo: false,
|
|
871
|
+
branch: null,
|
|
872
|
+
detached: false,
|
|
873
|
+
oid: null,
|
|
874
|
+
upstream: null,
|
|
875
|
+
ahead: 0,
|
|
876
|
+
behind: 0,
|
|
877
|
+
dirty: 0,
|
|
878
|
+
changes: [],
|
|
879
|
+
local: [],
|
|
880
|
+
remote: [],
|
|
881
|
+
commits: [],
|
|
882
|
+
error: null,
|
|
883
|
+
busy: null,
|
|
884
|
+
lastResult: null,
|
|
885
|
+
panelOpen: false
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
var state = idleState();
|
|
890
|
+
var listeners = new Set();
|
|
891
|
+
var seq = 0;
|
|
892
|
+
var inflight = null;
|
|
893
|
+
|
|
894
|
+
function emit(next) {
|
|
895
|
+
state = next;
|
|
896
|
+
for (var fn of Array.from(listeners)) {
|
|
897
|
+
try { fn(); } catch (error) { console.error("[dsh-git] store listener threw:", error); }
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function getSnapshot() { return state; }
|
|
902
|
+
|
|
903
|
+
function subscribe(fn) {
|
|
904
|
+
listeners.add(fn);
|
|
905
|
+
return function () { listeners.delete(fn); };
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function applyData(nextCwd, status, branches, log) {
|
|
909
|
+
var sv = status && status.ok === true ? status.value : null;
|
|
910
|
+
var bv = branches && branches.ok === true ? branches.value : null;
|
|
911
|
+
var lv = log && log.ok === true ? log.value : null;
|
|
912
|
+
// A reload is passive: it must not erase the last operation's
|
|
913
|
+
// output line, which `act()` sets *before* refreshing.
|
|
914
|
+
if (sv === null) {
|
|
915
|
+
return Object.assign(idleState(), {
|
|
916
|
+
cwd: nextCwd,
|
|
917
|
+
phase: "error",
|
|
918
|
+
error: status && status.error ? String(status.error.message || "git status failed") : "git status failed",
|
|
919
|
+
panelOpen: state.panelOpen,
|
|
920
|
+
lastResult: state.lastResult
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
return Object.assign(idleState(), {
|
|
924
|
+
cwd: nextCwd,
|
|
925
|
+
phase: "ready",
|
|
926
|
+
repo: sv.repo === true,
|
|
927
|
+
branch: sv.branch || null,
|
|
928
|
+
detached: sv.detached === true,
|
|
929
|
+
oid: sv.oid || null,
|
|
930
|
+
upstream: sv.upstream || null,
|
|
931
|
+
ahead: typeof sv.ahead === "number" ? sv.ahead : 0,
|
|
932
|
+
behind: typeof sv.behind === "number" ? sv.behind : 0,
|
|
933
|
+
dirty: typeof sv.dirty === "number" ? sv.dirty : 0,
|
|
934
|
+
changes: Array.isArray(sv.changes) ? sv.changes : [],
|
|
935
|
+
local: bv !== null && Array.isArray(bv.local) ? bv.local : [],
|
|
936
|
+
remote: bv !== null && Array.isArray(bv.remote) ? bv.remote : [],
|
|
937
|
+
commits: lv !== null && Array.isArray(lv.commits) ? lv.commits : [],
|
|
938
|
+
panelOpen: state.panelOpen,
|
|
939
|
+
lastResult: state.lastResult
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function doRefresh(cwd) {
|
|
944
|
+
var mySeq = ++seq;
|
|
945
|
+
emit(Object.assign({}, state, { sessionId: sessionId, cwd: cwd, phase: cwd ? "loading" : "idle", error: null }));
|
|
946
|
+
if (!cwd) return Promise.resolve();
|
|
947
|
+
return Promise.all([
|
|
948
|
+
verbs.status(cwd),
|
|
949
|
+
verbs.branches(cwd),
|
|
950
|
+
verbs.log(cwd)
|
|
951
|
+
]).then(function (results) {
|
|
952
|
+
if (mySeq !== seq) return;
|
|
953
|
+
emit(applyData(cwd, results[0], results[1], results[2]));
|
|
954
|
+
}).catch(function (error) {
|
|
955
|
+
if (mySeq !== seq) return;
|
|
956
|
+
emit(Object.assign(idleState(), {
|
|
957
|
+
cwd: cwd,
|
|
958
|
+
phase: "error",
|
|
959
|
+
error: error && error.message ? String(error.message) : String(error),
|
|
960
|
+
panelOpen: state.panelOpen,
|
|
961
|
+
lastResult: state.lastResult
|
|
962
|
+
}));
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function refresh(cwd) {
|
|
967
|
+
if (inflight !== null && inflight.cwd === cwd) return inflight.promise;
|
|
968
|
+
var promise = doRefresh(cwd);
|
|
969
|
+
inflight = { cwd: cwd, promise: promise };
|
|
970
|
+
promise.finally(function () {
|
|
971
|
+
if (inflight !== null && inflight.cwd === cwd) inflight = null;
|
|
972
|
+
}).catch(function () {});
|
|
973
|
+
return promise;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* Bind the store to one Session and its workspace directory. The
|
|
978
|
+
* session-scoped seat calls this with the identity and cwd the
|
|
979
|
+
* framework gave it (`sessionId` slot prop + the sessions list it
|
|
980
|
+
* reads through `useSessions`); everything else — the root-scoped
|
|
981
|
+
* panel included — then reads the shared store.
|
|
982
|
+
* @param id - the Session identity, or undefined/null for "none".
|
|
983
|
+
* @param cwd - that Session's workspace directory, when it has one.
|
|
984
|
+
* @returns the refresh promise, so tests can await the first load.
|
|
985
|
+
*/
|
|
986
|
+
function bindSession(id, cwd) {
|
|
987
|
+
var nextId = typeof id === "string" && id !== "" ? id : null;
|
|
988
|
+
var nextCwd = typeof cwd === "string" && cwd !== "" ? cwd : null;
|
|
989
|
+
if (nextId === sessionId && nextCwd === state.cwd) return undefined;
|
|
990
|
+
sessionId = nextId;
|
|
991
|
+
if (nextCwd === state.cwd) {
|
|
992
|
+
// Two Sessions can share one workspace: the cwd is unchanged,
|
|
993
|
+
// but the identity the panel resolves its model route from is
|
|
994
|
+
// not, so publish it without another load.
|
|
995
|
+
emit(Object.assign({}, state, { sessionId: sessionId }));
|
|
996
|
+
return undefined;
|
|
997
|
+
}
|
|
998
|
+
return refresh(nextCwd);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Run one action; on success force a fresh load; surfaces lastResult.
|
|
1003
|
+
* `describe` lets a caller own the wording of both outcomes (used by
|
|
1004
|
+
* generation, where the success value is the message itself and a
|
|
1005
|
+
* failure code maps to a localized string). Without it the result is
|
|
1006
|
+
* summarized by {@link summarizeResult}: a localized one-liner plus
|
|
1007
|
+
* git's own output as detail. `params` interpolates that summary (e.g.
|
|
1008
|
+
* `{ branch }`); the panel localizes it, since the store holds no `t`.
|
|
1009
|
+
* Resolves with the raw RPC result so the caller can also act on it.
|
|
1010
|
+
* @param name - the operation name (`push`, `commit`, …).
|
|
1011
|
+
* @param run - performs the call.
|
|
1012
|
+
* @param describe - optional wording override for both outcomes.
|
|
1013
|
+
* @param params - optional interpolation for the default summary.
|
|
1014
|
+
*/
|
|
1015
|
+
function act(name, run, describe, params) {
|
|
1016
|
+
emit(Object.assign({}, state, { busy: name, lastResult: null }));
|
|
1017
|
+
return run().then(function (res) {
|
|
1018
|
+
var result = describe ? describe(res) : summarizeResult(name, res, params);
|
|
1019
|
+
emit(Object.assign({}, state, { busy: null, lastResult: result }));
|
|
1020
|
+
if (res && res.ok === true) {
|
|
1021
|
+
var cwd = state.cwd;
|
|
1022
|
+
return refresh(cwd).then(function () { return res; });
|
|
1023
|
+
}
|
|
1024
|
+
return res;
|
|
1025
|
+
}).catch(function (error) {
|
|
1026
|
+
// A transport failure is worded exactly like a git failure: the
|
|
1027
|
+
// same summary/detail split, so the card never grows a second shape.
|
|
1028
|
+
var failure = { ok: false, error: { message: error && error.message ? String(error.message) : String(error) } };
|
|
1029
|
+
emit(Object.assign({}, state, { busy: null, lastResult: summarizeResult(name, failure, params) }));
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function setPanelOpen(open) {
|
|
1034
|
+
emit(Object.assign({}, state, { panelOpen: open === true }));
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
return {
|
|
1038
|
+
getSnapshot: getSnapshot,
|
|
1039
|
+
subscribe: subscribe,
|
|
1040
|
+
refresh: refresh,
|
|
1041
|
+
bindSession: bindSession,
|
|
1042
|
+
act: act,
|
|
1043
|
+
setPanelOpen: setPanelOpen,
|
|
1044
|
+
verbs: verbs
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/** Theme-matched colors for the native branch <select> dropdown list. */
|
|
1049
|
+
var OPTION_STYLE = {
|
|
1050
|
+
background: "var(--dsw-alias-bg-base, #1e1e1e)",
|
|
1051
|
+
color: "var(--dsw-alias-label-secondary, #c8ccd2)"
|
|
1052
|
+
};
|
|
1053
|
+
var OPTGROUP_STYLE = {
|
|
1054
|
+
background: "var(--dsw-alias-bg-base, #1e1e1e)",
|
|
1055
|
+
color: "var(--dsw-alias-label-tertiary, #9aa0a6)",
|
|
1056
|
+
fontWeight: 600
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
/** One branch option list; remote picks carry the DWIM-able short name. */
|
|
1060
|
+
function branchOptions(state, t) {
|
|
1061
|
+
var local = state.local.map(function (entry) {
|
|
1062
|
+
return h("option", { key: "l:" + entry.name, value: entry.name, style: OPTION_STYLE }, entry.name + (entry.current ? " ✓" : ""));
|
|
1063
|
+
});
|
|
1064
|
+
var remote = state.remote.map(function (entry) {
|
|
1065
|
+
return h("option", { key: "r:" + entry.name, value: entry.short, style: OPTION_STYLE }, entry.short);
|
|
1066
|
+
});
|
|
1067
|
+
var children = [];
|
|
1068
|
+
if (local.length > 0) children.push(h("optgroup", { key: "lg", label: t("group.local"), style: OPTGROUP_STYLE }, local));
|
|
1069
|
+
if (remote.length > 0) children.push(h("optgroup", { key: "rg", label: t("group.remote"), style: OPTGROUP_STYLE }, remote));
|
|
1070
|
+
return children;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Base-branch options for "new branch from…": local branch names plus
|
|
1075
|
+
* the FULL remote-tracking refs (e.g. `origin/feature/x`) so the host
|
|
1076
|
+
* can resolve them directly as the start point.
|
|
1077
|
+
*/
|
|
1078
|
+
function baseOptions(state, t) {
|
|
1079
|
+
var local = state.local.map(function (entry) {
|
|
1080
|
+
return h("option", { key: "l:" + entry.name, value: entry.name, style: OPTION_STYLE }, entry.name + (entry.current ? " ✓" : ""));
|
|
1081
|
+
});
|
|
1082
|
+
var remote = state.remote.map(function (entry) {
|
|
1083
|
+
return h("option", { key: "r:" + entry.name, value: entry.name, style: OPTION_STYLE }, entry.name);
|
|
1084
|
+
});
|
|
1085
|
+
var children = [];
|
|
1086
|
+
if (local.length > 0) children.push(h("optgroup", { key: "lg", label: t("group.local"), style: OPTGROUP_STYLE }, local));
|
|
1087
|
+
if (remote.length > 0) children.push(h("optgroup", { key: "rg", label: t("group.remote"), style: OPTGROUP_STYLE }, remote));
|
|
1088
|
+
return children;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/** The row kinds one parsed unified diff can produce. */
|
|
1092
|
+
var DIFF_ROW = {
|
|
1093
|
+
context: "context",
|
|
1094
|
+
add: "add",
|
|
1095
|
+
del: "del",
|
|
1096
|
+
hunk: "hunk",
|
|
1097
|
+
fileHeader: "fileHeader",
|
|
1098
|
+
meta: "meta",
|
|
1099
|
+
noNewline: "noNewline"
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
/** git's own bookkeeping lines, recognized only OUTSIDE a hunk. */
|
|
1103
|
+
var DIFF_FILE_HEADER = /^(diff --git |index |--- |\+\+\+ |new file mode|deleted file mode|old mode |new mode |similarity index|dissimilarity index|rename from |rename to |copy from |copy to |Binary files |GIT binary patch)/;
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Parse one unified diff into renderable rows.
|
|
1107
|
+
*
|
|
1108
|
+
* Line numbers are tracked from each hunk header, so every content row
|
|
1109
|
+
* carries the old and/or the new number it belongs to. `--- `/`+++ ` count
|
|
1110
|
+
* as file headers only outside a hunk: a deleted line whose own text starts
|
|
1111
|
+
* with `--` looks exactly like one, and misreading it would silently drop a
|
|
1112
|
+
* real change.
|
|
1113
|
+
* @param text - unified diff text (several files may be concatenated).
|
|
1114
|
+
* @returns `{ rows, additions, deletions, hunks }`.
|
|
1115
|
+
*/
|
|
1116
|
+
function parseUnifiedDiff(text) {
|
|
1117
|
+
var rows = [];
|
|
1118
|
+
var additions = 0;
|
|
1119
|
+
var deletions = 0;
|
|
1120
|
+
var hunks = 0;
|
|
1121
|
+
var oldLine = 0;
|
|
1122
|
+
var newLine = 0;
|
|
1123
|
+
var inHunk = false;
|
|
1124
|
+
var lines = String(text === undefined || text === null ? "" : text).split("\n");
|
|
1125
|
+
for (var i = 0; i < lines.length; i += 1) {
|
|
1126
|
+
var line = lines[i];
|
|
1127
|
+
// git ends a patch with a newline, so the split leaves one empty tail.
|
|
1128
|
+
if (i === lines.length - 1 && line === "") continue;
|
|
1129
|
+
if (line.indexOf("diff --git ") === 0) {
|
|
1130
|
+
inHunk = false;
|
|
1131
|
+
rows.push({ kind: DIFF_ROW.fileHeader, text: line, oldLine: null, newLine: null });
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
if (line.indexOf("@@") === 0) {
|
|
1135
|
+
var match = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
|
|
1136
|
+
if (match !== null) {
|
|
1137
|
+
oldLine = Number(match[1]);
|
|
1138
|
+
newLine = Number(match[2]);
|
|
1139
|
+
}
|
|
1140
|
+
hunks += 1;
|
|
1141
|
+
inHunk = true;
|
|
1142
|
+
rows.push({ kind: DIFF_ROW.hunk, text: line, oldLine: null, newLine: null });
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
if (!inHunk && DIFF_FILE_HEADER.test(line)) {
|
|
1146
|
+
rows.push({ kind: DIFF_ROW.fileHeader, text: line, oldLine: null, newLine: null });
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
if (line.charAt(0) === "\\") {
|
|
1150
|
+
rows.push({ kind: DIFF_ROW.noNewline, text: line, oldLine: null, newLine: null });
|
|
1151
|
+
continue;
|
|
1152
|
+
}
|
|
1153
|
+
var marker = line.charAt(0);
|
|
1154
|
+
if (marker === "+") {
|
|
1155
|
+
rows.push({ kind: DIFF_ROW.add, text: line.slice(1), oldLine: null, newLine: newLine });
|
|
1156
|
+
newLine += 1;
|
|
1157
|
+
additions += 1;
|
|
1158
|
+
} else if (marker === "-") {
|
|
1159
|
+
rows.push({ kind: DIFF_ROW.del, text: line.slice(1), oldLine: oldLine, newLine: null });
|
|
1160
|
+
oldLine += 1;
|
|
1161
|
+
deletions += 1;
|
|
1162
|
+
} else if (marker === " ") {
|
|
1163
|
+
rows.push({ kind: DIFF_ROW.context, text: line.slice(1), oldLine: oldLine, newLine: newLine });
|
|
1164
|
+
oldLine += 1;
|
|
1165
|
+
newLine += 1;
|
|
1166
|
+
} else {
|
|
1167
|
+
rows.push({ kind: DIFF_ROW.meta, text: line, oldLine: null, newLine: null });
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
return { rows: rows, additions: additions, deletions: deletions, hunks: hunks };
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Turn one side of the host's `diff` value into render state: the parsed
|
|
1175
|
+
* rows plus the flags that decide which notices the pane shows. Parsing
|
|
1176
|
+
* happens once per read, not per render, because a diff may be large.
|
|
1177
|
+
* @param side - `{ diff, binary, truncated }` as the host sent it.
|
|
1178
|
+
*/
|
|
1179
|
+
function parseDiffSide(side) {
|
|
1180
|
+
var data = side !== null && typeof side === "object" ? side : {};
|
|
1181
|
+
var text = typeof data.diff === "string" ? data.diff : "";
|
|
1182
|
+
var parsed = parseUnifiedDiff(text);
|
|
1183
|
+
return {
|
|
1184
|
+
empty: text.trim() === "",
|
|
1185
|
+
binary: data.binary === true,
|
|
1186
|
+
truncated: data.truncated === true,
|
|
1187
|
+
text: text,
|
|
1188
|
+
rows: parsed.rows,
|
|
1189
|
+
additions: parsed.additions,
|
|
1190
|
+
deletions: parsed.deletions,
|
|
1191
|
+
hunks: parsed.hunks
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/** Rows rendered at once before the pane offers "show all". */
|
|
1196
|
+
var DIFF_RENDER_MAX = 1500;
|
|
1197
|
+
/** Width of the diff pane when a file is opened, and its readable floor. */
|
|
1198
|
+
var DIFF_DEFAULT_WIDTH = 560;
|
|
1199
|
+
var DIFF_MIN_WIDTH = 240;
|
|
1200
|
+
/** Width of the workbench pane, the panel's own border, and its viewport margin. */
|
|
1201
|
+
var DIFF_LEFT_PANE_WIDTH = 300;
|
|
1202
|
+
var PANEL_BORDER_WIDTH = 2;
|
|
1203
|
+
var PANEL_MARGIN = 24;
|
|
1204
|
+
/** Grab width of the panel's right-edge resize handle. */
|
|
1205
|
+
var PANEL_RESIZE_GRIP = 6;
|
|
1206
|
+
|
|
1207
|
+
/** The last path segment, for chip-sized text. */
|
|
1208
|
+
function baseName(file) {
|
|
1209
|
+
var text = String(file === undefined || file === null ? "" : file);
|
|
1210
|
+
var slash = Math.max(text.lastIndexOf("/"), text.lastIndexOf("\\"));
|
|
1211
|
+
return slash === -1 ? text : text.slice(slash + 1);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* One row of the diff body: the two line-number gutters, then the text.
|
|
1216
|
+
* Colours come from the theme's state tokens (with rgba fallbacks), so the
|
|
1217
|
+
* added/removed rows stay readable in both light and dark themes.
|
|
1218
|
+
* @param row - a row from `parseUnifiedDiff`.
|
|
1219
|
+
* @param key - React key.
|
|
1220
|
+
* @param wrap - whether long lines wrap instead of scrolling.
|
|
1221
|
+
*/
|
|
1222
|
+
function diffRowView(row, key, wrap) {
|
|
1223
|
+
var style = Object.assign({}, S.diffRow);
|
|
1224
|
+
// Unwrapped rows take their natural width so the horizontal scroll runs
|
|
1225
|
+
// the patch's full length; a wrapped row must NOT keep `max-content`,
|
|
1226
|
+
// or the row stays as wide as its longest line.
|
|
1227
|
+
if (wrap === true) style = Object.assign(style, { minWidth: 0 });
|
|
1228
|
+
if (row.kind === DIFF_ROW.add) style = Object.assign(style, S.diffAddRow);
|
|
1229
|
+
else if (row.kind === DIFF_ROW.del) style = Object.assign(style, S.diffDelRow);
|
|
1230
|
+
else if (row.kind === DIFF_ROW.hunk) style = Object.assign(style, S.diffHunkRow);
|
|
1231
|
+
else if (row.kind !== DIFF_ROW.context) style = Object.assign(style, S.diffMetaRow);
|
|
1232
|
+
var code = row.kind === DIFF_ROW.add ? "+" : row.kind === DIFF_ROW.del ? "-" : row.kind === DIFF_ROW.context ? " " : "";
|
|
1233
|
+
var text = code === "" ? row.text : code + row.text;
|
|
1234
|
+
// The wrap belongs on the TEXT span: `S.diffText` carries its own
|
|
1235
|
+
// `white-space: pre`, which would win over the row's value and leave a
|
|
1236
|
+
// wrapped row overflowing anyway.
|
|
1237
|
+
var textStyle = wrap === true
|
|
1238
|
+
? Object.assign({}, S.diffText, { whiteSpace: "pre-wrap", wordBreak: "break-word" })
|
|
1239
|
+
: S.diffText;
|
|
1240
|
+
return h("div", {
|
|
1241
|
+
key: key,
|
|
1242
|
+
style: style,
|
|
1243
|
+
"data-dsh-git": "diff-row",
|
|
1244
|
+
"data-kind": row.kind
|
|
1245
|
+
}, [
|
|
1246
|
+
h("span", { key: "old", style: S.diffGutter }, row.oldLine === null || row.oldLine === undefined ? "" : String(row.oldLine)),
|
|
1247
|
+
h("span", { key: "new", style: S.diffGutter }, row.newLine === null || row.newLine === undefined ? "" : String(row.newLine)),
|
|
1248
|
+
h("span", { key: "text", style: textStyle }, text)
|
|
1249
|
+
]);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* The right-hand pane of the workbench: one changed file's unified diff.
|
|
1254
|
+
*
|
|
1255
|
+
* The pane owns its own reads, because its inputs are the selected path
|
|
1256
|
+
* plus a data version the panel computes from the store (staging,
|
|
1257
|
+
* committing, switching a branch, or refreshing all change it, so the open
|
|
1258
|
+
* diff follows the repository without the user clicking anything). The side
|
|
1259
|
+
* of the index shown follows the data by default — unstaged when it has
|
|
1260
|
+
* anything, staged otherwise — and an explicit pick sticks until the file
|
|
1261
|
+
* changes (the panel remounts this pane per file with a React key).
|
|
1262
|
+
*
|
|
1263
|
+
* @param props - `{ store, t, cwd, change, dataVersion, onClose }`.
|
|
1264
|
+
*/
|
|
1265
|
+
function GitDiffPane(props) {
|
|
1266
|
+
var store = props.store;
|
|
1267
|
+
var t = props.t;
|
|
1268
|
+
var cwd = props.cwd;
|
|
1269
|
+
var change = props.change;
|
|
1270
|
+
var dataVersion = props.dataVersion;
|
|
1271
|
+
var onClose = props.onClose;
|
|
1272
|
+
|
|
1273
|
+
var readRef = React.useState({ phase: "loading", error: null, value: null, worktree: null, index: null });
|
|
1274
|
+
var read = readRef[0];
|
|
1275
|
+
var setRead = readRef[1];
|
|
1276
|
+
// null = follow the data (unstaged if it has anything, else staged).
|
|
1277
|
+
var scopeRef = React.useState(null);
|
|
1278
|
+
var scope = scopeRef[0];
|
|
1279
|
+
var setScope = scopeRef[1];
|
|
1280
|
+
var wrapRef = React.useState(false);
|
|
1281
|
+
var wrap = wrapRef[0];
|
|
1282
|
+
var setWrap = wrapRef[1];
|
|
1283
|
+
var copiedRef = React.useState(false);
|
|
1284
|
+
var copied = copiedRef[0];
|
|
1285
|
+
var setCopied = copiedRef[1];
|
|
1286
|
+
var showAllRef = React.useState(false);
|
|
1287
|
+
var showAll = showAllRef[0];
|
|
1288
|
+
var setShowAll = showAllRef[1];
|
|
1289
|
+
var nonceRef = React.useState(0);
|
|
1290
|
+
var nonce = nonceRef[0];
|
|
1291
|
+
var setNonce = nonceRef[1];
|
|
1292
|
+
var seqRef = React.useRef(0);
|
|
1293
|
+
var copiedTimer = React.useRef(null);
|
|
1294
|
+
|
|
1295
|
+
var file = change ? change.file : null;
|
|
1296
|
+
var origFile = change ? change.origFile : null;
|
|
1297
|
+
|
|
1298
|
+
// One effect owns the read: it bumps the sequence, shows the loading
|
|
1299
|
+
// state, and only the newest answer is allowed to land (a stale reply
|
|
1300
|
+
// from a previously selected file must never be rendered).
|
|
1301
|
+
React.useEffect(function () {
|
|
1302
|
+
if (!cwd || !file) return;
|
|
1303
|
+
var mySeq = seqRef.current + 1;
|
|
1304
|
+
seqRef.current = mySeq;
|
|
1305
|
+
setRead({ phase: "loading", error: null, value: null, worktree: null, index: null });
|
|
1306
|
+
store.verbs.diff(cwd, file, origFile).then(function (res) {
|
|
1307
|
+
if (seqRef.current !== mySeq) return;
|
|
1308
|
+
if (!res || res.ok !== true) {
|
|
1309
|
+
var message = res && res.error && res.error.message ? String(res.error.message) : t("diff.failed");
|
|
1310
|
+
setRead({ phase: "error", error: message, value: null, worktree: null, index: null });
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
var value = res.value !== null && typeof res.value === "object" ? res.value : {};
|
|
1314
|
+
if (value.repo === false) {
|
|
1315
|
+
setRead({ phase: "error", error: t("notRepo"), value: null, worktree: null, index: null });
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
setRead({
|
|
1319
|
+
phase: "ready",
|
|
1320
|
+
error: null,
|
|
1321
|
+
value: value,
|
|
1322
|
+
worktree: parseDiffSide(value.worktree),
|
|
1323
|
+
index: parseDiffSide(value.index)
|
|
1324
|
+
});
|
|
1325
|
+
}).catch(function (error) {
|
|
1326
|
+
if (seqRef.current !== mySeq) return;
|
|
1327
|
+
setRead({
|
|
1328
|
+
phase: "error",
|
|
1329
|
+
error: error && error.message ? String(error.message) : t("diff.failed"),
|
|
1330
|
+
value: null,
|
|
1331
|
+
worktree: null,
|
|
1332
|
+
index: null
|
|
1333
|
+
});
|
|
1334
|
+
});
|
|
1335
|
+
}, [cwd, file, origFile, dataVersion, nonce, store]);
|
|
1336
|
+
|
|
1337
|
+
React.useEffect(function () {
|
|
1338
|
+
return function () {
|
|
1339
|
+
if (copiedTimer.current !== null) clearTimeout(copiedTimer.current);
|
|
1340
|
+
};
|
|
1341
|
+
}, []);
|
|
1342
|
+
|
|
1343
|
+
if (!change || !cwd) return null;
|
|
1344
|
+
|
|
1345
|
+
var worktree = read.worktree;
|
|
1346
|
+
var index = read.index;
|
|
1347
|
+
var autoScope = worktree !== null && worktree.empty === false ? "worktree" : "index";
|
|
1348
|
+
var side = scope === null ? autoScope : scope;
|
|
1349
|
+
var active = side === "index" ? index : worktree;
|
|
1350
|
+
var otherLabel = side === "index" ? t("diff.scope.worktree") : t("diff.scope.index");
|
|
1351
|
+
var pathText = change.path ? String(change.path) : String(change.file);
|
|
1352
|
+
|
|
1353
|
+
function pickScope(kind) {
|
|
1354
|
+
setScope(kind);
|
|
1355
|
+
setShowAll(false);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* One side chip. A side with nothing in it stays visible — the pair has
|
|
1360
|
+
* to read as a switch — but cannot be picked while it is the other chip.
|
|
1361
|
+
*/
|
|
1362
|
+
function scopeChip(kind) {
|
|
1363
|
+
var target = kind === "index" ? index : worktree;
|
|
1364
|
+
var isEmpty = target === null || target.empty === true;
|
|
1365
|
+
var isActive = side === kind;
|
|
1366
|
+
if (isEmpty && !isActive) {
|
|
1367
|
+
return h("button", {
|
|
1368
|
+
key: "scope-" + kind,
|
|
1369
|
+
type: "button",
|
|
1370
|
+
"data-dsh-git": "diff-scope-" + kind,
|
|
1371
|
+
"aria-pressed": false,
|
|
1372
|
+
style: Object.assign({}, S.scopeChip, { opacity: 0.45, cursor: "default" }),
|
|
1373
|
+
disabled: true,
|
|
1374
|
+
title: t("diff.empty")
|
|
1375
|
+
}, t("diff.scope." + kind));
|
|
1376
|
+
}
|
|
1377
|
+
return h("button", {
|
|
1378
|
+
key: "scope-" + kind,
|
|
1379
|
+
type: "button",
|
|
1380
|
+
"data-dsh-git": "diff-scope-" + kind,
|
|
1381
|
+
style: isActive ? S.scopeChipActive : S.scopeChip,
|
|
1382
|
+
title: t("diff.scope.title"),
|
|
1383
|
+
"aria-pressed": isActive,
|
|
1384
|
+
onClick: function () { pickScope(kind); }
|
|
1385
|
+
}, t("diff.scope." + kind));
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function doCopy() {
|
|
1389
|
+
var text = active !== null && typeof active.text === "string" ? active.text : "";
|
|
1390
|
+
var clipboard = typeof navigator !== "undefined" ? navigator.clipboard : undefined;
|
|
1391
|
+
if (text === "" || !clipboard || typeof clipboard.writeText !== "function") return;
|
|
1392
|
+
clipboard.writeText(text).then(function () {
|
|
1393
|
+
setCopied(true);
|
|
1394
|
+
if (copiedTimer.current !== null) clearTimeout(copiedTimer.current);
|
|
1395
|
+
copiedTimer.current = setTimeout(function () { setCopied(false); }, 1500);
|
|
1396
|
+
}).catch(function () {});
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
var headerChildren = [
|
|
1400
|
+
h("span", { key: "path", style: S.diffPath, title: String(change.file) }, shortText(pathText, 90)),
|
|
1401
|
+
h("span", { key: "status", style: S.statusTag }, t("status." + change.status))
|
|
1402
|
+
];
|
|
1403
|
+
if (read.phase === "ready" && active !== null && active.empty === false) {
|
|
1404
|
+
headerChildren.push(h("span", { key: "stat", style: S.meta }, "+" + active.additions + " −" + active.deletions));
|
|
1405
|
+
}
|
|
1406
|
+
headerChildren.push(h("button", {
|
|
1407
|
+
key: "close",
|
|
1408
|
+
type: "button",
|
|
1409
|
+
style: S.button,
|
|
1410
|
+
title: t("diff.close"),
|
|
1411
|
+
"aria-label": t("diff.close"),
|
|
1412
|
+
"data-dsh-git": "diff-close",
|
|
1413
|
+
onClick: onClose
|
|
1414
|
+
}, "×"));
|
|
1415
|
+
headerChildren.push(h("div", {
|
|
1416
|
+
key: "tools",
|
|
1417
|
+
style: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap", flexBasis: "100%", marginTop: 2 }
|
|
1418
|
+
}, [
|
|
1419
|
+
scopeChip("worktree"),
|
|
1420
|
+
scopeChip("index"),
|
|
1421
|
+
h("button", {
|
|
1422
|
+
key: "wrap",
|
|
1423
|
+
type: "button",
|
|
1424
|
+
"data-dsh-git": "diff-wrap",
|
|
1425
|
+
style: wrap === true ? S.scopeChipActive : S.scopeChip,
|
|
1426
|
+
title: t("diff.wrap"),
|
|
1427
|
+
"aria-pressed": wrap === true,
|
|
1428
|
+
onClick: function () { setWrap(!wrap); }
|
|
1429
|
+
}, t("diff.wrap")),
|
|
1430
|
+
h("button", {
|
|
1431
|
+
key: "copy",
|
|
1432
|
+
type: "button",
|
|
1433
|
+
"data-dsh-git": "diff-copy",
|
|
1434
|
+
style: S.scopeChip,
|
|
1435
|
+
title: t("diff.copy"),
|
|
1436
|
+
onClick: doCopy
|
|
1437
|
+
}, copied === true ? t("diff.copied") : t("diff.copy")),
|
|
1438
|
+
h("button", {
|
|
1439
|
+
key: "reload",
|
|
1440
|
+
type: "button",
|
|
1441
|
+
"data-dsh-git": "diff-reload",
|
|
1442
|
+
style: S.scopeChip,
|
|
1443
|
+
title: t("diff.refresh"),
|
|
1444
|
+
"aria-label": t("diff.refresh"),
|
|
1445
|
+
onClick: function () { setNonce(nonce + 1); }
|
|
1446
|
+
}, "↻")
|
|
1447
|
+
]));
|
|
1448
|
+
|
|
1449
|
+
var bodyChildren = [];
|
|
1450
|
+
if (read.phase === "loading") {
|
|
1451
|
+
bodyChildren.push(h("div", { key: "loading", style: S.diffNotice }, t("diff.loading")));
|
|
1452
|
+
} else if (read.phase === "error") {
|
|
1453
|
+
bodyChildren.push(h("div", { key: "error", style: Object.assign({}, S.diffNotice, S.outputError) }, shortText(read.error, 240)));
|
|
1454
|
+
} else if (active !== null) {
|
|
1455
|
+
var value = read.value !== null && typeof read.value === "object" ? read.value : {};
|
|
1456
|
+
if (value.untracked === true) {
|
|
1457
|
+
bodyChildren.push(h("div", { key: "untracked", style: S.diffNotice, "data-dsh-git": "diff-untracked" }, t("diff.untracked")));
|
|
1458
|
+
}
|
|
1459
|
+
if (typeof value.skipped === "number" && value.skipped > 0) {
|
|
1460
|
+
bodyChildren.push(h("div", { key: "skipped", style: S.diffNotice }, t("diff.skipped", { count: value.skipped })));
|
|
1461
|
+
}
|
|
1462
|
+
if (active.truncated === true) {
|
|
1463
|
+
bodyChildren.push(h("div", { key: "truncated", style: S.diffNotice }, t("diff.truncated")));
|
|
1464
|
+
}
|
|
1465
|
+
if (active.binary === true) {
|
|
1466
|
+
bodyChildren.push(h("div", { key: "binary", style: S.diffNotice, "data-dsh-git": "diff-binary" }, t("diff.binary")));
|
|
1467
|
+
} else if (active.empty === true) {
|
|
1468
|
+
bodyChildren.push(h("div", {
|
|
1469
|
+
key: "empty",
|
|
1470
|
+
style: S.diffNotice,
|
|
1471
|
+
"data-dsh-git": "diff-empty"
|
|
1472
|
+
}, t("diff.empty") + " · " + t("diff.emptyHint", { other: otherLabel })));
|
|
1473
|
+
} else {
|
|
1474
|
+
var clipped = active.rows.length > DIFF_RENDER_MAX && showAll !== true;
|
|
1475
|
+
var visible = clipped ? active.rows.slice(0, DIFF_RENDER_MAX) : active.rows;
|
|
1476
|
+
for (var i = 0; i < visible.length; i += 1) {
|
|
1477
|
+
bodyChildren.push(diffRowView(visible[i], "r" + i, wrap === true));
|
|
1478
|
+
}
|
|
1479
|
+
if (clipped) {
|
|
1480
|
+
bodyChildren.push(h("button", {
|
|
1481
|
+
key: "all",
|
|
1482
|
+
type: "button",
|
|
1483
|
+
style: Object.assign({}, S.button, { margin: "8px 10px" }),
|
|
1484
|
+
onClick: function () { setShowAll(true); }
|
|
1485
|
+
}, t("diff.showAll", { count: active.rows.length })));
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
return h("div", { style: S.diffPane, "data-dsh-git": "diff" }, [
|
|
1491
|
+
h("div", { key: "head", style: S.diffHeader, "data-dsh-git": "diff-header" }, headerChildren),
|
|
1492
|
+
h("div", { key: "scroll", style: S.diffScroll, "data-dsh-git": "diff-body" }, bodyChildren)
|
|
1493
|
+
]);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
/**
|
|
1497
|
+
* Floating workbench entry (shell.overlay). Renders nothing while
|
|
1498
|
+
* collapsed — the persistent status lives in the input.left pill, and
|
|
1499
|
+
* this panel appears only when the user expands it from that pill.
|
|
1500
|
+
* Follows the current session cwd.
|
|
1501
|
+
*/
|
|
1502
|
+
function GitFloatingPanel(props) {
|
|
1503
|
+
var store = props.store;
|
|
1504
|
+
var t = props.t || function (key, params) {
|
|
1505
|
+
return key + (params ? " " + JSON.stringify(params) : "");
|
|
1506
|
+
};
|
|
1507
|
+
var useSessions = props.useSessions;
|
|
1508
|
+
// useSessions is a selector hook (useSyncExternalStoreWithSelector):
|
|
1509
|
+
// the selector argument is REQUIRED — a bare useSessions() crashes
|
|
1510
|
+
// with "selector is not a function".
|
|
1511
|
+
var sessions = useSessions(function (s) { return s; });
|
|
1512
|
+
|
|
1513
|
+
// NOTE: the shell's seed react-dom is an experimental 18.3.1-next
|
|
1514
|
+
// build where React.useSyncExternalStore misbehaves (returns
|
|
1515
|
+
// undefined / throws), so subscribe through the classic
|
|
1516
|
+
// useState + useEffect pattern instead — safe on any React.
|
|
1517
|
+
var stateRef = React.useState(function () { return store.getSnapshot(); });
|
|
1518
|
+
var state = stateRef[0];
|
|
1519
|
+
var setState = stateRef[1];
|
|
1520
|
+
React.useEffect(function () {
|
|
1521
|
+
var unsubscribe = store.subscribe(function () {
|
|
1522
|
+
setState(store.getSnapshot());
|
|
1523
|
+
});
|
|
1524
|
+
return unsubscribe;
|
|
1525
|
+
}, [store]);
|
|
1526
|
+
|
|
1527
|
+
// The session-scoped pill owns the session → cwd binding; this
|
|
1528
|
+
// root-scoped seat follows the shared store. dsh >= 0.1.6 no longer
|
|
1529
|
+
// carries `current` in the sessions list snapshot — it now selects
|
|
1530
|
+
// the current Session through the renderer's scope adapter, which a
|
|
1531
|
+
// root-scoped entry cannot read — so `state.sessionId` is the only
|
|
1532
|
+
// identity reachable from here.
|
|
1533
|
+
var cwd = state.cwd;
|
|
1534
|
+
var sessionId = state.sessionId;
|
|
1535
|
+
var currentSummary = sessionId !== null && sessions && sessions.byId ? sessions.byId[sessionId] : undefined;
|
|
1536
|
+
|
|
1537
|
+
// The session's own model route, when one is recorded: the durable
|
|
1538
|
+
// `modelSelection` projection, pending pick first, last used second.
|
|
1539
|
+
// Generation falls back to the host's first registered route.
|
|
1540
|
+
var sessionRoute = (function () {
|
|
1541
|
+
var selection = currentSummary && currentSummary.projectionValues ? currentSummary.projectionValues.modelSelection : undefined;
|
|
1542
|
+
if (!selection) return null;
|
|
1543
|
+
var effective = selection.next || selection.lastUsed;
|
|
1544
|
+
if (!effective || !effective.provider || !effective.model) return null;
|
|
1545
|
+
return { provider: effective.provider, model: effective.model };
|
|
1546
|
+
})();
|
|
1547
|
+
|
|
1548
|
+
var messageRef = React.useState("");
|
|
1549
|
+
var message = messageRef[0];
|
|
1550
|
+
var setMessage = messageRef[1];
|
|
1551
|
+
// Draft basis. `staged` is the default because it is the only basis
|
|
1552
|
+
// whose content is what the commit below will actually record.
|
|
1553
|
+
var generateModeRef = React.useState("staged");
|
|
1554
|
+
var generateMode = generateModeRef[0];
|
|
1555
|
+
var setGenerateMode = generateModeRef[1];
|
|
1556
|
+
var changesOpenRef = React.useState(false);
|
|
1557
|
+
var changesOpen = changesOpenRef[0];
|
|
1558
|
+
var setChangesOpen = changesOpenRef[1];
|
|
1559
|
+
var logOpenRef = React.useState(false);
|
|
1560
|
+
var logOpen = logOpenRef[0];
|
|
1561
|
+
var setLogOpen = logOpenRef[1];
|
|
1562
|
+
// Whether the last operation's git output is expanded. A new result
|
|
1563
|
+
// always starts collapsed: the summary line is the notification, the
|
|
1564
|
+
// raw output is opt-in.
|
|
1565
|
+
var outputOpenRef = React.useState(false);
|
|
1566
|
+
var outputOpen = outputOpenRef[0];
|
|
1567
|
+
var setOutputOpen = outputOpenRef[1];
|
|
1568
|
+
React.useEffect(function () {
|
|
1569
|
+
setOutputOpen(false);
|
|
1570
|
+
}, [state.lastResult]);
|
|
1571
|
+
|
|
1572
|
+
// Branch the user picked while the tree is dirty: the pre-check
|
|
1573
|
+
// shows a warning instead of switching immediately; the switch only
|
|
1574
|
+
// runs via the "switch anyway" button (or once the tree is clean).
|
|
1575
|
+
var pendingRef = React.useState(null);
|
|
1576
|
+
var pendingBranch = pendingRef[0];
|
|
1577
|
+
var setPendingBranch = pendingRef[1];
|
|
1578
|
+
|
|
1579
|
+
// "New branch from…" form state: open flag, the new branch name
|
|
1580
|
+
// being typed, and the base branch explicitly picked by the user
|
|
1581
|
+
// (null = default to the current branch on first open).
|
|
1582
|
+
var newBranchOpenRef = React.useState(false);
|
|
1583
|
+
var newBranchOpen = newBranchOpenRef[0];
|
|
1584
|
+
var setNewBranchOpen = newBranchOpenRef[1];
|
|
1585
|
+
var newBranchNameRef = React.useState("");
|
|
1586
|
+
var newBranchName = newBranchNameRef[0];
|
|
1587
|
+
var setNewBranchName = newBranchNameRef[1];
|
|
1588
|
+
var newBranchBaseRef = React.useState(null);
|
|
1589
|
+
var newBranchBase = newBranchBaseRef[0];
|
|
1590
|
+
var setNewBranchBase = newBranchBaseRef[1];
|
|
1591
|
+
|
|
1592
|
+
// ── draggable panel (header is the drag handle) ─────────────
|
|
1593
|
+
// Default: centered above the composer (S.panel). After the first
|
|
1594
|
+
// drag, switch to explicit left/top so the panel stays where the
|
|
1595
|
+
// user dropped it. Double-click the header to snap back.
|
|
1596
|
+
var panelRef = React.useRef(null);
|
|
1597
|
+
var dragPosRef = React.useState(null);
|
|
1598
|
+
var dragPos = dragPosRef[0];
|
|
1599
|
+
var setDragPos = dragPosRef[1];
|
|
1600
|
+
|
|
1601
|
+
function clampDragPos(left, top, width, height) {
|
|
1602
|
+
var vw = (typeof window !== "undefined" && window.innerWidth) || 1920;
|
|
1603
|
+
var vh = (typeof window !== "undefined" && window.innerHeight) || 1080;
|
|
1604
|
+
var w = width || 400;
|
|
1605
|
+
var hh = height || 200;
|
|
1606
|
+
var maxLeft = Math.max(0, vw - Math.min(w, 120));
|
|
1607
|
+
var maxTop = Math.max(0, vh - 40);
|
|
1608
|
+
return {
|
|
1609
|
+
left: Math.min(Math.max(0, left), maxLeft),
|
|
1610
|
+
top: Math.min(Math.max(0, top), maxTop)
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function onPanelHeaderMouseDown(event) {
|
|
1615
|
+
if (event.button !== undefined && event.button !== 0) return;
|
|
1616
|
+
var target = event.target;
|
|
1617
|
+
if (target && target.closest) {
|
|
1618
|
+
try { if (target.closest("button,select,input,textarea,a")) return; } catch (ignore) {}
|
|
1619
|
+
} else if (target && /^(BUTTON|SELECT|INPUT|TEXTAREA|A)$/.test(target.tagName || "")) {
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
var panelEl = panelRef.current;
|
|
1623
|
+
if (!panelEl || !panelEl.getBoundingClientRect) return;
|
|
1624
|
+
var doc = (typeof document !== "undefined") ? document : null;
|
|
1625
|
+
if (!doc || !doc.addEventListener) return;
|
|
1626
|
+
var rect = panelEl.getBoundingClientRect();
|
|
1627
|
+
var startX = event.clientX;
|
|
1628
|
+
var startY = event.clientY;
|
|
1629
|
+
var baseLeft = rect.left;
|
|
1630
|
+
var baseTop = rect.top;
|
|
1631
|
+
if (event.preventDefault) event.preventDefault();
|
|
1632
|
+
function onMove(e) {
|
|
1633
|
+
var c = clampDragPos(
|
|
1634
|
+
baseLeft + (e.clientX - startX),
|
|
1635
|
+
baseTop + (e.clientY - startY),
|
|
1636
|
+
rect.width, rect.height
|
|
1637
|
+
);
|
|
1638
|
+
setDragPos(c);
|
|
1639
|
+
}
|
|
1640
|
+
function onUp() {
|
|
1641
|
+
doc.removeEventListener("mousemove", onMove);
|
|
1642
|
+
doc.removeEventListener("mouseup", onUp);
|
|
1643
|
+
try { doc.body.style.userSelect = ""; doc.body.style.cursor = ""; } catch (ignore2) {}
|
|
1644
|
+
}
|
|
1645
|
+
doc.addEventListener("mousemove", onMove);
|
|
1646
|
+
doc.addEventListener("mouseup", onUp);
|
|
1647
|
+
try { doc.body.style.userSelect = "none"; doc.body.style.cursor = "grabbing"; } catch (ignore3) {}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
function onPanelHeaderTouchStart(event) {
|
|
1651
|
+
var touches = event.touches;
|
|
1652
|
+
if (!touches || touches.length !== 1) return;
|
|
1653
|
+
var target = event.target;
|
|
1654
|
+
if (target && target.closest) {
|
|
1655
|
+
try { if (target.closest("button,select,input,textarea,a")) return; } catch (ignore) {}
|
|
1656
|
+
}
|
|
1657
|
+
var panelEl = panelRef.current;
|
|
1658
|
+
if (!panelEl || !panelEl.getBoundingClientRect) return;
|
|
1659
|
+
var doc = (typeof document !== "undefined") ? document : null;
|
|
1660
|
+
if (!doc || !doc.addEventListener) return;
|
|
1661
|
+
var rect = panelEl.getBoundingClientRect();
|
|
1662
|
+
var touch = touches[0];
|
|
1663
|
+
var startX = touch.clientX;
|
|
1664
|
+
var startY = touch.clientY;
|
|
1665
|
+
var baseLeft = rect.left;
|
|
1666
|
+
var baseTop = rect.top;
|
|
1667
|
+
function onMove(e) {
|
|
1668
|
+
var t = (e.touches && e.touches[0]) || (e.changedTouches && e.changedTouches[0]);
|
|
1669
|
+
if (!t) return;
|
|
1670
|
+
if (e.preventDefault) e.preventDefault();
|
|
1671
|
+
var c = clampDragPos(
|
|
1672
|
+
baseLeft + (t.clientX - startX),
|
|
1673
|
+
baseTop + (t.clientY - startY),
|
|
1674
|
+
rect.width, rect.height
|
|
1675
|
+
);
|
|
1676
|
+
setDragPos(c);
|
|
1677
|
+
}
|
|
1678
|
+
function onEnd() {
|
|
1679
|
+
doc.removeEventListener("touchmove", onMove);
|
|
1680
|
+
doc.removeEventListener("touchend", onEnd);
|
|
1681
|
+
doc.removeEventListener("touchcancel", onEnd);
|
|
1682
|
+
}
|
|
1683
|
+
doc.addEventListener("touchmove", onMove, { passive: false });
|
|
1684
|
+
doc.addEventListener("touchend", onEnd);
|
|
1685
|
+
doc.addEventListener("touchcancel", onEnd);
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
// ── diff viewer state ───────────────────────────────────────────
|
|
1689
|
+
// `selected` is the change the right-hand pane is showing (null = the
|
|
1690
|
+
// panel is the plain single-column workbench), `diffWidth` is that
|
|
1691
|
+
// pane's width, and the PANEL'S RIGHT EDGE is its handle.
|
|
1692
|
+
var selectedRef = React.useState(null);
|
|
1693
|
+
var selected = selectedRef[0];
|
|
1694
|
+
var setSelected = selectedRef[1];
|
|
1695
|
+
var diffWidthRef = React.useState(DIFF_DEFAULT_WIDTH);
|
|
1696
|
+
var diffWidth = diffWidthRef[0];
|
|
1697
|
+
var setDiffWidth = diffWidthRef[1];
|
|
1698
|
+
var resizeHoverRef = React.useState(false);
|
|
1699
|
+
var resizeHover = resizeHoverRef[0];
|
|
1700
|
+
var setResizeHover = resizeHoverRef[1];
|
|
1701
|
+
|
|
1702
|
+
/**
|
|
1703
|
+
* Widen the pane only as far as the viewport allows.
|
|
1704
|
+
* @param value - the wanted diff width.
|
|
1705
|
+
* @param anchor - the left edge to grow from; defaults to the panel's
|
|
1706
|
+
* current position, or to a symmetric margin while it is centred.
|
|
1707
|
+
*/
|
|
1708
|
+
function clampDiffWidth(value, anchor) {
|
|
1709
|
+
var vw = (typeof window !== "undefined" && window.innerWidth) || 1920;
|
|
1710
|
+
var pos = anchor !== undefined ? anchor : dragPos;
|
|
1711
|
+
var room = pos === null || pos === undefined ? vw - PANEL_MARGIN : vw - 12 - pos.left;
|
|
1712
|
+
var available = room - DIFF_LEFT_PANE_WIDTH - PANEL_BORDER_WIDTH;
|
|
1713
|
+
return Math.min(Math.max(DIFF_MIN_WIDTH, Math.round(value)), Math.max(DIFF_MIN_WIDTH, available));
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
/**
|
|
1717
|
+
* Start a width drag on the panel's right edge.
|
|
1718
|
+
*
|
|
1719
|
+
* The panel is anchored to its CURRENT left edge first: while it is
|
|
1720
|
+
* centred, widening it moves the right edge by half the growth, so a
|
|
1721
|
+
* centred panel would lag behind the pointer by 2×. Anchoring makes the
|
|
1722
|
+
* edge follow the mouse exactly, and leaves the panel where the user
|
|
1723
|
+
* found it.
|
|
1724
|
+
*/
|
|
1725
|
+
function onPanelResizeMouseDown(event) {
|
|
1726
|
+
if (event.button !== undefined && event.button !== 0) return;
|
|
1727
|
+
var doc = (typeof document !== "undefined") ? document : null;
|
|
1728
|
+
if (!doc || !doc.addEventListener) return;
|
|
1729
|
+
var el = panelRef.current;
|
|
1730
|
+
if (!el || !el.getBoundingClientRect) return;
|
|
1731
|
+
var rect = el.getBoundingClientRect();
|
|
1732
|
+
var anchor = { left: rect.left, top: rect.top };
|
|
1733
|
+
var startX = event.clientX;
|
|
1734
|
+
var base = diffWidth;
|
|
1735
|
+
if (event.preventDefault) event.preventDefault();
|
|
1736
|
+
setDragPos(anchor);
|
|
1737
|
+
function onMove(e) {
|
|
1738
|
+
setDiffWidth(clampDiffWidth(base + (e.clientX - startX), anchor));
|
|
1739
|
+
}
|
|
1740
|
+
function onUp() {
|
|
1741
|
+
doc.removeEventListener("mousemove", onMove);
|
|
1742
|
+
doc.removeEventListener("mouseup", onUp);
|
|
1743
|
+
try { doc.body.style.userSelect = ""; doc.body.style.cursor = ""; } catch (ignore) {}
|
|
1744
|
+
}
|
|
1745
|
+
doc.addEventListener("mousemove", onMove);
|
|
1746
|
+
doc.addEventListener("mouseup", onUp);
|
|
1747
|
+
try { doc.body.style.userSelect = "none"; doc.body.style.cursor = "col-resize"; } catch (ignore2) {}
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
function onPanelResizeTouchStart(event) {
|
|
1751
|
+
var touches = event.touches;
|
|
1752
|
+
if (!touches || touches.length !== 1) return;
|
|
1753
|
+
var doc = (typeof document !== "undefined") ? document : null;
|
|
1754
|
+
if (!doc || !doc.addEventListener) return;
|
|
1755
|
+
var el = panelRef.current;
|
|
1756
|
+
if (!el || !el.getBoundingClientRect) return;
|
|
1757
|
+
var rect = el.getBoundingClientRect();
|
|
1758
|
+
var anchor = { left: rect.left, top: rect.top };
|
|
1759
|
+
var startX = touches[0].clientX;
|
|
1760
|
+
var base = diffWidth;
|
|
1761
|
+
setDragPos(anchor);
|
|
1762
|
+
function onMove(e) {
|
|
1763
|
+
var touch = (e.touches && e.touches[0]) || (e.changedTouches && e.changedTouches[0]);
|
|
1764
|
+
if (!touch) return;
|
|
1765
|
+
if (e.preventDefault) e.preventDefault();
|
|
1766
|
+
setDiffWidth(clampDiffWidth(base + (touch.clientX - startX), anchor));
|
|
1767
|
+
}
|
|
1768
|
+
function onEnd() {
|
|
1769
|
+
doc.removeEventListener("touchmove", onMove);
|
|
1770
|
+
doc.removeEventListener("touchend", onEnd);
|
|
1771
|
+
doc.removeEventListener("touchcancel", onEnd);
|
|
1772
|
+
}
|
|
1773
|
+
doc.addEventListener("touchmove", onMove, { passive: false });
|
|
1774
|
+
doc.addEventListener("touchend", onEnd);
|
|
1775
|
+
doc.addEventListener("touchcancel", onEnd);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
// A window that shrank can leave the pane wider than the room it has.
|
|
1779
|
+
React.useEffect(function () {
|
|
1780
|
+
if (typeof window === "undefined" || !window.addEventListener) return undefined;
|
|
1781
|
+
function onResize() {
|
|
1782
|
+
setDiffWidth(function (current) { return clampDiffWidth(current); });
|
|
1783
|
+
}
|
|
1784
|
+
window.addEventListener("resize", onResize);
|
|
1785
|
+
return function () { window.removeEventListener("resize", onResize); };
|
|
1786
|
+
}, [dragPos]);
|
|
1787
|
+
|
|
1788
|
+
// The viewer follows the session's workspace; keeping a selection across
|
|
1789
|
+
// a rebind would show another repository's file.
|
|
1790
|
+
React.useEffect(function () {
|
|
1791
|
+
setSelected(null);
|
|
1792
|
+
}, [cwd]);
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* Select one change for the viewer. Clicking the file already showing
|
|
1796
|
+
* closes the pane (the panel returns to its compact single column), and
|
|
1797
|
+
* the first selection opens the full change list — the list is the
|
|
1798
|
+
* viewer's navigation, so leaving it behind "show more" hides half the
|
|
1799
|
+
* feature.
|
|
1800
|
+
*/
|
|
1801
|
+
function selectChange(change) {
|
|
1802
|
+
var file = change.file !== undefined ? change.file : change.path;
|
|
1803
|
+
var origFile = change.origFile !== undefined ? change.origFile : null;
|
|
1804
|
+
if (selected !== null && selected.file === file && selected.origFile === origFile) {
|
|
1805
|
+
setSelected(null);
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
setSelected({
|
|
1809
|
+
file: file,
|
|
1810
|
+
origFile: origFile,
|
|
1811
|
+
status: change.status,
|
|
1812
|
+
path: change.path !== undefined ? change.path : file,
|
|
1813
|
+
index: change.index,
|
|
1814
|
+
worktree: change.worktree
|
|
1815
|
+
});
|
|
1816
|
+
if (!changesOpen) setChangesOpen(true);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// Width follows what the panel is showing: the compact single column
|
|
1820
|
+
// while nothing is selected, and the two-pane width (workbench + diff)
|
|
1821
|
+
// once a file is open. Dragging the panel's right edge is what resizes
|
|
1822
|
+
// the diff, so the panel grows and shrinks with it.
|
|
1823
|
+
var panelWidth = selected === null ? S.panel.width : DIFF_LEFT_PANE_WIDTH + PANEL_BORDER_WIDTH + diffWidth;
|
|
1824
|
+
var panelStyle = Object.assign({}, S.panel, {
|
|
1825
|
+
width: panelWidth,
|
|
1826
|
+
maxHeight: selected === null ? S.panel.maxHeight : "72vh"
|
|
1827
|
+
});
|
|
1828
|
+
if (dragPos) {
|
|
1829
|
+
panelStyle = Object.assign(panelStyle, {
|
|
1830
|
+
left: dragPos.left,
|
|
1831
|
+
top: dragPos.top,
|
|
1832
|
+
bottom: "auto",
|
|
1833
|
+
transform: "none"
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
if (cwd === null) {
|
|
1838
|
+
// No Session bound (nothing to float over the input).
|
|
1839
|
+
return null;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
var busy = state.busy;
|
|
1843
|
+
var busyLabel = busy ? t("busy." + busy) : null;
|
|
1844
|
+
|
|
1845
|
+
// ── collapsed state: render nothing ─────────────────────────────
|
|
1846
|
+
// No floating bubble: the persistent status lives in the input.left
|
|
1847
|
+
// pill; this panel only appears when the user clicks that pill.
|
|
1848
|
+
if (!state.panelOpen) return null;
|
|
1849
|
+
|
|
1850
|
+
// ── expanded panel ───────────────────────────────────────────────
|
|
1851
|
+
var header = h("div", {
|
|
1852
|
+
key: "header",
|
|
1853
|
+
style: S.header,
|
|
1854
|
+
onMouseDown: onPanelHeaderMouseDown,
|
|
1855
|
+
onTouchStart: onPanelHeaderTouchStart,
|
|
1856
|
+
onDoubleClick: function () { setDragPos(null); },
|
|
1857
|
+
title: "拖动移动面板(双击复位)",
|
|
1858
|
+
"data-dsh-git": "panel-drag"
|
|
1859
|
+
}, [
|
|
1860
|
+
h("span", { key: "title", style: S.title }, "⎇ " + t("panel.title")),
|
|
1861
|
+
h("span", { key: "cwd", style: S.meta, title: cwd }, shortText(cwd, 60)),
|
|
1862
|
+
h("button", {
|
|
1863
|
+
key: "refresh",
|
|
1864
|
+
type: "button",
|
|
1865
|
+
style: S.button,
|
|
1866
|
+
disabled: busy !== null,
|
|
1867
|
+
onClick: function () { store.refresh(state.cwd); },
|
|
1868
|
+
"aria-label": t("refresh.aria"),
|
|
1869
|
+
title: t("refresh.aria")
|
|
1870
|
+
}, "↻"),
|
|
1871
|
+
h("button", {
|
|
1872
|
+
key: "collapse",
|
|
1873
|
+
type: "button",
|
|
1874
|
+
style: S.button,
|
|
1875
|
+
onClick: function () { store.setPanelOpen(false); },
|
|
1876
|
+
"aria-label": t("panel.collapse"),
|
|
1877
|
+
title: t("panel.collapse")
|
|
1878
|
+
}, "–")
|
|
1879
|
+
]);
|
|
1880
|
+
|
|
1881
|
+
var bodyChildren = [];
|
|
1882
|
+
// Operation feedback sits at the very TOP of the body. A push banner is
|
|
1883
|
+
// several lines long, and at the bottom of a 400px panel it landed under
|
|
1884
|
+
// the change list and the log — below the fold on any repo with a few
|
|
1885
|
+
// files, which is exactly where nobody looks after clicking a button.
|
|
1886
|
+
if (state.lastResult) {
|
|
1887
|
+
bodyChildren.push(outputView(state.lastResult, t, outputOpen, function () { setOutputOpen(!outputOpen); }));
|
|
1888
|
+
}
|
|
1889
|
+
if (busy) {
|
|
1890
|
+
bodyChildren.push(h("div", { key: "busy", style: S.meta }, busyLabel));
|
|
1891
|
+
}
|
|
1892
|
+
if (state.phase === "loading" || state.phase === "idle") {
|
|
1893
|
+
bodyChildren.push(h("div", { key: "loading", style: S.meta }, t("loading")));
|
|
1894
|
+
} else if (state.phase === "error") {
|
|
1895
|
+
bodyChildren.push(h("div", { key: "error", style: { color: "var(--dsw-alias-state-error-primary, #ef4444)", fontSize: 12, lineHeight: "18px" } }, shortText(state.error, 240)));
|
|
1896
|
+
} else if (!state.repo) {
|
|
1897
|
+
bodyChildren.push(h("div", { key: "norepo", style: S.meta }, t("notRepo")));
|
|
1898
|
+
} else {
|
|
1899
|
+
// status line
|
|
1900
|
+
var statusLine = [];
|
|
1901
|
+
var branchText = state.branch;
|
|
1902
|
+
if (!branchText && state.detached) {
|
|
1903
|
+
branchText = t("detached") + (state.oid ? " " + state.oid.slice(0, 7) : "");
|
|
1904
|
+
}
|
|
1905
|
+
statusLine.push(h("span", { key: "chip", style: S.chip, title: branchText || cwd }, branchText || "—"));
|
|
1906
|
+
if (state.dirty > 0) {
|
|
1907
|
+
statusLine.push(h("span", { key: "dirty", style: S.dirty, title: t("dirty", { count: state.dirty }) }, "● " + state.dirty));
|
|
1908
|
+
}
|
|
1909
|
+
if (state.ahead > 0 || state.behind > 0) {
|
|
1910
|
+
statusLine.push(h("span", { key: "ab", style: S.meta }, "↑" + state.ahead + " ↓" + state.behind));
|
|
1911
|
+
}
|
|
1912
|
+
if (state.upstream) {
|
|
1913
|
+
statusLine.push(h("span", { key: "up", style: S.meta, title: state.upstream }, shortText(state.upstream, 40)));
|
|
1914
|
+
}
|
|
1915
|
+
bodyChildren.push(h("div", { key: "status", style: S.row }, statusLine));
|
|
1916
|
+
|
|
1917
|
+
// branch switcher — dirty-tree pre-check: selecting a branch while
|
|
1918
|
+
// the tree has uncommitted changes shows a warning instead of an
|
|
1919
|
+
// immediate switch; the user decides (commit/stash, or force).
|
|
1920
|
+
var selectChildren = [h("option", { key: "ph", value: "", disabled: true, style: OPTION_STYLE }, branchText || "—")];
|
|
1921
|
+
Array.prototype.push.apply(selectChildren, branchOptions(state, t));
|
|
1922
|
+
bodyChildren.push(h("div", { key: "branch", style: S.row }, [
|
|
1923
|
+
h("select", {
|
|
1924
|
+
key: "sel",
|
|
1925
|
+
style: S.select,
|
|
1926
|
+
value: state.branch || "",
|
|
1927
|
+
disabled: busy !== null,
|
|
1928
|
+
onChange: function (event) {
|
|
1929
|
+
var value = event.target.value;
|
|
1930
|
+
if (!value || value === state.branch) { setPendingBranch(null); return; }
|
|
1931
|
+
if (state.dirty > 0) { setPendingBranch(value); return; }
|
|
1932
|
+
store.act("checkout", function () { return store.verbs.checkout(state.cwd, value); }, null, { branch: value });
|
|
1933
|
+
},
|
|
1934
|
+
"aria-label": t("switch.aria")
|
|
1935
|
+
}, selectChildren),
|
|
1936
|
+
h("button", {
|
|
1937
|
+
key: "new",
|
|
1938
|
+
type: "button",
|
|
1939
|
+
style: Object.assign({}, S.button, busy !== null ? S.buttonDisabled : null),
|
|
1940
|
+
disabled: busy !== null,
|
|
1941
|
+
title: t("branch.newTitle"),
|
|
1942
|
+
"aria-label": t("branch.new"),
|
|
1943
|
+
onClick: function () {
|
|
1944
|
+
if (!newBranchOpen && (newBranchBase === null || newBranchBase === "")) {
|
|
1945
|
+
setNewBranchBase(state.branch || (state.local.length > 0 ? state.local[0].name : ""));
|
|
1946
|
+
}
|
|
1947
|
+
setNewBranchOpen(!newBranchOpen);
|
|
1948
|
+
}
|
|
1949
|
+
}, busy === "createBranch" ? t("busy.createBranch") : "+ " + t("branch.new"))
|
|
1950
|
+
]));
|
|
1951
|
+
if (newBranchOpen) {
|
|
1952
|
+
var effectiveBase = newBranchBase || state.branch || "";
|
|
1953
|
+
var baseKids = baseOptions(state, t);
|
|
1954
|
+
bodyChildren.push(h("div", { key: "newbranch", style: S.row }, [
|
|
1955
|
+
h("input", {
|
|
1956
|
+
key: "name",
|
|
1957
|
+
type: "text",
|
|
1958
|
+
style: S.input,
|
|
1959
|
+
value: newBranchName,
|
|
1960
|
+
placeholder: t("newBranch.placeholder"),
|
|
1961
|
+
disabled: busy !== null,
|
|
1962
|
+
onChange: function (event) { setNewBranchName(event.target.value); },
|
|
1963
|
+
onKeyDown: function (event) {
|
|
1964
|
+
if (event.key === "Enter" && newBranchName.trim() !== "" && effectiveBase !== "" && busy === null) {
|
|
1965
|
+
doCreateBranch();
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
}),
|
|
1969
|
+
h("select", {
|
|
1970
|
+
key: "base",
|
|
1971
|
+
style: S.select,
|
|
1972
|
+
value: effectiveBase,
|
|
1973
|
+
disabled: busy !== null,
|
|
1974
|
+
title: t("newBranch.base"),
|
|
1975
|
+
"aria-label": t("newBranch.base"),
|
|
1976
|
+
onChange: function (event) { setNewBranchBase(event.target.value); }
|
|
1977
|
+
}, baseKids)
|
|
1978
|
+
]));
|
|
1979
|
+
bodyChildren.push(h("div", { key: "newbranchact", style: S.row }, [
|
|
1980
|
+
h("button", {
|
|
1981
|
+
key: "create",
|
|
1982
|
+
type: "button",
|
|
1983
|
+
style: Object.assign({}, S.buttonPrimary, busy !== null || newBranchName.trim() === "" || effectiveBase === "" ? S.buttonDisabled : null),
|
|
1984
|
+
disabled: busy !== null || newBranchName.trim() === "" || effectiveBase === "",
|
|
1985
|
+
title: newBranchName.trim() === "" ? t("newBranch.empty") : undefined,
|
|
1986
|
+
onClick: doCreateBranch
|
|
1987
|
+
}, busy === "createBranch" ? t("busy.createBranch") : t("newBranch.create")),
|
|
1988
|
+
h("button", {
|
|
1989
|
+
key: "cancel",
|
|
1990
|
+
type: "button",
|
|
1991
|
+
style: S.button,
|
|
1992
|
+
disabled: busy !== null,
|
|
1993
|
+
onClick: function () { setNewBranchOpen(false); }
|
|
1994
|
+
}, t("newBranch.cancel"))
|
|
1995
|
+
]));
|
|
1996
|
+
|
|
1997
|
+
function doCreateBranch() {
|
|
1998
|
+
var nameText = newBranchName.trim();
|
|
1999
|
+
var baseText = effectiveBase;
|
|
2000
|
+
if (nameText === "" || baseText === "" || busy !== null) return;
|
|
2001
|
+
setNewBranchName("");
|
|
2002
|
+
setNewBranchOpen(false);
|
|
2003
|
+
store.act("createBranch", function () { return store.verbs.createBranch(state.cwd, nameText, baseText); }, null, { branch: nameText });
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
if (pendingBranch !== null && state.dirty > 0) {
|
|
2007
|
+
var samplePaths = state.changes.slice(0, 3).map(function (change) { return change.path; }).join("、");
|
|
2008
|
+
if (samplePaths === "") samplePaths = "…";
|
|
2009
|
+
if (samplePaths.length > 60) samplePaths = samplePaths.slice(0, 57) + "…";
|
|
2010
|
+
bodyChildren.push(h("div", { key: "dirtywarn", style: S.warn }, [
|
|
2011
|
+
h("span", { key: "txt", style: { flex: "1", minWidth: 0 } },
|
|
2012
|
+
t("switch.dirtyWarn", { count: state.dirty, paths: samplePaths })),
|
|
2013
|
+
h("button", {
|
|
2014
|
+
key: "force",
|
|
2015
|
+
type: "button",
|
|
2016
|
+
style: Object.assign({}, S.button, busy !== null ? S.buttonDisabled : null),
|
|
2017
|
+
disabled: busy !== null,
|
|
2018
|
+
onClick: function () {
|
|
2019
|
+
var target = pendingBranch;
|
|
2020
|
+
setPendingBranch(null);
|
|
2021
|
+
store.act("checkout", function () { return store.verbs.checkout(state.cwd, target); }, null, { branch: target });
|
|
2022
|
+
}
|
|
2023
|
+
}, busy === "checkout" ? t("busy.checkout") : t("switch.force"))
|
|
2024
|
+
]));
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// network + commit row
|
|
2028
|
+
bodyChildren.push(h("div", { key: "net", style: S.row }, [
|
|
2029
|
+
h("button", {
|
|
2030
|
+
key: "fetch",
|
|
2031
|
+
type: "button",
|
|
2032
|
+
style: Object.assign({}, S.button, busy !== null ? S.buttonDisabled : null),
|
|
2033
|
+
disabled: busy !== null,
|
|
2034
|
+
onClick: function () { store.act("fetch", function () { return store.verbs.fetch(state.cwd); }); }
|
|
2035
|
+
}, busy === "fetch" ? t("busy.fetch") : t("action.fetch")),
|
|
2036
|
+
h("button", {
|
|
2037
|
+
key: "pull",
|
|
2038
|
+
type: "button",
|
|
2039
|
+
style: Object.assign({}, S.button, busy !== null ? S.buttonDisabled : null),
|
|
2040
|
+
disabled: busy !== null,
|
|
2041
|
+
onClick: function () { store.act("pull", function () { return store.verbs.pull(state.cwd); }); }
|
|
2042
|
+
}, busy === "pull" ? t("busy.pull") : t("action.pull")),
|
|
2043
|
+
h("button", {
|
|
2044
|
+
key: "push",
|
|
2045
|
+
type: "button",
|
|
2046
|
+
style: Object.assign({}, S.buttonPrimary, busy !== null ? S.buttonDisabled : null),
|
|
2047
|
+
disabled: busy !== null,
|
|
2048
|
+
onClick: function () { store.act("push", function () { return store.verbs.push(state.cwd); }); }
|
|
2049
|
+
}, busy === "push" ? t("busy.push") : t("action.push"))
|
|
2050
|
+
]));
|
|
2051
|
+
|
|
2052
|
+
// commit tooling: stage everything, pick the draft basis, draft.
|
|
2053
|
+
// Ordering mirrors the real workflow (stage → draft → commit) and
|
|
2054
|
+
// keeps the 400px panel readable by splitting the tool row from the
|
|
2055
|
+
// message row.
|
|
2056
|
+
var noChanges = state.dirty === 0;
|
|
2057
|
+
bodyChildren.push(h("div", { key: "commitTools", style: S.row }, [
|
|
2058
|
+
h("button", {
|
|
2059
|
+
key: "stage",
|
|
2060
|
+
type: "button",
|
|
2061
|
+
style: Object.assign({}, S.button, busy !== null || noChanges ? S.buttonDisabled : null),
|
|
2062
|
+
disabled: busy !== null || noChanges,
|
|
2063
|
+
title: noChanges ? t("changes.none") : t("action.stage"),
|
|
2064
|
+
onClick: function () { store.act("stage", function () { return store.verbs.stage(state.cwd); }); }
|
|
2065
|
+
}, busy === "stage" ? t("busy.stage") : t("action.stage")),
|
|
2066
|
+
h("select", {
|
|
2067
|
+
key: "mode",
|
|
2068
|
+
style: Object.assign({}, S.select, { flex: "none", width: "auto", maxWidth: 96 }),
|
|
2069
|
+
value: generateMode,
|
|
2070
|
+
disabled: busy !== null,
|
|
2071
|
+
title: t("generate.mode.title"),
|
|
2072
|
+
"aria-label": t("generate.mode.title"),
|
|
2073
|
+
onChange: function (event) { setGenerateMode(event.target.value); }
|
|
2074
|
+
}, [
|
|
2075
|
+
h("option", { key: "staged", value: "staged", style: OPTION_STYLE }, t("generate.mode.staged")),
|
|
2076
|
+
h("option", { key: "unstaged", value: "unstaged", style: OPTION_STYLE }, t("generate.mode.unstaged")),
|
|
2077
|
+
h("option", { key: "all", value: "all", style: OPTION_STYLE }, t("generate.mode.all"))
|
|
2078
|
+
]),
|
|
2079
|
+
h("button", {
|
|
2080
|
+
key: "generate",
|
|
2081
|
+
type: "button",
|
|
2082
|
+
style: Object.assign({}, S.button, busy !== null || noChanges ? S.buttonDisabled : null),
|
|
2083
|
+
disabled: busy !== null || noChanges,
|
|
2084
|
+
title: noChanges ? t("changes.none") : t("action.generate"),
|
|
2085
|
+
onClick: doGenerate
|
|
2086
|
+
}, busy === "generate" ? t("busy.generate") : "✨ " + t("action.generate"))
|
|
2087
|
+
]));
|
|
2088
|
+
|
|
2089
|
+
bodyChildren.push(h("div", { key: "commit", style: S.row }, [
|
|
2090
|
+
h("textarea", {
|
|
2091
|
+
key: "msg",
|
|
2092
|
+
rows: 2,
|
|
2093
|
+
style: S.textarea,
|
|
2094
|
+
value: message,
|
|
2095
|
+
placeholder: t("commit.placeholder"),
|
|
2096
|
+
disabled: busy !== null,
|
|
2097
|
+
onChange: function (event) { setMessage(event.target.value); },
|
|
2098
|
+
// Enter inserts a newline (a message may have a body); the
|
|
2099
|
+
// explicit commit chord is Ctrl/Cmd+Enter.
|
|
2100
|
+
onKeyDown: function (event) {
|
|
2101
|
+
if (event.key === "Enter" && (event.ctrlKey || event.metaKey) && message.trim() !== "" && busy === null) {
|
|
2102
|
+
event.preventDefault();
|
|
2103
|
+
doCommit();
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
}),
|
|
2107
|
+
h("button", {
|
|
2108
|
+
key: "commit",
|
|
2109
|
+
type: "button",
|
|
2110
|
+
style: Object.assign({}, S.buttonPrimary, busy !== null || message.trim() === "" ? S.buttonDisabled : null),
|
|
2111
|
+
disabled: busy !== null || message.trim() === "",
|
|
2112
|
+
title: message.trim() === "" ? t("commit.empty") : undefined,
|
|
2113
|
+
onClick: doCommit
|
|
2114
|
+
}, busy === "commit" ? t("busy.commit") : t("action.commit"))
|
|
2115
|
+
]));
|
|
2116
|
+
|
|
2117
|
+
function doCommit() {
|
|
2118
|
+
var text = message.trim();
|
|
2119
|
+
if (text === "" || busy !== null) return;
|
|
2120
|
+
setMessage("");
|
|
2121
|
+
store.act("commit", function () { return store.verbs.commit(state.cwd, text); });
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
/**
|
|
2125
|
+
* Word one generation outcome. The host reports a stable code in
|
|
2126
|
+
* `error.details.code`; known codes get a localized sentence and
|
|
2127
|
+
* anything unrecognized falls through to the host's own message.
|
|
2128
|
+
*/
|
|
2129
|
+
function describeGenerate(res) {
|
|
2130
|
+
if (res && res.ok === true) {
|
|
2131
|
+
return { kind: "ok", text: t("generate.done") };
|
|
2132
|
+
}
|
|
2133
|
+
var details = res && res.error && res.error.details ? res.error.details : {};
|
|
2134
|
+
var known = {
|
|
2135
|
+
"no-changes": details.mode === "staged" ? t("generate.noStaged") : t("generate.noChanges"),
|
|
2136
|
+
"no-provider": t("generate.noProvider"),
|
|
2137
|
+
"no-model": t("generate.noModel"),
|
|
2138
|
+
"llm-empty": t("generate.empty"),
|
|
2139
|
+
"llm-truncated": t("generate.truncated"),
|
|
2140
|
+
"cancelled": t("generate.cancelled")
|
|
2141
|
+
};
|
|
2142
|
+
var text = known[details.code];
|
|
2143
|
+
if (text === undefined) {
|
|
2144
|
+
text = res && res.error && res.error.message ? String(res.error.message) : t("generate.failed");
|
|
2145
|
+
}
|
|
2146
|
+
return { kind: "error", text: text };
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
function doGenerate() {
|
|
2150
|
+
if (busy !== null) return;
|
|
2151
|
+
var route = sessionRoute;
|
|
2152
|
+
store.act("generate", function () {
|
|
2153
|
+
return store.verbs.generateMessage(
|
|
2154
|
+
state.cwd,
|
|
2155
|
+
generateMode,
|
|
2156
|
+
route ? route.provider : undefined,
|
|
2157
|
+
route ? route.model : undefined
|
|
2158
|
+
);
|
|
2159
|
+
}, describeGenerate).then(function (res) {
|
|
2160
|
+
if (res && res.ok === true && res.value && res.value.message) {
|
|
2161
|
+
setMessage(String(res.value.message));
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// changes (one row per file) — collapsible, and the diff viewer's
|
|
2167
|
+
// navigation: clicking a row opens that file's diff on the right.
|
|
2168
|
+
var changesRow;
|
|
2169
|
+
if (state.changes.length === 0) {
|
|
2170
|
+
changesRow = h("div", { key: "none", style: S.meta }, t("changes.none"));
|
|
2171
|
+
} else {
|
|
2172
|
+
var shown = changesOpen ? state.changes : state.changes.slice(0, 8);
|
|
2173
|
+
changesRow = h("div", { key: "list" }, shown.map(function (change, index) {
|
|
2174
|
+
var file = change.file !== undefined ? change.file : change.path;
|
|
2175
|
+
var origFile = change.origFile !== undefined ? change.origFile : null;
|
|
2176
|
+
var isActive = selected !== null && selected.file === file && selected.origFile === origFile;
|
|
2177
|
+
return h("div", {
|
|
2178
|
+
key: index,
|
|
2179
|
+
style: isActive ? S.changeRowActive : S.changeRow,
|
|
2180
|
+
role: "button",
|
|
2181
|
+
tabIndex: 0,
|
|
2182
|
+
title: t("diff.open"),
|
|
2183
|
+
"aria-label": t("diff.open") + " " + change.path,
|
|
2184
|
+
"data-dsh-git": "change-row",
|
|
2185
|
+
"data-active": isActive === true ? "true" : "false",
|
|
2186
|
+
onClick: function () { selectChange(change); },
|
|
2187
|
+
onKeyDown: function (event) {
|
|
2188
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
2189
|
+
if (event.preventDefault) event.preventDefault();
|
|
2190
|
+
selectChange(change);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
}, [
|
|
2194
|
+
h("span", { key: "tag", style: S.statusTag }, t("status." + change.status)),
|
|
2195
|
+
h("span", { key: "path", style: S.changePath, title: change.path }, change.path)
|
|
2196
|
+
]);
|
|
2197
|
+
}));
|
|
2198
|
+
if (state.changes.length > shown.length) {
|
|
2199
|
+
changesRow = h("div", { key: "list" }, [
|
|
2200
|
+
changesRow,
|
|
2201
|
+
h("button", {
|
|
2202
|
+
key: "more",
|
|
2203
|
+
type: "button",
|
|
2204
|
+
style: S.button,
|
|
2205
|
+
onClick: function () { setChangesOpen(!changesOpen); }
|
|
2206
|
+
}, changesOpen ? "▴" : "▾ " + (state.changes.length - shown.length))
|
|
2207
|
+
]);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
bodyChildren.push(h("div", { key: "changes", style: S.sectionTitle, onClick: function () { setChangesOpen(!changesOpen); } }, (changesOpen ? "▾ " : "▸ ") + t("changes.title", { count: state.changes.length })));
|
|
2211
|
+
bodyChildren.push(h("div", { key: "changesBody" }, changesRow));
|
|
2212
|
+
|
|
2213
|
+
// recent commits — collapsible
|
|
2214
|
+
var logRows;
|
|
2215
|
+
if (state.commits.length === 0) {
|
|
2216
|
+
logRows = h("div", { key: "none", style: S.meta }, t("log.none"));
|
|
2217
|
+
} else {
|
|
2218
|
+
var logShown = logOpen ? state.commits : state.commits.slice(0, 5);
|
|
2219
|
+
logRows = h("div", { key: "list" }, logShown.map(function (commit, index) {
|
|
2220
|
+
return h("div", { key: index, style: S.logRow, title: commit.subject }, [
|
|
2221
|
+
h("span", { key: "sha", style: S.logSha }, commit.sha),
|
|
2222
|
+
h("span", { key: "subj", style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: "1", minWidth: 0 } }, commit.subject)
|
|
2223
|
+
]);
|
|
2224
|
+
}));
|
|
2225
|
+
if (state.commits.length > logShown.length) {
|
|
2226
|
+
logRows = h("div", { key: "list" }, [
|
|
2227
|
+
logRows,
|
|
2228
|
+
h("button", {
|
|
2229
|
+
key: "more",
|
|
2230
|
+
type: "button",
|
|
2231
|
+
style: S.button,
|
|
2232
|
+
onClick: function () { setLogOpen(!logOpen); }
|
|
2233
|
+
}, logOpen ? "▴" : "▾ " + (state.commits.length - logShown.length))
|
|
2234
|
+
]);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
bodyChildren.push(h("div", { key: "logTitle", style: S.sectionTitle, onClick: function () { setLogOpen(!logOpen); } }, (logOpen ? "▾ " : "▸ ") + t("log.title")));
|
|
2238
|
+
bodyChildren.push(h("div", { key: "logBody" }, logRows));
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
// Everything the open diff depends on, as one comparable string: a
|
|
2242
|
+
// commit moves the oid, a branch switch moves several of these, a
|
|
2243
|
+
// session switch changes the cwd, and STAGING moves the per-file index
|
|
2244
|
+
// letters. The letters are in here, not just the file count, because
|
|
2245
|
+
// `git add` leaves the count and the dirty tally untouched while moving
|
|
2246
|
+
// the change from the worktree side to the index side — the exact move
|
|
2247
|
+
// the open diff has to follow.
|
|
2248
|
+
var dataVersion = [
|
|
2249
|
+
state.cwd,
|
|
2250
|
+
state.oid,
|
|
2251
|
+
state.branch,
|
|
2252
|
+
state.dirty,
|
|
2253
|
+
state.changes.map(function (change) {
|
|
2254
|
+
return (change.index !== undefined ? change.index : " ") + (change.worktree !== undefined ? change.worktree : " ") + change.file;
|
|
2255
|
+
}).join(",")
|
|
2256
|
+
].join("|");
|
|
2257
|
+
|
|
2258
|
+
// The pane's state chip follows the CURRENT status of the file, not the
|
|
2259
|
+
// one captured when it was clicked: staging or committing moves a file
|
|
2260
|
+
// between the list's labels while the pane stays open on it.
|
|
2261
|
+
var liveSelection = selected;
|
|
2262
|
+
if (selected !== null) {
|
|
2263
|
+
for (var ci = 0; ci < state.changes.length; ci += 1) {
|
|
2264
|
+
var candidate = state.changes[ci];
|
|
2265
|
+
var candidateFile = candidate.file !== undefined ? candidate.file : candidate.path;
|
|
2266
|
+
var candidateOrig = candidate.origFile !== undefined ? candidate.origFile : null;
|
|
2267
|
+
if (candidateFile === selected.file && candidateOrig === selected.origFile) {
|
|
2268
|
+
liveSelection = Object.assign({}, selected, { status: candidate.status, path: candidate.path });
|
|
2269
|
+
break;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
var body = selected === null
|
|
2275
|
+
? h("div", { key: "body", style: S.body }, bodyChildren)
|
|
2276
|
+
: h("div", { key: "body", style: S.bodySplit }, [
|
|
2277
|
+
h("div", { key: "workbench", style: S.leftPane, "data-dsh-git": "panel-left" }, bodyChildren),
|
|
2278
|
+
h(GitDiffPane, {
|
|
2279
|
+
// Keyed by file so a new selection starts with fresh view state
|
|
2280
|
+
// (side choice, wrapping, "show all") instead of inheriting the
|
|
2281
|
+
// previous file's.
|
|
2282
|
+
key: "diff:" + selected.file + "\u0000" + (selected.origFile === null || selected.origFile === undefined ? "" : selected.origFile),
|
|
2283
|
+
store: store,
|
|
2284
|
+
t: t,
|
|
2285
|
+
cwd: state.cwd,
|
|
2286
|
+
change: liveSelection,
|
|
2287
|
+
dataVersion: dataVersion,
|
|
2288
|
+
onClose: function () { setSelected(null); }
|
|
2289
|
+
})
|
|
2290
|
+
]);
|
|
2291
|
+
|
|
2292
|
+
// The diff's width handle IS the panel's right edge: dragging it widens
|
|
2293
|
+
// the panel (the workbench keeps its width) and the diff takes the rest.
|
|
2294
|
+
// It covers the full height but sits under the header, so the header's
|
|
2295
|
+
// own drag and its buttons keep the pointer in that band.
|
|
2296
|
+
var resizeHandle = selected === null ? null : h("div", {
|
|
2297
|
+
key: "resize",
|
|
2298
|
+
"data-dsh-git": "panel-resize",
|
|
2299
|
+
style: resizeHover === true ? Object.assign({}, S.resizeHandle, S.resizeHandleActive) : S.resizeHandle,
|
|
2300
|
+
role: "separator",
|
|
2301
|
+
"aria-orientation": "vertical",
|
|
2302
|
+
"aria-label": t("diff.resize"),
|
|
2303
|
+
title: t("diff.resize"),
|
|
2304
|
+
onMouseDown: onPanelResizeMouseDown,
|
|
2305
|
+
onTouchStart: onPanelResizeTouchStart,
|
|
2306
|
+
onDoubleClick: function () { setDiffWidth(clampDiffWidth(DIFF_DEFAULT_WIDTH)); },
|
|
2307
|
+
onMouseEnter: function () { setResizeHover(true); },
|
|
2308
|
+
onMouseLeave: function () { setResizeHover(false); }
|
|
2309
|
+
});
|
|
2310
|
+
|
|
2311
|
+
return h("div", { "data-dsh-git": "panel", style: panelStyle, ref: panelRef }, [header, body, resizeHandle]);
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
/**
|
|
2315
|
+
* Input-dock pill (conversation.input.dock): a compact status capsule,
|
|
2316
|
+
* left-aligned above the composer card (textarea top-left); clicking
|
|
2317
|
+
* toggles the workbench panel. Reads the same store, so it stays in
|
|
2318
|
+
* sync with the panel and with session switches.
|
|
2319
|
+
*/
|
|
2320
|
+
function GitDockLine(props) {
|
|
2321
|
+
var store = props.store;
|
|
2322
|
+
var t = props.t || function (key, params) {
|
|
2323
|
+
return key + (params ? " " + JSON.stringify(params) : "");
|
|
2324
|
+
};
|
|
2325
|
+
// This seat is session-scoped, so the framework hands it the current
|
|
2326
|
+
// Session identity; that `sessionId` is the one thing a root-scoped
|
|
2327
|
+
// seat (the panel) cannot read for itself under dsh >= 0.1.6.
|
|
2328
|
+
var sessionId = props.sessionId;
|
|
2329
|
+
var useSessions = props.useSessions;
|
|
2330
|
+
// useSessions is a selector hook (useSyncExternalStoreWithSelector):
|
|
2331
|
+
// the selector argument is REQUIRED — a bare useSessions() crashes
|
|
2332
|
+
// with "selector is not a function".
|
|
2333
|
+
var sessions = useSessions(function (s) { return s; });
|
|
2334
|
+
var summary = sessionId !== undefined && sessions && sessions.byId ? sessions.byId[sessionId] : undefined;
|
|
2335
|
+
var cwd = summary ? summary.cwd : undefined;
|
|
2336
|
+
|
|
2337
|
+
// Embedded Conversations (a right-sidebar chat tab) carry their own
|
|
2338
|
+
// Session binding; only the main-view seat may drive the shared
|
|
2339
|
+
// workbench, or a subagent chat would pull the panel's repo out from
|
|
2340
|
+
// under the Session the user is looking at. `retainedBy.mainView` is
|
|
2341
|
+
// the same signal ui-session derives the current Session from; when
|
|
2342
|
+
// the snapshot does not carry it (an older host) the seat binds
|
|
2343
|
+
// anyway, so the pill never disappears for want of a count.
|
|
2344
|
+
var mainView = summary === undefined || summary.retainedBy === undefined || summary.retainedBy === null
|
|
2345
|
+
? true
|
|
2346
|
+
: (summary.retainedBy.mainView || 0) > 0;
|
|
2347
|
+
|
|
2348
|
+
// Classic subscription pattern (see GitFloatingPanel for why).
|
|
2349
|
+
var stateRef = React.useState(function () { return store.getSnapshot(); });
|
|
2350
|
+
var state = stateRef[0];
|
|
2351
|
+
var setState = stateRef[1];
|
|
2352
|
+
React.useEffect(function () {
|
|
2353
|
+
var unsubscribe = store.subscribe(function () {
|
|
2354
|
+
setState(store.getSnapshot());
|
|
2355
|
+
});
|
|
2356
|
+
return unsubscribe;
|
|
2357
|
+
}, [store]);
|
|
2358
|
+
|
|
2359
|
+
// The pill owns the store's session binding: it is the only seat that
|
|
2360
|
+
// knows the current Session's identity and workspace directory.
|
|
2361
|
+
React.useEffect(function () {
|
|
2362
|
+
if (!mainView) return;
|
|
2363
|
+
store.bindSession(sessionId, cwd);
|
|
2364
|
+
}, [sessionId, cwd, mainView, store]);
|
|
2365
|
+
|
|
2366
|
+
if (!cwd || !mainView || state.sessionId !== sessionId) return null;
|
|
2367
|
+
|
|
2368
|
+
var text;
|
|
2369
|
+
if (state.phase === "loading" || state.phase === "idle") {
|
|
2370
|
+
text = "⎇ " + t("loading");
|
|
2371
|
+
} else if (state.phase === "error" || !state.repo) {
|
|
2372
|
+
text = "⎇ " + t("notRepo");
|
|
2373
|
+
} else {
|
|
2374
|
+
var branchText = state.branch;
|
|
2375
|
+
if (!branchText && state.detached) {
|
|
2376
|
+
branchText = t("detached") + (state.oid ? " " + state.oid.slice(0, 7) : "");
|
|
2377
|
+
}
|
|
2378
|
+
text = "⎇ " + (branchText || "—");
|
|
2379
|
+
if (state.dirty > 0) text += " ●" + state.dirty;
|
|
2380
|
+
if (state.ahead > 0 || state.behind > 0) text += " ↑" + state.ahead + " ↓" + state.behind;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
// Left-aligned compact pill in the row band ABOVE the composer card
|
|
2384
|
+
// (conversation.input.dock). The padding-left mirrors the centered
|
|
2385
|
+
// card's left margin, so the pill hugs the textarea's top-left
|
|
2386
|
+
// corner instead of floating at the conversation column's edge.
|
|
2387
|
+
return h("div", {
|
|
2388
|
+
style: {
|
|
2389
|
+
boxSizing: "border-box",
|
|
2390
|
+
display: "flex",
|
|
2391
|
+
justifyContent: "flex-start",
|
|
2392
|
+
width: "100%",
|
|
2393
|
+
paddingLeft: "calc((100% - var(--dsh-composer-card-max-width, 778px)) / 2)",
|
|
2394
|
+
margin: "2px 0"
|
|
2395
|
+
},
|
|
2396
|
+
"data-dsh-git": "dock-row"
|
|
2397
|
+
}, h("button", {
|
|
2398
|
+
type: "button",
|
|
2399
|
+
style: {
|
|
2400
|
+
display: "inline-flex",
|
|
2401
|
+
alignItems: "center",
|
|
2402
|
+
gap: 6,
|
|
2403
|
+
maxWidth: 220,
|
|
2404
|
+
padding: "1px 10px",
|
|
2405
|
+
borderRadius: 999,
|
|
2406
|
+
border: "1px solid var(--dsw-alias-separator-primary, rgba(127,127,127,0.25))",
|
|
2407
|
+
background: "var(--dsw-alias-bg-layer-1, rgba(127,127,127,0.12))",
|
|
2408
|
+
color: "var(--dsw-alias-label-secondary, #c8ccd2)",
|
|
2409
|
+
font: "inherit",
|
|
2410
|
+
fontSize: 12,
|
|
2411
|
+
lineHeight: "20px",
|
|
2412
|
+
cursor: "pointer",
|
|
2413
|
+
whiteSpace: "nowrap",
|
|
2414
|
+
overflow: "hidden",
|
|
2415
|
+
textOverflow: "ellipsis",
|
|
2416
|
+
userSelect: "none"
|
|
2417
|
+
},
|
|
2418
|
+
"data-dsh-git": "dock",
|
|
2419
|
+
title: cwd,
|
|
2420
|
+
"aria-label": t("dock.aria"),
|
|
2421
|
+
onClick: function () { store.setPanelOpen(!state.panelOpen); }
|
|
2422
|
+
}, text));
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
/**
|
|
2426
|
+
* Client plugin body.
|
|
2427
|
+
* @param ctx - client root context (slots, connection, locale).
|
|
2428
|
+
*/
|
|
2429
|
+
function apply(ctx) {
|
|
2430
|
+
ctx.effect(function () {
|
|
2431
|
+
return ctx.locale.register(NS, { zh: zh, en: en });
|
|
2432
|
+
}, "dsh-git: dictionaries");
|
|
2433
|
+
|
|
2434
|
+
var store = createGitStore(ctx);
|
|
2435
|
+
|
|
2436
|
+
ctx.slots.inject("shell.overlay", function () {
|
|
2437
|
+
return ctx.slots.register({
|
|
2438
|
+
name: "shell.overlay",
|
|
2439
|
+
id: "dsh-git-panel",
|
|
2440
|
+
order: 10,
|
|
2441
|
+
locale: NS,
|
|
2442
|
+
inject: function () {
|
|
2443
|
+
return { store: store };
|
|
2444
|
+
}
|
|
2445
|
+
}, GitFloatingPanel);
|
|
2446
|
+
});
|
|
2447
|
+
|
|
2448
|
+
ctx.slots.inject("conversation.input.dock", function () {
|
|
2449
|
+
return ctx.slots.register({
|
|
2450
|
+
name: "conversation.input.dock",
|
|
2451
|
+
id: "dsh-git-pill",
|
|
2452
|
+
order: 20,
|
|
2453
|
+
locale: NS,
|
|
2454
|
+
inject: function (sessionId) {
|
|
2455
|
+
return { store: store };
|
|
2456
|
+
}
|
|
2457
|
+
}, GitDockLine);
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
exports.apply = apply;
|
|
2462
|
+
exports.inject = inject;
|
|
2463
|
+
exports.name = name;
|
|
2464
|
+
// Test-only surface: the module-loader contract reads `apply`, `inject`,
|
|
2465
|
+
// and `name` and ignores everything else, while the render test needs the
|
|
2466
|
+
// diff parser and one row renderer without a browser (SSR runs no effects,
|
|
2467
|
+
// so the pane's own reads cannot be driven from a static render).
|
|
2468
|
+
exports.__internals = {
|
|
2469
|
+
parseUnifiedDiff: parseUnifiedDiff,
|
|
2470
|
+
parseDiffSide: parseDiffSide,
|
|
2471
|
+
diffRowView: diffRowView,
|
|
2472
|
+
GitDiffPane: GitDiffPane,
|
|
2473
|
+
DIFF_ROW: DIFF_ROW,
|
|
2474
|
+
stripAnsiEscapes: stripAnsiEscapes,
|
|
2475
|
+
outputView: outputView
|
|
2476
|
+
};
|
|
2477
|
+
return module.exports;
|
|
2478
|
+
}
|
|
2479
|
+
});
|