dsh-remote-vs 0.9.3 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.en.md +13 -10
  2. package/README.md +14 -9
  3. package/cordis.patch.yml +10 -5
  4. package/lib/client.js +3319 -1093
  5. package/lib/index.js +211 -9
  6. package/lib/types/client/api.d.ts +12 -0
  7. package/lib/types/client/composer.d.ts +5 -1
  8. package/lib/types/client/exec-output.d.ts +58 -0
  9. package/lib/types/client/i18n.d.ts +1 -1
  10. package/lib/types/client/scm/CommitPane.d.ts +73 -0
  11. package/lib/types/client/scm/ExecPanel.d.ts +34 -0
  12. package/lib/types/client/scm/HistoryPanel.d.ts +12 -4
  13. package/lib/types/client/scm/actions.d.ts +50 -3
  14. package/lib/types/client/scm/commit-model.d.ts +119 -0
  15. package/lib/types/client/store.d.ts +9 -3
  16. package/lib/types/client/vsc/kit.d.ts +16 -0
  17. package/lib/types/git-format.d.ts +5 -0
  18. package/lib/types/git-ops.d.ts +57 -2
  19. package/lib/types/index.d.ts +5 -0
  20. package/lib/types/pool.d.ts +9 -0
  21. package/lib/types/protocol.d.ts +23 -0
  22. package/lib/types/timeouts.d.ts +17 -2
  23. package/package.json +37 -37
  24. package/src/client/SettingsHosts.tsx +2 -1
  25. package/src/client/api.ts +61 -0
  26. package/src/client/composer.ts +11 -2
  27. package/src/client/exec-output.ts +129 -0
  28. package/src/client/i18n.ts +133 -4
  29. package/src/client/scm/CommitPane.tsx +726 -0
  30. package/src/client/scm/ExecPanel.tsx +124 -0
  31. package/src/client/scm/HistoryPanel.tsx +483 -104
  32. package/src/client/scm/ScmPanel.tsx +247 -9
  33. package/src/client/scm/actions.ts +93 -10
  34. package/src/client/scm/commit-model.ts +317 -0
  35. package/src/client/store.tsx +13 -6
  36. package/src/client/vsc/kit.tsx +71 -0
  37. package/src/client/vsc/vsc.css +647 -0
  38. package/src/git-format.ts +7 -3
  39. package/src/git-ops.ts +83 -7
  40. package/src/index.ts +10 -2
  41. package/src/pool.ts +84 -4
  42. package/src/protocol.ts +14 -0
  43. package/src/routes.ts +135 -1
  44. package/src/timeouts.ts +18 -2
@@ -2075,6 +2075,542 @@ body:not([data-ds-dark-theme]) .rvs-undo-affordance:focus-visible {
2075
2075
  font-size: 11px;
2076
2076
  }
2077
2077
 
2078
+ /* ------------------------------------------------------- commit pane */
2079
+
2080
+ /**
2081
+ * The history panel's bottom half: the graph keeps the top, the selected
2082
+ * commit takes this pane below the drag handle (same gesture and styling as
2083
+ * the editor split, so `.rvs-vsplit-handle` is reused rather than copied).
2084
+ */
2085
+ .rvs-history-body {
2086
+ flex: 1;
2087
+ min-height: 0;
2088
+ display: flex;
2089
+ flex-direction: column;
2090
+ }
2091
+
2092
+ .rvs-hpane {
2093
+ flex: none;
2094
+ min-height: 140px;
2095
+ max-height: 82%;
2096
+ display: flex;
2097
+ flex-direction: column;
2098
+ overflow: hidden;
2099
+ border-top: 1px solid var(--rvs-border);
2100
+ /* Same glass as the graph above (the sidebar's own backdrop shows through):
2101
+ an opaque `.rvs-code-bg` here read as a separate dark slab glued under the
2102
+ list. Patch rows already carry their own translucent add/del washes, so the
2103
+ diff stays legible without a filled background. */
2104
+ background: transparent;
2105
+ }
2106
+
2107
+ /* Full-height mode: the graph is hidden by the panel, so the pane takes the
2108
+ remaining column instead of its persisted pixel height. */
2109
+ .rvs-hpane.expanded {
2110
+ flex: 1;
2111
+ max-height: none;
2112
+ }
2113
+
2114
+ /* A toolbar toggle that is currently ON (wrap, for example). */
2115
+ .rvs-hbtn.active {
2116
+ color: var(--rvs-brand);
2117
+ }
2118
+
2119
+ .rvs-hpane-body {
2120
+ flex: 1;
2121
+ min-height: 0;
2122
+ display: flex;
2123
+ flex-direction: column;
2124
+ }
2125
+
2126
+ /* Tab strip: same underline idiom as the editor's diff/worktree switch, but
2127
+ scoped so the pane's three views cannot be confused with editor tabs. */
2128
+ .rvs-htabs {
2129
+ flex: none;
2130
+ display: flex;
2131
+ align-items: center;
2132
+ gap: 2px;
2133
+ height: 30px;
2134
+ padding: 0 6px;
2135
+ border-bottom: 1px solid var(--rvs-border);
2136
+ }
2137
+
2138
+ /* The tabs themselves live in their own role=tablist element: the strip also
2139
+ carries the hash, expand and close buttons, and those must NOT inherit the
2140
+ tablist's arrow-key handling (ArrowRight on "close" used to switch tabs). */
2141
+ .rvs-hpane-tablist {
2142
+ flex: none;
2143
+ display: flex;
2144
+ align-items: center;
2145
+ gap: 2px;
2146
+ }
2147
+
2148
+ .rvs-htab {
2149
+ appearance: none;
2150
+ border: none;
2151
+ border-bottom: 2px solid transparent;
2152
+ background: transparent;
2153
+ color: var(--rvs-fg3);
2154
+ font: inherit;
2155
+ font-size: 12px;
2156
+ padding: 5px 8px 4px;
2157
+ cursor: pointer;
2158
+ }
2159
+
2160
+ .rvs-htab:hover {
2161
+ color: var(--rvs-fg);
2162
+ }
2163
+
2164
+ .rvs-htab.active {
2165
+ color: var(--rvs-fg);
2166
+ border-bottom-color: var(--rvs-brand);
2167
+ }
2168
+
2169
+ .rvs-htabs-spacer {
2170
+ flex: 1;
2171
+ min-width: 0;
2172
+ }
2173
+
2174
+ .rvs-hhash-mini {
2175
+ flex: none;
2176
+ margin-right: 2px;
2177
+ font-family: var(--rvs-mono);
2178
+ font-size: 10.5px;
2179
+ color: var(--rvs-fg3);
2180
+ }
2181
+
2182
+ /**
2183
+ * Every tab body stays mounted (the patch's scroll position and parse survive
2184
+ * a look at 提交详情) and is hidden with the `hidden` attribute. The class
2185
+ * declares `flex`, which outranks the UA `[hidden]` rule, so hiding needs an
2186
+ * explicit rule of its own.
2187
+ */
2188
+ .rvs-hbody {
2189
+ flex: 1;
2190
+ min-height: 0;
2191
+ overflow: auto;
2192
+ padding: 10px 12px 14px;
2193
+ }
2194
+
2195
+ .rvs-hbody[hidden] {
2196
+ display: none;
2197
+ }
2198
+
2199
+ /* -- 提交详情 -- */
2200
+
2201
+ .rvs-hsubject {
2202
+ font-size: 13px;
2203
+ font-weight: 600;
2204
+ line-height: 1.45;
2205
+ margin-bottom: 8px;
2206
+ white-space: pre-wrap;
2207
+ word-break: break-word;
2208
+ user-select: text;
2209
+ }
2210
+
2211
+ .rvs-hmeta {
2212
+ display: flex;
2213
+ align-items: center;
2214
+ gap: 6px;
2215
+ min-height: 21px;
2216
+ font-size: 11.5px;
2217
+ color: var(--rvs-fg3);
2218
+ }
2219
+
2220
+ .rvs-hmeta-icon {
2221
+ flex: none;
2222
+ display: flex;
2223
+ width: 13px;
2224
+ align-items: center;
2225
+ color: var(--rvs-fg3);
2226
+ }
2227
+
2228
+ .rvs-hmeta-value {
2229
+ min-width: 0;
2230
+ overflow: hidden;
2231
+ text-overflow: ellipsis;
2232
+ white-space: nowrap;
2233
+ }
2234
+
2235
+ .rvs-hmeta-label {
2236
+ flex: none;
2237
+ }
2238
+
2239
+ .rvs-hhash {
2240
+ font-family: var(--rvs-mono);
2241
+ font-size: 11px;
2242
+ line-height: 16px;
2243
+ padding: 0 5px;
2244
+ border: 1px solid var(--rvs-border);
2245
+ border-radius: 4px;
2246
+ background: var(--rvs-input);
2247
+ color: var(--rvs-fg2);
2248
+ user-select: all;
2249
+ }
2250
+
2251
+ .rvs-hparent {
2252
+ font-family: var(--rvs-mono);
2253
+ font-size: 11px;
2254
+ line-height: 16px;
2255
+ padding: 0 4px;
2256
+ border: 1px solid var(--rvs-border2);
2257
+ border-radius: 4px;
2258
+ color: var(--rvs-fg2);
2259
+ }
2260
+
2261
+ .rvs-hdash {
2262
+ color: var(--rvs-fg3);
2263
+ }
2264
+
2265
+ .rvs-hrefs {
2266
+ display: flex;
2267
+ flex-wrap: wrap;
2268
+ gap: 4px;
2269
+ margin-top: 6px;
2270
+ }
2271
+
2272
+ .rvs-hmsg {
2273
+ margin-top: 10px;
2274
+ padding-top: 8px;
2275
+ border-top: 1px dashed var(--rvs-border);
2276
+ font-size: 12px;
2277
+ line-height: 1.55;
2278
+ color: var(--rvs-fg2);
2279
+ white-space: pre-wrap;
2280
+ word-break: break-word;
2281
+ user-select: text;
2282
+ }
2283
+
2284
+ .rvs-hfooter {
2285
+ display: flex;
2286
+ gap: 6px;
2287
+ margin-top: 14px;
2288
+ }
2289
+
2290
+ /* -- 内容 -- */
2291
+
2292
+ .rvs-hhint {
2293
+ margin-bottom: 6px;
2294
+ font-size: 11px;
2295
+ color: var(--rvs-fg3);
2296
+ }
2297
+
2298
+ /* One line: how many files, and the path filter that makes a 60-file commit
2299
+ narrowable without scrolling. */
2300
+ .rvs-hcontent-head {
2301
+ display: flex;
2302
+ align-items: center;
2303
+ gap: 8px;
2304
+ margin-bottom: 6px;
2305
+ }
2306
+
2307
+ .rvs-hcount {
2308
+ flex: none;
2309
+ font-size: 11px;
2310
+ color: var(--rvs-fg3);
2311
+ }
2312
+
2313
+ .rvs-hfilter {
2314
+ flex: 1;
2315
+ min-width: 0;
2316
+ }
2317
+
2318
+ .rvs-hfiles {
2319
+ display: flex;
2320
+ flex-direction: column;
2321
+ gap: 1px;
2322
+ }
2323
+
2324
+ /* Directory row of the 内容 tree: chevron + folder glyph + path + count. */
2325
+ .rvs-hfolder {
2326
+ display: flex;
2327
+ align-items: center;
2328
+ gap: 4px;
2329
+ padding: 2px 4px;
2330
+ border-radius: 4px;
2331
+ color: var(--rvs-fg2);
2332
+ font-size: 12px;
2333
+ cursor: pointer;
2334
+ user-select: none;
2335
+ }
2336
+
2337
+ .rvs-hfolder:hover {
2338
+ background: var(--rvs-hover);
2339
+ }
2340
+
2341
+ .rvs-hfolder-name {
2342
+ flex: 1;
2343
+ min-width: 0;
2344
+ overflow: hidden;
2345
+ text-overflow: ellipsis;
2346
+ white-space: nowrap;
2347
+ font-family: var(--rvs-mono);
2348
+ font-size: 11.5px;
2349
+ }
2350
+
2351
+ .rvs-hfolder-count {
2352
+ flex: none;
2353
+ font-size: 10.5px;
2354
+ color: var(--rvs-fg3);
2355
+ }
2356
+
2357
+ /* Files under a folder keep the editor tree's indent idiom. */
2358
+ .rvs-file-row.indent {
2359
+ padding-left: 18px;
2360
+ }
2361
+
2362
+ .rvs-file-row.active {
2363
+ background: var(--rvs-active);
2364
+ }
2365
+
2366
+ /* The row's quick "open in editor" affordance: invisible until the row is
2367
+ hovered/focused, so the path keeps the whole width at rest. */
2368
+ .rvs-file-open {
2369
+ opacity: 0;
2370
+ }
2371
+
2372
+ .rvs-file-row:hover .rvs-file-open,
2373
+ .rvs-file-row:focus-within .rvs-file-open,
2374
+ .rvs-file-row:focus .rvs-file-open {
2375
+ opacity: 1;
2376
+ }
2377
+
2378
+ /* Keyboard focus must be visible on the pane's role=button rows (a bare
2379
+ browser default is nearly invisible on this dark chrome). */
2380
+ .rvs-file-row:focus-visible,
2381
+ .rvs-hfolder:focus-visible,
2382
+ .rvs-commit-item:focus-visible,
2383
+ .rvs-patch-chevron:focus-visible,
2384
+ .rvs-vsplit-handle:focus-visible {
2385
+ outline: 2px solid var(--rvs-brand);
2386
+ outline-offset: -2px;
2387
+ }
2388
+
2389
+ /* -- 差异 -- */
2390
+
2391
+ /**
2392
+ * The diff tab is a column: a PINNED tool row, then the one scroller. The
2393
+ * reference pane made the whole tab a scroll area, so the file you were in and
2394
+ * the controls both disappeared on a long patch.
2395
+ */
2396
+ .rvs-hdiff {
2397
+ display: flex;
2398
+ flex-direction: column;
2399
+ padding: 0;
2400
+ overflow: hidden;
2401
+ }
2402
+
2403
+ .rvs-hdiff[hidden] {
2404
+ display: none;
2405
+ }
2406
+
2407
+ .rvs-ptoolbar {
2408
+ flex: none;
2409
+ display: flex;
2410
+ align-items: center;
2411
+ gap: 3px;
2412
+ height: 30px;
2413
+ padding: 0 6px;
2414
+ border-bottom: 1px solid var(--rvs-border);
2415
+ background: var(--rvs-layer3);
2416
+ }
2417
+
2418
+ .rvs-pfile-index {
2419
+ flex: none;
2420
+ min-width: 32px;
2421
+ font-family: var(--rvs-mono);
2422
+ font-size: 10.5px;
2423
+ color: var(--rvs-fg3);
2424
+ }
2425
+
2426
+ .rvs-pfile-name {
2427
+ flex: 1;
2428
+ min-width: 0;
2429
+ overflow: hidden;
2430
+ text-overflow: ellipsis;
2431
+ white-space: nowrap;
2432
+ font-size: 11.5px;
2433
+ color: var(--rvs-fg2);
2434
+ }
2435
+
2436
+ .rvs-pscroll {
2437
+ flex: 1;
2438
+ min-height: 0;
2439
+ overflow: auto;
2440
+ padding: 10px 12px 14px;
2441
+ }
2442
+
2443
+ .rvs-patch-file {
2444
+ margin-bottom: 10px;
2445
+ border: 1px solid var(--rvs-border);
2446
+ border-radius: 6px;
2447
+ }
2448
+
2449
+ .rvs-patch-file.folded .rvs-patch-head {
2450
+ border-bottom: none;
2451
+ border-radius: 6px;
2452
+ }
2453
+
2454
+ .rvs-patch-file.active {
2455
+ border-color: var(--rvs-brand);
2456
+ }
2457
+
2458
+ /* Sticky per-file header: scrolling a 3000-line hunk keeps saying which file
2459
+ it belongs to. `position: sticky` needs the nearest scrollport, which is
2460
+ `.rvs-pscroll` — no `overflow` on the section itself. */
2461
+ .rvs-patch-head {
2462
+ position: sticky;
2463
+ top: 0;
2464
+ z-index: 1;
2465
+ display: flex;
2466
+ align-items: center;
2467
+ gap: 6px;
2468
+ padding: 3px 6px;
2469
+ border-bottom: 1px solid var(--rvs-border);
2470
+ border-radius: 6px 6px 0 0;
2471
+ background: var(--rvs-layer3);
2472
+ cursor: pointer;
2473
+ }
2474
+
2475
+ .rvs-patch-head:hover {
2476
+ background: var(--rvs-hover);
2477
+ }
2478
+
2479
+ .rvs-patch-chevron {
2480
+ flex: none;
2481
+ display: flex;
2482
+ align-items: center;
2483
+ color: var(--rvs-fg3);
2484
+ cursor: pointer;
2485
+ transform: rotate(0deg);
2486
+ }
2487
+
2488
+ .rvs-patch-chevron.open {
2489
+ transform: rotate(90deg);
2490
+ }
2491
+
2492
+ .rvs-patch-path {
2493
+ flex: 1;
2494
+ min-width: 0;
2495
+ overflow: hidden;
2496
+ text-overflow: ellipsis;
2497
+ white-space: nowrap;
2498
+ font-size: 12px;
2499
+ }
2500
+
2501
+ .rvs-patch-letter {
2502
+ flex: none;
2503
+ font-size: 11px;
2504
+ font-weight: 700;
2505
+ }
2506
+
2507
+ .rvs-patch-counts {
2508
+ flex: none;
2509
+ display: flex;
2510
+ gap: 5px;
2511
+ font-family: var(--rvs-mono);
2512
+ font-size: 10.5px;
2513
+ }
2514
+
2515
+ .rvs-patch-add-count {
2516
+ color: var(--rvs-git-a);
2517
+ }
2518
+
2519
+ .rvs-patch-del-count {
2520
+ color: var(--rvs-git-d);
2521
+ }
2522
+
2523
+ .rvs-patch-rows {
2524
+ padding: 2px 0 6px;
2525
+ font-family: var(--rvs-mono);
2526
+ font-size: 11.5px;
2527
+ line-height: 17px;
2528
+ }
2529
+
2530
+ .rvs-patch-row {
2531
+ display: flex;
2532
+ align-items: stretch;
2533
+ min-width: max-content;
2534
+ white-space: pre;
2535
+ }
2536
+
2537
+ /* Wrap mode: the row gives up its horizontal runway and the text wraps under
2538
+ the gutter instead (the whole point on a narrow sidebar). */
2539
+ .rvs-patch-rows.wrap .rvs-patch-row {
2540
+ min-width: 0;
2541
+ white-space: pre-wrap;
2542
+ word-break: break-word;
2543
+ }
2544
+
2545
+ .rvs-patch-rows.wrap .rvs-patch-text {
2546
+ white-space: pre-wrap;
2547
+ word-break: break-word;
2548
+ }
2549
+
2550
+ /* A block whose raw patch text could not be paired with the parsed model
2551
+ (defensive: block count and model count disagreed) is dimmed instead of
2552
+ silently looking complete. */
2553
+ .rvs-patch-rows.partial {
2554
+ opacity: .62;
2555
+ }
2556
+
2557
+ /* Truncation marker in the pinned toolbar: the cut point can be thousands of
2558
+ rows away, so the bottom note is not where a reader learns about it. */
2559
+ .rvs-ptruncated {
2560
+ flex: none;
2561
+ font-size: 12px;
2562
+ color: var(--rvs-git-m);
2563
+ }
2564
+
2565
+ .rvs-patch-row.add {
2566
+ background: var(--rvs-add-bg);
2567
+ }
2568
+
2569
+ .rvs-patch-row.del {
2570
+ background: var(--rvs-del-bg);
2571
+ }
2572
+
2573
+ .rvs-patch-num {
2574
+ flex: none;
2575
+ width: 42px;
2576
+ padding-right: 6px;
2577
+ text-align: right;
2578
+ color: var(--rvs-fg3);
2579
+ user-select: none;
2580
+ }
2581
+
2582
+ .rvs-patch-sign {
2583
+ flex: none;
2584
+ width: 14px;
2585
+ text-align: center;
2586
+ color: var(--rvs-fg3);
2587
+ user-select: none;
2588
+ }
2589
+
2590
+ .rvs-patch-text {
2591
+ flex: 1;
2592
+ padding-right: 12px;
2593
+ user-select: text;
2594
+ }
2595
+
2596
+ /* One "the hunk jumped ahead" marker, so a patch never pretends its line
2597
+ numbers are contiguous. */
2598
+ .rvs-patch-skip {
2599
+ margin: 2px 0;
2600
+ border-top: 1px dashed var(--rvs-border);
2601
+ border-bottom: 1px dashed var(--rvs-border);
2602
+ color: var(--rvs-fg3);
2603
+ font-size: 11px;
2604
+ text-align: center;
2605
+ user-select: none;
2606
+ }
2607
+
2608
+ .rvs-patch-note {
2609
+ padding: 6px 8px;
2610
+ font-size: 11px;
2611
+ color: var(--rvs-fg3);
2612
+ }
2613
+
2078
2614
  /* ---------------------------------------------------- floating editor window */
2079
2615
 
2080
2616
  /**
@@ -2957,3 +3493,114 @@ body:not([data-ds-dark-theme]) .rvs-undo-affordance:focus-visible {
2957
3493
  color: var(--rvs-error, #f85149);
2958
3494
  font-size: 11.5px;
2959
3495
  }
3496
+
3497
+ /* --------------------------------------------------- streaming exec panel */
3498
+
3499
+ /**
3500
+ * Live log for the network verbs (fetch / pull / push / sync).
3501
+ *
3502
+ * Fixed to the viewport's bottom-right instead of joining the panel's own
3503
+ * column: the pane can be collapsed or scrolled away while the push runs, and a
3504
+ * dialog that vanishes with its parent would hide the cancel button exactly
3505
+ * when it matters.
3506
+ */
3507
+ .rvs-execpanel {
3508
+ /* PORTALED to <body>, i.e. OUTSIDE `.rvs-root` where these tokens are
3509
+ declared — without this block every var() below resolves to nothing and the
3510
+ panel renders transparent and borderless over the page content. Keep this
3511
+ subset in sync with `.rvs-root`; the dark code surfaces get their own
3512
+ `body[data-ds-dark-theme]` override right after this rule. */
3513
+ --rvs-fg: var(--dsw-alias-label-primary, #e6e6e6);
3514
+ --rvs-fg2: var(--dsw-alias-label-secondary, #b9b9b9);
3515
+ --rvs-fg3: var(--dsw-alias-label-tertiary, #8b8b8b);
3516
+ --rvs-hover: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .14));
3517
+ --rvs-border: var(--dsw-alias-border-l1, rgba(127, 127, 127, .25));
3518
+ --rvs-border2: var(--dsw-alias-border-l2, rgba(127, 127, 127, .38));
3519
+ --rvs-overlay: var(--dsw-alias-bg-overlay, #24262e);
3520
+ --rvs-brand: var(--dsw-alias-brand-primary, #4d6bfe);
3521
+ --rvs-danger: var(--dsw-alias-state-error-primary, #e05555);
3522
+ --rvs-git-m: #e2b714;
3523
+ --rvs-git-a: #4ec9b0;
3524
+ --rvs-mono: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', Consolas, 'Courier New', monospace;
3525
+ --rvs-code-bg: #1e1e1e;
3526
+ --rvs-code-fg: #d4d4d4;
3527
+ position: fixed;
3528
+ right: 16px;
3529
+ bottom: 16px;
3530
+ z-index: 45;
3531
+ width: min(620px, calc(100vw - 32px));
3532
+ max-height: min(60vh, 520px);
3533
+ display: flex;
3534
+ flex-direction: column;
3535
+ overflow: hidden;
3536
+ border: 1px solid var(--rvs-border2);
3537
+ border-radius: 10px;
3538
+ background: var(--rvs-overlay);
3539
+ color: var(--rvs-fg);
3540
+ box-shadow: 0 18px 60px rgba(0, 0, 0, .45), 0 2px 8px rgba(0, 0, 0, .28);
3541
+ }
3542
+
3543
+ body[data-ds-dark-theme] .rvs-execpanel {
3544
+ --rvs-code-bg: #1e1e1e;
3545
+ --rvs-code-fg: #d4d4d4;
3546
+ }
3547
+
3548
+
3549
+ .rvs-execpanel-head {
3550
+ flex: none;
3551
+ display: flex;
3552
+ align-items: center;
3553
+ gap: 6px;
3554
+ padding: 5px 6px 5px 10px;
3555
+ border-bottom: 1px solid var(--rvs-border);
3556
+ }
3557
+
3558
+ .rvs-execpanel-title {
3559
+ min-width: 0;
3560
+ overflow: hidden;
3561
+ text-overflow: ellipsis;
3562
+ white-space: nowrap;
3563
+ font-size: 12px;
3564
+ font-weight: 600;
3565
+ }
3566
+
3567
+ .rvs-execpanel-status {
3568
+ flex: none;
3569
+ font-size: 11px;
3570
+ color: var(--rvs-fg3);
3571
+ font-variant-numeric: tabular-nums;
3572
+ }
3573
+
3574
+ .rvs-execpanel-status.done { color: var(--rvs-git-a); }
3575
+ .rvs-execpanel-status.error { color: var(--rvs-danger); }
3576
+ .rvs-execpanel-status.cancelled { color: var(--rvs-git-m); }
3577
+
3578
+ .rvs-execpanel-spacer {
3579
+ flex: 1;
3580
+ min-width: 0;
3581
+ }
3582
+
3583
+ .rvs-execpanel-body {
3584
+ flex: 1;
3585
+ min-height: 60px;
3586
+ margin: 0;
3587
+ padding: 8px 10px;
3588
+ overflow: auto;
3589
+ background: var(--rvs-code-bg);
3590
+ color: var(--rvs-code-fg);
3591
+ font-family: var(--rvs-mono);
3592
+ font-size: 11.5px;
3593
+ line-height: 1.45;
3594
+ white-space: pre-wrap;
3595
+ word-break: break-word;
3596
+ user-select: text;
3597
+ }
3598
+
3599
+ .rvs-execpanel-foot {
3600
+ flex: none;
3601
+ padding: 4px 10px 6px;
3602
+ border-top: 1px solid var(--rvs-border);
3603
+ color: var(--rvs-fg3);
3604
+ font-size: 11px;
3605
+ }
3606
+
package/src/git-format.ts CHANGED
@@ -196,10 +196,14 @@ export function parseBranches(stdout: string, current: string): GitBranch[] {
196
196
  return branches
197
197
  }
198
198
 
199
+ /** Split a raw `%B` message into its subject line and the body below it. */
200
+ export function parseMessage(text: string): { subject: string; body: string } {
201
+ const lines = text.replace(/\r\n/g, '\n').split('\n')
202
+ return { subject: (lines[0] ?? '').trim(), body: lines.slice(1).join('\n').trim() }
203
+ }
204
+
199
205
  /** Assemble commit detail from message + stat outputs. */
200
206
  export function parseShow(hash: string, message: string, stat: string): GitShow {
201
- const lines = message.split('\n')
202
- const subject = (lines[0] ?? '').trim()
203
- const body = lines.slice(1).join('\n').trim()
207
+ const { subject, body } = parseMessage(message)
204
208
  return { hash, subject, body, stat: stat.trim() }
205
209
  }