dsh-ide-git 0.14.1 → 0.14.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/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-ide-git",
3
- "version": "0.14.1",
3
+ "version": "0.14.3",
4
4
  "main": "./src/index.js",
5
5
  "description": "IDE 级 Git 操作面板:分支树 + 提交图谱 + 变更/暂存/提交 + 提交详情。装了 dsh-better-sidebar 时挂成它的原生 tab(右侧栏 + 底部工作台);没装时直接挂 DSH 原生右侧栏,单独安装即可用。JetBrains 风格 Git 工具窗口 for DeepSeek Harness.",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ide-git",
3
- "version": "0.14.1",
3
+ "version": "0.14.3",
4
4
  "description": "DSH plugin: an IDE-grade Git tool window as a native dsh-better-sidebar tab — branch tree, commit graph, changes, commit details, JetBrains-style actions, in the right sidebar and the bottom panel. | DSH 插件:IDE 级 Git 工具窗口,以 dsh-better-sidebar 原生 Tab 挂载——分支树 / 提交图谱 / 变更与提交详情 / JetBrains 风格操作,右侧栏与底部面板双布局自适应。",
5
5
  "type": "module",
6
6
  "repository": {
package/src/client.js CHANGED
@@ -40,6 +40,11 @@ window.__ModuleLoader__.load({
40
40
  of throwing at load time. Every consumer checks for null first. */
41
41
  let UIPrimitives = null
42
42
  try { UIPrimitives = require('@deepseek-ai/dsh-client-ui-primitives') } catch (error) { void error }
43
+ /* The shared lazy Shiki highlighter, bound once (v0.14.1): FileDiff in
44
+ ui-deliverables rides this same hook for its line highlighting. A stable
45
+ module-level reference keeps the LineDiff hook call unconditional. */
46
+ const useCodeHighlighterSeed = UIPrimitives !== null && typeof UIPrimitives.useCodeHighlighter === 'function' ? UIPrimitives.useCodeHighlighter : null
47
+ const languageForPathSeed = UIPrimitives !== null && typeof UIPrimitives.languageForPath === 'function' ? UIPrimitives.languageForPath : null
43
48
 
44
49
  /* API base is MOUNT-RELATIVE (no leading slash). dsh 0.1.7 serves the
45
50
  shell with <base href="./">, so document.baseURI is the mount the page
@@ -197,6 +202,16 @@ window.__ModuleLoader__.load({
197
202
  'settings.placement.immersive': '沉浸模式(占满全高,隐藏输入框)',
198
203
  'settings.placement.immersiveHint': '开启后 Git 标签页占满整个会话区高度,输入框自动收起;审批与提问仍会正常显示。',
199
204
  'preview.openFile': '打开文件预览',
205
+ 'lf.added': '+{count}',
206
+ 'lf.deleted': '-{count}',
207
+ 'lf.selectFile': '选择要查看的文件',
208
+ 'lf.split': '切换为左右对比',
209
+ 'lf.unified': '切换为单栏对比',
210
+ 'lf.wrap': '开启自动换行',
211
+ 'lf.nowrap': '关闭自动换行',
212
+ 'lf.created': '新建的文件',
213
+ 'lf.removedFile': '删除的文件',
214
+ 'lf.truncated': '只显示前 {count} 行',
200
215
  'preview.title': 'Git 预览',
201
216
  'preview.hint': '点击文件或提交的「显示差异」,差异会在侧边栏这里打开。',
202
217
  'settings.rail.title': '动作条按钮',
@@ -412,6 +427,16 @@ window.__ModuleLoader__.load({
412
427
  'settings.placement.immersive': 'Immersive mode (full height, hidden composer)',
413
428
  'settings.placement.immersiveHint': 'On, the Git tab fills the whole conversation area and the composer steps aside; approvals and questions still show.',
414
429
  'preview.openFile': 'Open in the file preview',
430
+ 'lf.added': '+{count}',
431
+ 'lf.deleted': '-{count}',
432
+ 'lf.selectFile': 'Choose the file to review',
433
+ 'lf.split': 'Switch to split view',
434
+ 'lf.unified': 'Switch to unified view',
435
+ 'lf.wrap': 'Enable line wrap',
436
+ 'lf.nowrap': 'Disable line wrap',
437
+ 'lf.created': 'Created file',
438
+ 'lf.removedFile': 'Deleted file',
439
+ 'lf.truncated': 'Showing the first {count} lines',
415
440
  'preview.title': 'Git Preview',
416
441
  'preview.hint': 'Click “Show diff” on a file or commit — the diff opens here in the sidebar.',
417
442
  'settings.rail.title': 'Action rail buttons',
@@ -635,6 +660,16 @@ window.__ModuleLoader__.load({
635
660
  'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
636
661
  'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個會話區高度,輸入框自動收起;審批與提問仍會正常顯示。',
637
662
  'preview.openFile': '開啟檔案預覽',
663
+ 'lf.added': '+{count}',
664
+ 'lf.deleted': '-{count}',
665
+ 'lf.selectFile': '選擇要查看嘅檔案',
666
+ 'lf.split': '切換為左右對比',
667
+ 'lf.unified': '切換為單欄對比',
668
+ 'lf.wrap': '開啟自動換行',
669
+ 'lf.nowrap': '關閉自動換行',
670
+ 'lf.created': '新建嘅檔案',
671
+ 'lf.removedFile': '刪除嘅檔案',
672
+ 'lf.truncated': '只顯示前 {count} 行',
638
673
  'preview.title': 'Git 預覽',
639
674
  'preview.hint': '喺檔案或提交撳「顯示差異」,差異就會喺側邊欄度開啟。',
640
675
  'settings.rail.title': '動作列按鈕',
@@ -850,6 +885,16 @@ window.__ModuleLoader__.load({
850
885
  'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
851
886
  'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個對話區高度,輸入框自動收起;審核與提問仍會正常顯示。',
852
887
  'preview.openFile': '開啟檔案預覽',
888
+ 'lf.added': '+{count}',
889
+ 'lf.deleted': '-{count}',
890
+ 'lf.selectFile': '選擇要查看的檔案',
891
+ 'lf.split': '切換為左右對比',
892
+ 'lf.unified': '切換為單欄對比',
893
+ 'lf.wrap': '開啟自動換行',
894
+ 'lf.nowrap': '關閉自動換行',
895
+ 'lf.created': '新建的檔案',
896
+ 'lf.removedFile': '刪除的檔案',
897
+ 'lf.truncated': '只顯示前 {count} 行',
853
898
  'preview.title': 'Git 預覽',
854
899
  'preview.hint': '點選檔案或提交的「顯示差異」,差異就會在側邊欄開啟。',
855
900
  'settings.rail.title': '動作列按鈕',
@@ -1065,6 +1110,16 @@ window.__ModuleLoader__.load({
1065
1110
  'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
1066
1111
  'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個會話區高度,輸入框自動收起;審批與提問仍會正常顯示。',
1067
1112
  'preview.openFile': '開啟檔案預覽',
1113
+ 'lf.added': '+{count}',
1114
+ 'lf.deleted': '-{count}',
1115
+ 'lf.selectFile': '選擇要查看嘅檔案',
1116
+ 'lf.split': '切換為左右對比',
1117
+ 'lf.unified': '切換為單欄對比',
1118
+ 'lf.wrap': '開啟自動換行',
1119
+ 'lf.nowrap': '關閉自動換行',
1120
+ 'lf.created': '新建嘅檔案',
1121
+ 'lf.removedFile': '刪除嘅檔案',
1122
+ 'lf.truncated': '只顯示前 {count} 行',
1068
1123
  'preview.title': 'Git 預覽',
1069
1124
  'preview.hint': '喺檔案或提交撳「顯示差異」,差異就會喺側邊欄度開啟。',
1070
1125
  'settings.rail.title': '動作列按鈕',
@@ -1280,6 +1335,16 @@ window.__ModuleLoader__.load({
1280
1335
  'settings.placement.immersive': '没入モード(全高表示・入力欄を非表示)',
1281
1336
  'settings.placement.immersiveHint': 'オンにすると Git タブが会話エリアの高さいっぱいに広がり、入力欄は引っ込みます。承認や質問は通常どおり表示されます。',
1282
1337
  'preview.openFile': 'ファイルプレビューを開く',
1338
+ 'lf.added': '+{count}',
1339
+ 'lf.deleted': '-{count}',
1340
+ 'lf.selectFile': 'レビューするファイルを選択',
1341
+ 'lf.split': '左右分割に切り替える',
1342
+ 'lf.unified': '単欄に切り替える',
1343
+ 'lf.wrap': '自動折り返しをオンにする',
1344
+ 'lf.nowrap': '自動折り返しをオフにする',
1345
+ 'lf.created': '新規作成されたファイル',
1346
+ 'lf.removedFile': '削除されたファイル',
1347
+ 'lf.truncated': '先頭 {count} 行のみ表示',
1283
1348
  'preview.title': 'Git プレビュー',
1284
1349
  'preview.hint': 'ファイルやコミットの「差分を表示」をクリックすると、差分がここに開きます。',
1285
1350
  'settings.rail.title': 'アクションレールのボタン',
@@ -1495,6 +1560,16 @@ window.__ModuleLoader__.load({
1495
1560
  'settings.placement.immersive': '몰입 모드(전체 높이, 입력창 숨김)',
1496
1561
  'settings.placement.immersiveHint': '켜면 Git 탭이 대화 영역 전체 높이를 차지하고 입력창은 숨겨집니다. 승인과 질문은 정상적으로 표시됩니다.',
1497
1562
  'preview.openFile': '파일 미리보기로 열기',
1563
+ 'lf.added': '+{count}',
1564
+ 'lf.deleted': '-{count}',
1565
+ 'lf.selectFile': '검토할 파일 선택',
1566
+ 'lf.split': '좌우 분할 보기로 전환',
1567
+ 'lf.unified': '단일 보기로 전환',
1568
+ 'lf.wrap': '자동 줄바꿈 켜기',
1569
+ 'lf.nowrap': '자동 줄바꿈 끄기',
1570
+ 'lf.created': '새로 만든 파일',
1571
+ 'lf.removedFile': '삭제된 파일',
1572
+ 'lf.truncated': '처음 {count}줄만 표시',
1498
1573
  'preview.title': 'Git 미리보기',
1499
1574
  'preview.hint': '파일이나 커밋의 「차이 보기」를 클릭하면 차이가 사이드바 여기에 열립니다.',
1500
1575
  'settings.rail.title': '액션 레일 버튼',
@@ -1710,6 +1785,16 @@ window.__ModuleLoader__.load({
1710
1785
  'settings.placement.immersive': 'Immersiver Modus (volle Höhe, ausgeblendetes Eingabefeld)',
1711
1786
  'settings.placement.immersiveHint': 'Eingeschaltet füllt der Git-Tab die gesamte Gesprächshöhe und das Eingabefeld weicht zurück; Freigaben und Rückfragen bleiben sichtbar.',
1712
1787
  'preview.openFile': 'In der Dateivorschau öffnen',
1788
+ 'lf.added': '+{count}',
1789
+ 'lf.deleted': '-{count}',
1790
+ 'lf.selectFile': 'Zu prüfende Datei wählen',
1791
+ 'lf.split': 'Zur geteilten Ansicht wechseln',
1792
+ 'lf.unified': 'Zur gemeinsamen Ansicht wechseln',
1793
+ 'lf.wrap': 'Zeilenumbruch aktivieren',
1794
+ 'lf.nowrap': 'Zeilenumbruch deaktivieren',
1795
+ 'lf.created': 'Neue Datei',
1796
+ 'lf.removedFile': 'Gelöschte Datei',
1797
+ 'lf.truncated': 'Nur die ersten {count} Zeilen',
1713
1798
  'preview.title': 'Git-Vorschau',
1714
1799
  'preview.hint': '„Diff anzeigen“ an einer Datei oder einem Commit öffnet den Diff hier in der Seitenleiste.',
1715
1800
  'settings.rail.title': 'Schaltflächen der Aktionsleiste',
@@ -1925,6 +2010,16 @@ window.__ModuleLoader__.load({
1925
2010
  'settings.placement.immersive': 'Mode immersif (pleine hauteur, zone de saisie masquée)',
1926
2011
  'settings.placement.immersiveHint': 'Activé, l’onglet Git occupe toute la hauteur de la conversation et la zone de saisie s’efface ; approbations et questions restent visibles.',
1927
2012
  'preview.openFile': 'Ouvrir dans l’aperçu du fichier',
2013
+ 'lf.added': '+{count}',
2014
+ 'lf.deleted': '-{count}',
2015
+ 'lf.selectFile': 'Choisir le fichier à examiner',
2016
+ 'lf.split': 'Passer à la vue scindée',
2017
+ 'lf.unified': 'Passer à la vue unifiée',
2018
+ 'lf.wrap': 'Activer le retour à la ligne',
2019
+ 'lf.nowrap': 'Désactiver le retour à la ligne',
2020
+ 'lf.created': 'Fichier créé',
2021
+ 'lf.removedFile': 'Fichier supprimé',
2022
+ 'lf.truncated': 'Affichage des {count} premières lignes',
1928
2023
  'preview.title': 'Aperçu Git',
1929
2024
  'preview.hint': 'Cliquez sur « Afficher le diff » sur un fichier ou un commit : le diff s’ouvre ici, dans la barre latérale.',
1930
2025
  'settings.rail.title': 'Boutons de la barre d’actions',
@@ -2140,6 +2235,16 @@ window.__ModuleLoader__.load({
2140
2235
  'settings.placement.immersive': 'Полноэкранный режим (во всю высоту, без поля ввода)',
2141
2236
  'settings.placement.immersiveHint': 'Во включённом виде вкладка Git занимает всю высоту области диалога, поле ввода скрывается; подтверждения и вопросы остаются на виду.',
2142
2237
  'preview.openFile': 'Открыть в предпросмотре файла',
2238
+ 'lf.added': '+{count}',
2239
+ 'lf.deleted': '-{count}',
2240
+ 'lf.selectFile': 'Выберите файл для просмотра',
2241
+ 'lf.split': 'Переключить на двухпанельный вид',
2242
+ 'lf.unified': 'Переключить на однопанельный вид',
2243
+ 'lf.wrap': 'Включить перенос строк',
2244
+ 'lf.nowrap': 'Отключить перенос строк',
2245
+ 'lf.created': 'Новый файл',
2246
+ 'lf.removedFile': 'Удалённый файл',
2247
+ 'lf.truncated': 'Показаны первые {count} строк',
2143
2248
  'preview.title': 'Git-предпросмотр',
2144
2249
  'preview.hint': 'Нажмите «Показать различия» у файла или коммита — различия откроются здесь, в боковой панели.',
2145
2250
  'settings.rail.title': 'Кнопки панели действий',
@@ -2355,6 +2460,16 @@ window.__ModuleLoader__.load({
2355
2460
  'settings.placement.immersive': 'Modo imersivo (altura total, campo de entrada oculto)',
2356
2461
  'settings.placement.immersiveHint': 'Ativado, a aba Git ocupa toda a altura da conversa e o campo de entrada fica de lado; aprovações e perguntas continuam visíveis.',
2357
2462
  'preview.openFile': 'Abrir na pré-visualização do arquivo',
2463
+ 'lf.added': '+{count}',
2464
+ 'lf.deleted': '-{count}',
2465
+ 'lf.selectFile': 'Escolher o arquivo para revisar',
2466
+ 'lf.split': 'Alternar para a vista dividida',
2467
+ 'lf.unified': 'Alternar para a vista unificada',
2468
+ 'lf.wrap': 'Ativar quebra de linha',
2469
+ 'lf.nowrap': 'Desativar quebra de linha',
2470
+ 'lf.created': 'Arquivo criado',
2471
+ 'lf.removedFile': 'Arquivo excluído',
2472
+ 'lf.truncated': 'Mostrando as primeiras {count} linhas',
2358
2473
  'preview.title': 'Pré-visualização do Git',
2359
2474
  'preview.hint': 'Clique em “Mostrar diff” num arquivo ou commit — o diff abre aqui, na barra lateral.',
2360
2475
  'settings.rail.title': 'Botões da barra de ações',
@@ -2570,6 +2685,16 @@ window.__ModuleLoader__.load({
2570
2685
  'settings.placement.immersive': 'Modalità immersiva (altezza piena, input nascosto)',
2571
2686
  'settings.placement.immersiveHint': 'Attiva, la scheda Git occupa tutta l’altezza della conversazione e la barra di input resta nascosta; approvazioni e domande restano visibili.',
2572
2687
  'preview.openFile': 'Apri nell’anteprima del file',
2688
+ 'lf.added': '+{count}',
2689
+ 'lf.deleted': '-{count}',
2690
+ 'lf.selectFile': 'Scegli il file da esaminare',
2691
+ 'lf.split': 'Passa alla vista affiancata',
2692
+ 'lf.unified': 'Passa alla vista unificata',
2693
+ 'lf.wrap': 'Attiva il ritorno a capo',
2694
+ 'lf.nowrap': 'Disattiva il ritorno a capo',
2695
+ 'lf.created': 'File creato',
2696
+ 'lf.removedFile': 'File eliminato',
2697
+ 'lf.truncated': 'Mostrate le prime {count} righe',
2573
2698
  'preview.title': 'Anteprima Git',
2574
2699
  'preview.hint': 'Fai clic su «Mostra diff» su un file o un commit: il diff si apre qui, nella barra laterale.',
2575
2700
  'settings.rail.title': 'Pulsanti della barra azioni',
@@ -2785,6 +2910,16 @@ window.__ModuleLoader__.load({
2785
2910
  'settings.placement.immersive': 'Meeslepende modus (volledige hoogte, verborgen invoerveld)',
2786
2911
  'settings.placement.immersiveHint': 'Aan: het Git-tabblad vult de hele gespreksruimte en het invoerveld wijkt; goedkeuringen en vragen blijven zichtbaar.',
2787
2912
  'preview.openFile': 'Openen in de bestandsvoorbeeld',
2913
+ 'lf.added': '+{count}',
2914
+ 'lf.deleted': '-{count}',
2915
+ 'lf.selectFile': 'Kies het te bekijken bestand',
2916
+ 'lf.split': 'Overschakelen naar gesplitste weergave',
2917
+ 'lf.unified': 'Overschakelen naar gecombineerde weergave',
2918
+ 'lf.wrap': 'Regelterugloop inschakelen',
2919
+ 'lf.nowrap': 'Regelterugloop uitschakelen',
2920
+ 'lf.created': 'Nieuw bestand',
2921
+ 'lf.removedFile': 'Verwijderd bestand',
2922
+ 'lf.truncated': 'Eerste {count} regels worden weergegeven',
2788
2923
  'preview.title': 'Git-voorbeeld',
2789
2924
  'preview.hint': 'Klik op „Diff tonen“ bij een bestand of commit — de diff opent hier in de zijbalk.',
2790
2925
  'settings.rail.title': 'Knoppen van de actiebalk',
@@ -3000,6 +3135,16 @@ window.__ModuleLoader__.load({
3000
3135
  'settings.placement.immersive': 'Tryb immersyjny (pełna wysokość, ukryte pole wpisywania)',
3001
3136
  'settings.placement.immersiveHint': 'Po włączeniu karta Git wypełnia całą wysokość rozmowy, a pole wpisywania się chowa; zatwierdzenia i pytania pozostają widoczne.',
3002
3137
  'preview.openFile': 'Otwórz w podglądzie pliku',
3138
+ 'lf.added': '+{count}',
3139
+ 'lf.deleted': '-{count}',
3140
+ 'lf.selectFile': 'Wybierz plik do przeglądu',
3141
+ 'lf.split': 'Przełącz na widok dzielony',
3142
+ 'lf.unified': 'Przełącz na widok połączony',
3143
+ 'lf.wrap': 'Włącz zawijanie wierszy',
3144
+ 'lf.nowrap': 'Wyłącz zawijanie wierszy',
3145
+ 'lf.created': 'Nowy plik',
3146
+ 'lf.removedFile': 'Usunięty plik',
3147
+ 'lf.truncated': 'Pokazano pierwsze {count} wierszy',
3003
3148
  'preview.title': 'Podgląd Git',
3004
3149
  'preview.hint': 'Kliknij „Pokaż różnice” przy pliku lub commicie — różnice otworzą się tutaj, na pasku bocznym.',
3005
3150
  'settings.rail.title': 'Przyciski paska akcji',
@@ -3215,6 +3360,16 @@ window.__ModuleLoader__.load({
3215
3360
  'settings.placement.immersive': 'Uppslukande läge (full höjd, dolt inmatningsfält)',
3216
3361
  'settings.placement.immersiveHint': 'På: Git-fliken fyller hela konversationsytan och inmatningsfältet träder åt sidan; godkännanden och frågor visas fortfarande.',
3217
3362
  'preview.openFile': 'Öppna i filförhandsvisningen',
3363
+ 'lf.added': '+{count}',
3364
+ 'lf.deleted': '-{count}',
3365
+ 'lf.selectFile': 'Välj filen att granska',
3366
+ 'lf.split': 'Byt till delad vy',
3367
+ 'lf.unified': 'Byt till samlad vy',
3368
+ 'lf.wrap': 'Aktivera radbrytning',
3369
+ 'lf.nowrap': 'Inaktivera radbrytning',
3370
+ 'lf.created': 'Ny fil',
3371
+ 'lf.removedFile': 'Borttagen fil',
3372
+ 'lf.truncated': 'Visar de första {count} raderna',
3218
3373
  'preview.title': 'Git-förhandsvisning',
3219
3374
  'preview.hint': 'Klicka på ”Visa diff” på en fil eller en commit — diffen öppnas här i sidopanelen.',
3220
3375
  'settings.rail.title': 'Knappar i åtgärdsfältet',
@@ -3430,6 +3585,16 @@ window.__ModuleLoader__.load({
3430
3585
  'settings.placement.immersive': 'Odaklanma modu (tam yükseklik, gizli giriş alanı)',
3431
3586
  'settings.placement.immersiveHint': 'Açıkken Git sekmesi tüm konuşma alanını kaplar ve giriş alanı gizlenir; onaylar ve sorular yine de görünür.',
3432
3587
  'preview.openFile': 'Dosya önizlemesinde aç',
3588
+ 'lf.added': '+{count}',
3589
+ 'lf.deleted': '-{count}',
3590
+ 'lf.selectFile': 'İncelenecek dosyayı seçin',
3591
+ 'lf.split': 'Bölünmüş görünüme geç',
3592
+ 'lf.unified': 'Birleşik görünüme geç',
3593
+ 'lf.wrap': 'Satır kaydırmayı aç',
3594
+ 'lf.nowrap': 'Satır kaydırmayı kapat',
3595
+ 'lf.created': 'Yeni dosya',
3596
+ 'lf.removedFile': 'Silinen dosya',
3597
+ 'lf.truncated': 'Yalnızca ilk {count} satır gösteriliyor',
3433
3598
  'preview.title': 'Git Önizlemesi',
3434
3599
  'preview.hint': 'Bir dosya veya commit üzerinde „Farkı göster”e tıklayın — fark burada, kenar çubuğunda açılır.',
3435
3600
  'settings.rail.title': 'Eylem çubuğu düğmeleri',
@@ -3645,6 +3810,16 @@ window.__ModuleLoader__.load({
3645
3810
  'settings.placement.immersive': 'Mode imersif (tinggi penuh, kolom masukan tersembunyi)',
3646
3811
  'settings.placement.immersiveHint': 'Aktif: tab Git memenuhi seluruh tinggi area percakapan dan kolom masukan mengalah; persetujuan dan pertanyaan tetap tampil.',
3647
3812
  'preview.openFile': 'Buka di pratinjau berkas',
3813
+ 'lf.added': '+{count}',
3814
+ 'lf.deleted': '-{count}',
3815
+ 'lf.selectFile': 'Pilih berkas untuk ditinjau',
3816
+ 'lf.split': 'Ganti ke tampilan terbagi',
3817
+ 'lf.unified': 'Ganti ke tampilan tunggal',
3818
+ 'lf.wrap': 'Aktifkan pelipatan baris',
3819
+ 'lf.nowrap': 'Nonaktifkan pelipatan baris',
3820
+ 'lf.created': 'Berkas baru',
3821
+ 'lf.removedFile': 'Berkas dihapus',
3822
+ 'lf.truncated': 'Menampilkan {count} baris pertama',
3648
3823
  'preview.title': 'Pratinjau Git',
3649
3824
  'preview.hint': 'Klik “Tampilkan diff” pada file atau commit — diff terbuka di sini, di bilah sisi.',
3650
3825
  'settings.rail.title': 'Tombol bilah aksi',
@@ -3860,6 +4035,16 @@ window.__ModuleLoader__.load({
3860
4035
  'settings.placement.immersive': 'Chế độ đắm chìm (chiều cao đầy đủ, ẩn ô nhập)',
3861
4036
  'settings.placement.immersiveHint': 'Bật: thẻ Git chiếm toàn bộ chiều cao vùng hội thoại và ô nhập thu vào; phê duyệt và câu hỏi vẫn hiển thị bình thường.',
3862
4037
  'preview.openFile': 'Mở trong xem trước tệp',
4038
+ 'lf.added': '+{count}',
4039
+ 'lf.deleted': '-{count}',
4040
+ 'lf.selectFile': 'Chọn tệp để xem',
4041
+ 'lf.split': 'Chuyển sang xem đôi',
4042
+ 'lf.unified': 'Chuyển sang xem đơn',
4043
+ 'lf.wrap': 'Bật tự động xuống dòng',
4044
+ 'lf.nowrap': 'Tắt tự động xuống dòng',
4045
+ 'lf.created': 'Tệp mới tạo',
4046
+ 'lf.removedFile': 'Tệp bị xóa',
4047
+ 'lf.truncated': 'Chỉ hiện {count} dòng đầu',
3863
4048
  'preview.title': 'Xem trước Git',
3864
4049
  'preview.hint': 'Bấm “Xem diff” trên tệp hoặc commit — diff sẽ mở ở đây, trên thanh bên.',
3865
4050
  'settings.rail.title': 'Nút thanh tác vụ',
@@ -4075,6 +4260,16 @@ window.__ModuleLoader__.load({
4075
4260
  'settings.placement.immersive': 'الوضع الغامر (ارتفاع كامل، إخفاء صندوق الكتابة)',
4076
4261
  'settings.placement.immersiveHint': 'عند التشغيل يملأ تبويب Git منطقة المحادثة بكامل ارتفاعها ويتراجع صندوق الكتابة؛ تظل الموافقات والأسئلة ظاهرة كالمعتاد.',
4077
4262
  'preview.openFile': 'افتح في معاينة الملف',
4263
+ 'lf.added': '+{count}',
4264
+ 'lf.deleted': '-{count}',
4265
+ 'lf.selectFile': 'اختر الملف للمراجعة',
4266
+ 'lf.split': 'التبديل إلى العرض المنقسم',
4267
+ 'lf.unified': 'التبديل إلى العرض الموحد',
4268
+ 'lf.wrap': 'تمكين التفاف الأسطر',
4269
+ 'lf.nowrap': 'تعطيل التفاف الأسطر',
4270
+ 'lf.created': 'ملف جديد',
4271
+ 'lf.removedFile': 'ملف محذوف',
4272
+ 'lf.truncated': 'عرض أول {count} سطرًا فقط',
4078
4273
  'preview.title': 'معاينة Git',
4079
4274
  'preview.hint': 'انقر «عرض الفروق» على ملف أو commit — تُفتح الفروق هنا في الشريط الجانبي.',
4080
4275
  'settings.rail.title': 'أزرار شريط الإجراءات',
@@ -4290,6 +4485,16 @@ window.__ModuleLoader__.load({
4290
4485
  'settings.placement.immersive': 'इमर्सिव मोड (पूरी ऊँचाई, छिपा इनपुट बॉक्स)',
4291
4486
  'settings.placement.immersiveHint': 'चालू होने पर Git टैब पूरे संवाद क्षेत्र की ऊँचाई घेर लेता है और इनपुट बॉक्स हट जाता है; अनुमोदन और प्रश्न फिर भी दिखते हैं।',
4292
4487
  'preview.openFile': 'फ़ाइल पूर्वावलोकन में खोलें',
4488
+ 'lf.added': '+{count}',
4489
+ 'lf.deleted': '-{count}',
4490
+ 'lf.selectFile': 'समीक्षा की फ़ाइल चुनें',
4491
+ 'lf.split': 'स्प्लिट व्यू पर स्विच करें',
4492
+ 'lf.unified': 'यूनिफाइड व्यू पर स्विच करें',
4493
+ 'lf.wrap': 'लाइन रैप चालू करें',
4494
+ 'lf.nowrap': 'लाइन रैप बंद करें',
4495
+ 'lf.created': 'नई फ़ाइल',
4496
+ 'lf.removedFile': 'हटाई गई फ़ाइल',
4497
+ 'lf.truncated': 'केवल पहली {count} पंक्तियाँ दिखाई जा रही हैं',
4293
4498
  'preview.title': 'Git पूर्वावलोकन',
4294
4499
  'preview.hint': 'फ़ाइल या कमिट पर “अंतर दिखाएँ” पर क्लिक करें — अंतर यहीं साइडबार में खुलेगा।',
4295
4500
  'settings.rail.title': 'एक्शन रेल बटन',
@@ -4505,6 +4710,16 @@ window.__ModuleLoader__.load({
4505
4710
  'settings.placement.immersive': 'โหมดดื่มด่ำ (เต็มความสูง, ซ่อนกล่องพิมพ์)',
4506
4711
  'settings.placement.immersiveHint': 'เมื่อเปิด แท็บ Git จะกินพื้นที่สูงทั้งหมดของบทสนทนาและกล่องพิมพ์จะถอยออก; การอนุมัติและคำถามยังแสดงตามปกติ',
4507
4712
  'preview.openFile': 'เปิดในตัวอย่างไฟล์',
4713
+ 'lf.added': '+{count}',
4714
+ 'lf.deleted': '-{count}',
4715
+ 'lf.selectFile': 'เลือกไฟล์ที่จะตรวจสอบ',
4716
+ 'lf.split': 'สลับเป็นมุมมองคู่',
4717
+ 'lf.unified': 'สลับเป็นมุมมองเดียว',
4718
+ 'lf.wrap': 'เปิดการตัดบรรทัด',
4719
+ 'lf.nowrap': 'ปิดการตัดบรรทัด',
4720
+ 'lf.created': 'ไฟล์ที่สร้างใหม่',
4721
+ 'lf.removedFile': 'ไฟล์ที่ถูกลบ',
4722
+ 'lf.truncated': 'แสดงเฉพาะ {count} บรรทัดแรก',
4508
4723
  'preview.title': 'ดูตัวอย่าง Git',
4509
4724
  'preview.hint': 'คลิก “แสดงความต่าง” ที่ไฟล์หรือคอมมิต — ความต่างจะเปิดที่นี่ในแถบข้าง',
4510
4725
  'settings.rail.title': 'ปุ่มแถบการทำงาน',
@@ -6223,6 +6438,417 @@ window.__ModuleLoader__.load({
6223
6438
  )))
6224
6439
  }
6225
6440
 
6441
+ /* ---- external preview diff (v0.14.3): a faithful port of the review view ----
6442
+ ReviewTab + FileDiff (ui-deliverables) are the reference; every building
6443
+ block that package imports from the platform seed is reused here
6444
+ verbatim: Menu / PathLabel / Tooltip / the split, wrap, inspect and
6445
+ chevron icons, useCodeHighlighter, languageForPath, and the file-diff
6446
+ alias tokens. Only the layer the official bundle boundary hides — the
6447
+ row layout, the hunk parser, the review-store-free state — lives in
6448
+ this file, and it follows the source file by file: header anatomy and
6449
+ CSS from ReviewTab.tsx + ReviewTab.module.css, rows/hunks/highlights
6450
+ from FileDiff.tsx + FileDiff.module.css. When ui-deliverables
6451
+ re-exports FileDiff (see the host-follow note in AGENTS.md), this whole
6452
+ block retires in favor of a plain import of the deliverables
6453
+ package's re-exported FileDiff. */
6454
+
6455
+ /* One hunk in the shape FileDiff consumes: {oldStart, oldLines, newStart,
6456
+ newLines, lines[]} where lines keep their +/-/space prefix. oldLines and
6457
+ newLines are COUNTED from content (git omits the count when it is 1),
6458
+ exactly what the reconstructed @@ header needs. */
6459
+ function parseHunks(body) {
6460
+ const text = typeof body === 'string' ? body : ''
6461
+ const out = []
6462
+ let current = null
6463
+ for (const line of text.split('\n')) {
6464
+ if (line.indexOf('@@') === 0) {
6465
+ const match = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(line)
6466
+ if (match !== null) {
6467
+ if (current !== null && current.lines.length > 0) out.push(current)
6468
+ current = { oldStart: Number.parseInt(match[1], 10), newStart: Number.parseInt(match[3], 10), oldLines: 0, newLines: 0, lines: [] }
6469
+ continue
6470
+ }
6471
+ }
6472
+ if (current === null) continue
6473
+ const marker = line.charAt(0)
6474
+ if (marker === '+' || marker === '-' || marker === ' ' || line === '') {
6475
+ current.lines.push(line)
6476
+ if (marker === '+') current.newLines += 1
6477
+ else if (marker === '-') current.oldLines += 1
6478
+ else { current.oldLines += 1; current.newLines += 1 }
6479
+ } else if (line.indexOf('\\') === 0) {
6480
+ current.lines.push(line)
6481
+ }
6482
+ }
6483
+ if (current !== null && current.lines.length > 0) out.push(current)
6484
+ /* The patch's trailing newline parses as one empty context line; the
6485
+ review drops it, and so does this. */
6486
+ for (const hunk of out) {
6487
+ const last = hunk.lines[hunk.lines.length - 1]
6488
+ if (last === '' && hunk.lines.length > 1) {
6489
+ hunk.lines.pop()
6490
+ hunk.oldLines -= 1
6491
+ hunk.newLines -= 1
6492
+ }
6493
+ }
6494
+ return out
6495
+ }
6496
+
6497
+ /* One file segment of a (possibly multi-file) patch: path, hunks, ±
6498
+ counts, and the file-level facts FileDiff's notes need. */
6499
+ function splitPatchFiles(patch) {
6500
+ const text = typeof patch === 'string' ? patch : ''
6501
+ if (text.trim() === '') return []
6502
+ const segments = []
6503
+ let current = null
6504
+ for (const line of text.split('\n')) {
6505
+ if (line.indexOf('diff --git ') === 0) {
6506
+ if (current !== null) segments.push(current)
6507
+ current = { path: '', meta: [], body: [], binary: false, created: false, removed: false }
6508
+ continue
6509
+ }
6510
+ if (current === null) continue
6511
+ if (line.indexOf('+++ ') === 0) current.path = line.slice(4) === '/dev/null' ? current.path : line.slice(4).replace(/^b\//, '')
6512
+ else if (current.path === '' && line.indexOf('--- a/') === 0) current.path = line.slice(6)
6513
+ if (line.indexOf('Binary files') === 0 || line.indexOf('Binary file') === 0) current.binary = true
6514
+ if (line.indexOf('new file mode') === 0) current.created = true
6515
+ if (line.indexOf('deleted file mode') === 0) current.removed = true
6516
+ if (line.indexOf('diff --git ') === 0 || line.indexOf('index ') === 0 || line.indexOf('--- ') === 0 || line.indexOf('+++ ') === 0 || line.indexOf('new file') === 0 || line.indexOf('deleted file') === 0 || line.indexOf('similarity index') === 0 || line.indexOf('rename ') === 0 || line.indexOf('old mode') === 0 || line.indexOf('new mode') === 0) current.meta.push(line)
6517
+ else current.body.push(line)
6518
+ }
6519
+ if (current !== null) segments.push(current)
6520
+ const out = []
6521
+ for (const segment of segments) {
6522
+ const hunks = parseHunks(segment.body.join('\n'))
6523
+ let adds = 0
6524
+ let dels = 0
6525
+ for (const hunk of hunks) {
6526
+ for (const line of hunk.lines) {
6527
+ if (line.indexOf('+') === 0) adds += 1
6528
+ else if (line.indexOf('-') === 0) dels += 1
6529
+ }
6530
+ }
6531
+ if (segment.path === '' && hunks.length === 0 && segment.binary === false) continue
6532
+ out.push({ path: segment.path, binary: segment.binary, created: segment.created, removed: segment.removed, hunks: hunks, adds: adds, dels: dels })
6533
+ }
6534
+ return out
6535
+ }
6536
+
6537
+ /* ---- pure ports of FileDiff.tsx's exported row functions ---- */
6538
+
6539
+ /* Number a hunk's lines: context lines count on both sides, deletions on
6540
+ the old side, additions on the new side. */
6541
+ function hunkRows(hunk) {
6542
+ let oldNo = hunk.oldStart
6543
+ let newNo = hunk.newStart
6544
+ return hunk.lines.map((line) => {
6545
+ const body = line.slice(1)
6546
+ if (line.indexOf('+') === 0) return { kind: 'add', old: undefined, new: newNo++, text: body }
6547
+ if (line.indexOf('-') === 0) return { kind: 'del', old: oldNo++, new: undefined, text: body }
6548
+ return { kind: 'context', old: oldNo++, new: newNo++, text: body }
6549
+ })
6550
+ }
6551
+
6552
+ /* Pair a hunk's lines for the side-by-side view: each run of deletions is
6553
+ aligned with the run of additions that follows it, row by row, and
6554
+ context lines sit on both sides. */
6555
+ function splitRows(hunk) {
6556
+ const rows = []
6557
+ let dels = []
6558
+ let adds = []
6559
+ const flush = () => {
6560
+ for (let at = 0; at < Math.max(dels.length, adds.length); at += 1) {
6561
+ const left = dels[at]
6562
+ const right = adds[at]
6563
+ rows.push(Object.assign(left === undefined ? {} : { left: left }, right === undefined ? {} : { right: right }))
6564
+ }
6565
+ dels = []
6566
+ adds = []
6567
+ }
6568
+ for (const row of hunkRows(hunk)) {
6569
+ if (row.kind === 'del') dels.push({ no: row.old, text: row.text, kind: 'del' })
6570
+ else if (row.kind === 'add') adds.push({ no: row.new, text: row.text, kind: 'add' })
6571
+ else {
6572
+ flush()
6573
+ rows.push({ left: { no: row.old, text: row.text, kind: 'context' }, right: { no: row.new, text: row.text, kind: 'context' } })
6574
+ }
6575
+ }
6576
+ flush()
6577
+ return rows
6578
+ }
6579
+
6580
+ /* The hunks to draw, cut at LINEDIFF_MAX_LINES lines in total. */
6581
+ const LINEDIFF_MAX_LINES = 5000
6582
+ function renderedHunks(hunks) {
6583
+ let budget = LINEDIFF_MAX_LINES
6584
+ const kept = []
6585
+ for (const hunk of hunks) {
6586
+ if (budget === 0) return { hunks: kept, truncated: true }
6587
+ kept.push(hunk.lines.length <= budget ? hunk : Object.assign({}, hunk, { lines: hunk.lines.slice(0, budget) }))
6588
+ budget -= Math.min(budget, hunk.lines.length)
6589
+ }
6590
+ return { hunks: kept, truncated: hunks.some((hunk, at) => kept[at] !== hunk) }
6591
+ }
6592
+
6593
+ function hunkHeader(hunk) {
6594
+ return '@@ -' + String(hunk.oldStart) + ',' + String(hunk.oldLines) + ' +' + String(hunk.newStart) + ',' + String(hunk.newLines) + ' @@'
6595
+ }
6596
+
6597
+ /* Per-hunk, per-side Shiki alignment (the FileDiff rule): each side's
6598
+ lines join into ONE fragment so multi-line grammars hold, and the
6599
+ per-line spans map back by line number. */
6600
+ function hunkSideSpans(rows, side, highlighter) {
6601
+ if (highlighter === undefined) return undefined
6602
+ const source = []
6603
+ for (const row of rows) {
6604
+ const no = row[side]
6605
+ if (no === undefined) continue
6606
+ source.push({ no: no, text: row.text })
6607
+ }
6608
+ if (source.length === 0) return new Map()
6609
+ const highlighted = highlighter(source.map((line) => line.text).join('\n'))
6610
+ if (highlighted === undefined) return undefined
6611
+ const map = new Map()
6612
+ source.forEach((line, index) => { map.set(line.no, highlighted[index] || []) })
6613
+ return map
6614
+ }
6615
+
6616
+ function hunkHighlights(hunk, highlighter) {
6617
+ const rows = hunkRows(hunk)
6618
+ return { old: hunkSideSpans(rows, 'old', highlighter), new: hunkSideSpans(rows, 'new', highlighter) }
6619
+ }
6620
+
6621
+ /* split/wrap preferences are habits, not tab state — one localStorage
6622
+ home, the same policy as the tree folds. */
6623
+ const LINEDIFF_KEY = 'dsh-ide-git.linediff.v1'
6624
+ function readLineDiffPrefs() {
6625
+ try {
6626
+ const raw = window.localStorage.getItem(LINEDIFF_KEY)
6627
+ const parsed = raw === null ? null : JSON.parse(raw)
6628
+ return parsed !== null && typeof parsed === 'object'
6629
+ ? { split: parsed.split === true, wrap: parsed.wrap === true }
6630
+ : { split: false, wrap: false }
6631
+ } catch (error) { void error }
6632
+ return { split: false, wrap: false }
6633
+ }
6634
+ function writeLineDiffPrefs(prefs) {
6635
+ try { window.localStorage.setItem(LINEDIFF_KEY, JSON.stringify(prefs)) } catch (error) { void error }
6636
+ }
6637
+
6638
+ /* The seed blocks the official review uses, resolved once. Every consumer
6639
+ null-checks: a host whose primitives predate an export keeps working
6640
+ through the hand-drawn fallbacks (labels instead of icons, a plain
6641
+ select instead of Menu). */
6642
+ const SeedMenu = UIPrimitives !== null && typeof UIPrimitives.Menu === 'function' ? UIPrimitives.Menu : null
6643
+ const SeedPathLabel = UIPrimitives !== null && typeof UIPrimitives.PathLabel === 'function' ? UIPrimitives.PathLabel : null
6644
+ const SeedTooltip = UIPrimitives !== null && typeof UIPrimitives.Tooltip === 'function' ? UIPrimitives.Tooltip : null
6645
+ const SeedIcons = UIPrimitives !== null ? UIPrimitives : {}
6646
+
6647
+ function DiffText(props) {
6648
+ return E('span', { className: 'dig-lf-text', 'data-diff-code': props.spans === undefined ? undefined : '' },
6649
+ props.spans === undefined || props.spans === null || props.spans.length === 0
6650
+ ? (props.text === '' ? ' ' : props.text)
6651
+ : props.spans.map((span, at) => E('span', { key: at, style: span.style }, span.text)))
6652
+ }
6653
+
6654
+ /* The unified row: old number, new number, sign, text (FileDiff's .line). */
6655
+ function UnifiedLine(props) {
6656
+ const row = props.row
6657
+ const spans = props.spans
6658
+ return E('div', { className: 'dig-lf-line dig-lf-' + row.kind, 'data-diff-line': row.kind },
6659
+ E('span', { className: 'dig-lf-number' }, row.old === undefined ? '' : String(row.old)),
6660
+ E('span', { className: 'dig-lf-number' }, row.new === undefined ? '' : String(row.new)),
6661
+ E('span', { className: 'dig-lf-sign' }, row.kind === 'add' ? '+' : row.kind === 'del' ? '\u2212' : ' '),
6662
+ E(DiffText, { text: row.text, spans: row.kind === 'add' ? spans.new.get(row.new) : spans.old.get(row.old) }))
6663
+ }
6664
+
6665
+ /* The wrapped split row: two cells, each its own number + text. */
6666
+ function SplitLine(props) {
6667
+ const row = props.row
6668
+ const spans = props.spans
6669
+ const kind = row.left !== undefined && row.left.kind === 'del' ? 'del' : row.right !== undefined && row.right.kind === 'add' ? 'add' : 'context'
6670
+ return E('div', { className: 'dig-lf-splitLine', 'data-diff-line': kind },
6671
+ E('span', { className: 'dig-lf-cell ' + (row.left === undefined ? 'dig-lf-empty' : 'dig-lf-' + row.left.kind) },
6672
+ E('span', { className: 'dig-lf-number' }, row.left === undefined ? '' : String(row.left.no)),
6673
+ E(DiffText, { text: row.left === undefined ? '' : row.left.text, spans: row.left === undefined || spans.old === undefined ? undefined : spans.old.get(row.left.no) })),
6674
+ E('span', { className: 'dig-lf-cell ' + (row.right === undefined ? 'dig-lf-empty' : 'dig-lf-' + row.right.kind) },
6675
+ E('span', { className: 'dig-lf-number' }, row.right === undefined ? '' : String(row.right.no)),
6676
+ E(DiffText, { text: row.right === undefined ? '' : row.right.text, spans: row.right === undefined || spans.new === undefined ? undefined : spans.new.get(row.right.no) })))
6677
+ }
6678
+
6679
+ /* The no-wrap split: two columns that scroll together (FileDiff's
6680
+ SplitColumns). The sync mirrors the source's offset-record loop. */
6681
+ function SplitColumns(props) {
6682
+ const hunks = props.hunks
6683
+ const highlights = props.highlights
6684
+ const paired = useMemo(() => hunks.map((hunk) => ({ header: hunkHeader(hunk), rows: splitRows(hunk) })), [hunks])
6685
+ const leftRef = useRef(null)
6686
+ const rightRef = useRef(null)
6687
+ const offsets = useRef({ left: { x: 0, y: 0 }, right: { x: 0, y: 0 } })
6688
+ const follow = (side) => (event) => {
6689
+ const peer = side === 'left' ? rightRef : leftRef
6690
+ const other = peer.current
6691
+ if (other === null) return
6692
+ for (const axis of ['scrollLeft', 'scrollTop']) {
6693
+ const value = event.currentTarget[axis]
6694
+ const key = axis === 'scrollLeft' ? 'x' : 'y'
6695
+ if (offsets.current[side][key] === value) continue
6696
+ offsets.current[side][key] = value
6697
+ other[axis] = value
6698
+ offsets.current[side === 'left' ? 'right' : 'left'][key] = other[axis]
6699
+ }
6700
+ }
6701
+ return E('div', { className: 'dig-lf-columns' },
6702
+ ['left', 'right'].map((side) => E('div', {
6703
+ key: side,
6704
+ className: 'dig-lf-column',
6705
+ 'data-diff-side': side,
6706
+ ref: side === 'left' ? leftRef : rightRef,
6707
+ onScroll: follow(side),
6708
+ }, paired.map((hunk, position) => E('section', { key: position, className: 'dig-lf-hunk' },
6709
+ E('div', { className: 'dig-lf-hunkHeader', 'data-diff-hunk-header': '' }, hunk.header),
6710
+ hunk.rows.map((row, at) => {
6711
+ const cell = row[side]
6712
+ const spans = cell === undefined ? undefined : highlights[position][side === 'left' ? 'old' : 'new']
6713
+ return E('div', {
6714
+ key: at,
6715
+ className: 'dig-lf-sideLine ' + (cell === undefined ? 'dig-lf-empty' : 'dig-lf-' + cell.kind),
6716
+ },
6717
+ E('span', { className: 'dig-lf-number' }, cell === undefined ? '' : String(cell.no)),
6718
+ E(DiffText, { text: cell === undefined ? '' : cell.text, spans: cell === undefined || spans === undefined ? undefined : spans.get(cell.no) }))
6719
+ }))))))
6720
+ }
6721
+
6722
+ /* One toolbar button in the review's tool style; Tooltip-wrapped when the
6723
+ seed provides it, a title attribute otherwise. */
6724
+ function LineDiffTool(props) {
6725
+ const inner = E('button', {
6726
+ type: 'button',
6727
+ className: 'dig-lf-tool',
6728
+ 'aria-pressed': props.pressed,
6729
+ 'aria-label': props.label,
6730
+ 'data-review-tool': props.tool,
6731
+ onClick: props.onClick,
6732
+ }, props.children)
6733
+ if (SeedTooltip === null) {
6734
+ return E('span', { title: props.label }, inner)
6735
+ }
6736
+ return E(SeedTooltip, { label: props.label, side: 'bottom', delayMs: 500 }, inner)
6737
+ }
6738
+
6739
+ /* The external preview's diff view: the official review's header (file
6740
+ selector + counts + split/wrap/open tools) over the official row
6741
+ anatomy, fed by the plugin's own patch text instead of the host's
6742
+ workspace-changes events. */
6743
+ function LineDiff(props) {
6744
+ const t = props.t
6745
+ const files = useMemo(() => splitPatchFiles(props.patch), [props.patch])
6746
+ const [active, setActive] = useState(0)
6747
+ const [prefs, setPrefs] = useState(readLineDiffPrefs)
6748
+ useEffect(() => { writeLineDiffPrefs(prefs) }, [prefs])
6749
+ const [menuOpen, setMenuOpen] = useState(false)
6750
+ const index = active < files.length ? active : 0
6751
+ const file = files[index]
6752
+ const language = useMemo(() => (languageForPathSeed === null || file === undefined || file.path === '') ? undefined : languageForPathSeed(file.path), [file])
6753
+ const highlighter = useCodeHighlighterSeed === null ? undefined : useCodeHighlighterSeed(language)
6754
+ const cut = useMemo(() => {
6755
+ if (file === undefined) return { hunks: [], truncated: false, maps: [] }
6756
+ const result = renderedHunks(file.hunks)
6757
+ return { hunks: result.hunks, truncated: result.truncated, maps: result.hunks.map((hunk) => hunkHighlights(hunk, highlighter)) }
6758
+ }, [file, highlighter])
6759
+ const hasAdditions = file !== undefined && file.hunks.some((hunk) => hunk.lines.some((line) => line.indexOf('+') === 0))
6760
+ const hasDeletions = file !== undefined && file.hunks.some((hunk) => hunk.lines.some((line) => line.indexOf('-') === 0))
6761
+ const oneSided = hasAdditions !== hasDeletions
6762
+ const split = prefs.split === true && oneSided !== true
6763
+ const wrap = prefs.wrap === true
6764
+ if (props.loading === true) return E('div', { className: 'dig-lf-root' }, E('p', { className: 'dig-lf-status', role: 'status' }, t('diff.loading')))
6765
+ if (file === undefined) return E('div', { className: 'dig-lf-root' }, E('p', { className: 'dig-lf-status' }, t('diff.empty')))
6766
+ /* header: selector + counts + tools (ReviewTab anatomy) */
6767
+ const counts = file.binary === true
6768
+ ? E('span', { className: 'dig-lf-label' }, t('diff.binary'))
6769
+ : E('span', { className: 'dig-lf-counts' },
6770
+ E('span', { className: 'dig-lf-added' }, fill(t('lf.added'), { count: String(file.adds) })),
6771
+ E('span', { className: 'dig-lf-deleted' }, fill(t('lf.deleted'), { count: String(file.dels) })))
6772
+ const selectorButton = E('button', {
6773
+ type: 'button',
6774
+ className: 'dig-lf-selectorButton',
6775
+ 'aria-haspopup': 'menu',
6776
+ 'aria-expanded': menuOpen,
6777
+ 'aria-label': t('lf.selectFile'),
6778
+ title: file.path,
6779
+ 'data-review-file': file.path,
6780
+ onClick: () => { setMenuOpen((value) => !value) },
6781
+ },
6782
+ SeedPathLabel === null ? file.path : E(SeedPathLabel, { path: file.path }),
6783
+ SeedIcons.IconChevronDownOutlineRegular === undefined ? '\u25be' : E(SeedIcons.IconChevronDownOutlineRegular, { size: 12 }))
6784
+ const selector = files.length > 1
6785
+ ? (SeedMenu === null
6786
+ ? E('select', {
6787
+ className: 'dig-lf-selectorFallback',
6788
+ value: String(index),
6789
+ 'aria-label': t('lf.selectFile'),
6790
+ onChange: (event) => { setActive(Number(event.target.value)) },
6791
+ }, files.map((entry, at) => E('option', { key: at, value: String(at) }, entry.path + ' +' + String(entry.adds) + ' -' + String(entry.dels))))
6792
+ : E(SeedMenu, {
6793
+ className: 'dig-lf-selector',
6794
+ open: menuOpen,
6795
+ autoFocus: true,
6796
+ portal: true,
6797
+ align: 'start',
6798
+ dense: true,
6799
+ onClose: () => { setMenuOpen(false) },
6800
+ anchor: selectorButton,
6801
+ items: files.map((entry, at) => ({
6802
+ id: String(at),
6803
+ label: E('span', { className: 'dig-lf-item' },
6804
+ E('span', { className: 'dig-lf-itemPath' }, entry.path),
6805
+ E('span', { className: 'dig-lf-itemCounts' },
6806
+ E('span', { className: 'dig-lf-added' }, fill(t('lf.added'), { count: String(entry.adds) })),
6807
+ E('span', { className: 'dig-lf-deleted' }, fill(t('lf.deleted'), { count: String(entry.dels) })))),
6808
+ })),
6809
+ selectedId: String(index),
6810
+ onSelect: (id) => { setActive(Number(id)); setMenuOpen(false) },
6811
+ }))
6812
+ : E('span', { className: 'dig-lf-selectorStatic', title: file.path },
6813
+ SeedPathLabel === null ? file.path : E(SeedPathLabel, { path: file.path }))
6814
+ return E('div', { className: 'dig-lf-root' },
6815
+ E('div', { className: 'dig-lf-header' },
6816
+ selector,
6817
+ counts,
6818
+ E('span', { className: 'dig-lf-tools' },
6819
+ E(LineDiffTool, {
6820
+ tool: 'split', pressed: split, label: t(split === true ? 'lf.unified' : 'lf.split'),
6821
+ onClick: () => { setPrefs((now) => Object.assign({}, now, { split: now.split !== true })) },
6822
+ }, SeedIcons.IconCompareSplitOutlineRegular === undefined ? E(Icon, { name: 'split', size: 13 }) : E(SeedIcons.IconCompareSplitOutlineRegular, { className: 'dig-lf-compareIcon' })),
6823
+ E(LineDiffTool, {
6824
+ tool: 'wrap', pressed: wrap, label: t(wrap === true ? 'lf.nowrap' : 'lf.wrap'),
6825
+ onClick: () => { setPrefs((now) => Object.assign({}, now, { wrap: now.wrap !== true })) },
6826
+ }, SeedIcons.IconWrapFillRegular === undefined || SeedIcons.IconNowrapFillRegular === undefined
6827
+ ? E(Icon, { name: 'wrap', size: 13 })
6828
+ : (wrap === true ? E(SeedIcons.IconNowrapFillRegular) : E(SeedIcons.IconWrapFillRegular))),
6829
+ props.onOpenFile === undefined ? null : E(LineDiffTool, {
6830
+ tool: 'open-file', label: t('preview.openFile'),
6831
+ onClick: props.onOpenFile,
6832
+ }, SeedIcons.IconInspectOutlineRegular === undefined ? E(Icon, { name: 'file', size: 13 }) : E(SeedIcons.IconInspectOutlineRegular)),
6833
+ )),
6834
+ file.binary === true
6835
+ ? E('p', { className: 'dig-lf-status' }, t('diff.binary'))
6836
+ : E('div', { className: 'dig-lf-body', 'data-review-view': split === true ? 'split' : 'unified', 'data-review-wrap': wrap === true ? '' : undefined },
6837
+ file.created === true ? E('p', { className: 'dig-lf-note' }, t('lf.created')) : null,
6838
+ file.removed === true ? E('p', { className: 'dig-lf-note' }, t('lf.removedFile')) : null,
6839
+ cut.truncated === true ? E('p', { className: 'dig-lf-note' }, fill(t('lf.truncated'), { count: String(LINEDIFF_MAX_LINES) })) : null,
6840
+ split === true && wrap !== true
6841
+ ? E(SplitColumns, { hunks: cut.hunks, highlights: cut.maps })
6842
+ : cut.hunks.map((hunk, position) => {
6843
+ const paired = split === true ? splitRows(hunk) : null
6844
+ return E('section', { key: position, className: 'dig-lf-hunk' },
6845
+ E('div', { className: 'dig-lf-hunkHeader', 'data-diff-hunk-header': '' }, hunkHeader(hunk)),
6846
+ hunkRows(hunk).map((row, at) => split === true
6847
+ ? E(SplitLine, { key: at, row: paired[at], spans: cut.maps[position] })
6848
+ : E(UnifiedLine, { key: at, row: row, spans: cut.maps[position] })))
6849
+ })))
6850
+ }
6851
+
6226
6852
  /* The leading glyph of a change row (v0.14): the host's FileTypeIcon —
6227
6853
  classifyFileType runs inside when given a path. Directories (the
6228
6854
  trailing slash an `ls-files --directory` row carries) ask for the
@@ -9528,6 +10154,57 @@ window.__ModuleLoader__.load({
9528
10154
  '.dig-preview-actions{display:flex;align-items:center;gap:4px;flex:none}',
9529
10155
  '.dig-preview-btn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}',
9530
10156
  '.dig-preview-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
10157
+ /* LineDiff (v0.14.3): the review view's anatomy on its own tokens —
10158
+ every rule below mirrors FileDiff.module.css / ReviewTab.module.css
10159
+ line for line (class dig-lf- stands in for their css modules), so the
10160
+ official theme and reading habits carry over. */
10161
+ '.dig-lf-root{--diff-empty-fill:color-mix(in srgb,var(--dsw-alias-interactive-bg-hover) 50%,transparent);display:flex;flex-direction:column;box-sizing:border-box;width:100%;min-height:0;color:var(--dsw-alias-label-primary)}',
10162
+ '.dig-lf-header{display:flex;flex:0 0 auto;gap:6px;align-items:center;box-sizing:border-box;min-height:38px;padding:0 6px 0 8px;border-bottom:0.5px solid var(--dsw-alias-border-l3)}',
10163
+ '.dig-lf-status{display:flex;gap:12px;align-items:center;margin:0;padding:16px;font-size:13px;color:var(--dsw-alias-label-secondary)}',
10164
+ '.dig-lf-body{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;overflow:auto;padding:8px 0 16px;font:var(--dsw-font-markdown-code-block)}',
10165
+ '.dig-lf-columns{display:grid;flex:1 1 auto;grid-template-columns:minmax(0,1fr) minmax(0,1fr);min-height:0}',
10166
+ '.dig-lf-column,.dig-lf-body[data-review-view=unified]:not([data-review-wrap]){display:grid;grid-template-columns:minmax(max-content,100%);align-content:start}',
10167
+ '.dig-lf-column{min-width:0;overflow-x:scroll;overscroll-behavior:none}',
10168
+ '.dig-lf-column + .dig-lf-column{border-left:0.5px solid var(--dsw-alias-border-l3)}',
10169
+ '.dig-lf-sideLine{display:grid;grid-template-columns:3.5em max-content;box-sizing:border-box;min-width:100%;width:max-content;min-height:22px;line-height:22px;white-space:pre}',
10170
+ '.dig-lf-note{margin:0;padding:4px 16px 8px;font:var(--dsw-font-xs-13,var(--dsw-font-markdown-code-block));color:var(--dsw-alias-label-tertiary)}',
10171
+ '.dig-lf-hunk{margin-bottom:8px}',
10172
+ '.dig-lf-hunkHeader{padding:4px 16px;color:var(--dsw-alias-label-tertiary);white-space:pre}',
10173
+ '.dig-lf-line{display:grid;grid-template-columns:3.5em 3.5em 1.2em minmax(0,1fr);box-sizing:border-box;min-height:22px;line-height:22px;white-space:pre}',
10174
+ '.dig-lf-splitLine{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);min-height:22px;line-height:22px;white-space:pre}',
10175
+ '.dig-lf-cell{display:grid;grid-template-columns:3.5em minmax(0,1fr);box-sizing:border-box;min-width:0}',
10176
+ '.dig-lf-cell + .dig-lf-cell{border-left:0.5px solid var(--dsw-alias-border-l3)}',
10177
+ '.dig-lf-number{padding-right:8px;color:var(--dsw-alias-label-tertiary);text-align:right;user-select:none}',
10178
+ '.dig-lf-sign{text-align:center;user-select:none}',
10179
+ '.dig-lf-text{padding-right:16px}',
10180
+ '.dig-lf-body[data-review-wrap] .dig-lf-line,.dig-lf-body[data-review-wrap] .dig-lf-splitLine{white-space:pre-wrap}',
10181
+ '.dig-lf-body[data-review-wrap] .dig-lf-text{overflow-wrap:anywhere}',
10182
+ '.dig-lf-add{--diff-gutter-fill:var(--dsw-alias-file-diff-added-gutter);--diff-marker:var(--dsw-alias-file-diff-added-marker);background:var(--dsw-alias-file-diff-added-bg)}',
10183
+ '.dig-lf-del{--diff-gutter-fill:var(--dsw-alias-file-diff-deleted-gutter);--diff-marker:var(--dsw-alias-file-diff-deleted-marker);background:var(--dsw-alias-file-diff-deleted-bg)}',
10184
+ '.dig-lf-add .dig-lf-number,.dig-lf-del .dig-lf-number{background:var(--diff-gutter-fill);color:var(--diff-marker)}',
10185
+ '.dig-lf-add .dig-lf-number:first-child,.dig-lf-del .dig-lf-number:first-child{box-shadow:inset 3px 0 0 var(--diff-marker)}',
10186
+ '.dig-lf-add .dig-lf-sign,.dig-lf-del .dig-lf-sign{color:var(--diff-marker)}',
10187
+ '.dig-lf-context .dig-lf-text{color:var(--dsw-alias-label-secondary)}',
10188
+ '.dig-lf-empty{background:var(--diff-empty-fill)}',
10189
+ /* header pieces from ReviewTab.module.css */
10190
+ '.dig-lf-selector{flex:0 1 auto;min-width:0}',
10191
+ '.dig-lf-selectorStatic{flex:0 1 auto;min-width:0;overflow:hidden;font-size:12px;text-overflow:ellipsis;white-space:nowrap;font-weight:600}',
10192
+ '.dig-lf-selectorFallback{max-width:60%;padding:2px 6px;border:none;border-radius:var(--dsw-radius-sm,8px);background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1));color:inherit;font:inherit;font-size:12px}',
10193
+ '.dig-lf-selectorButton{display:inline-flex;gap:4px;align-items:center;box-sizing:border-box;max-width:100%;height:28px;padding:0 6px 0 8px;border:0;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;font-size:12px;line-height:20px}',
10194
+ '.dig-lf-selectorButton:hover,.dig-lf-selectorButton[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover)}',
10195
+ '.dig-lf-selectorButton svg{display:block;flex:none}',
10196
+ '.dig-lf-item{display:flex;gap:12px;align-items:center;justify-content:space-between;min-width:0}',
10197
+ '.dig-lf-itemPath{min-width:0;overflow:hidden;line-height:20px;text-overflow:ellipsis;white-space:nowrap}',
10198
+ '.dig-lf-itemCounts,.dig-lf-counts{display:inline-flex;flex:none;gap:6px;align-items:center;font-family:var(--ds-font-family-code);font-size:12px;line-height:20px;color:var(--dsw-alias-label-tertiary)}',
10199
+ '.dig-lf-counts{min-width:0;margin-right:auto}',
10200
+ '.dig-lf-added{color:var(--dsw-alias-state-success-primary)}',
10201
+ '.dig-lf-deleted{color:var(--dsw-alias-state-error-primary)}',
10202
+ '.dig-lf-label{color:var(--dsw-alias-label-tertiary)}',
10203
+ '.dig-lf-tools{display:inline-flex;flex:none;gap:2px;align-items:center;margin-left:auto}',
10204
+ '.dig-lf-tool{display:inline-flex;flex:none;align-items:center;justify-content:center;width:28px;height:28px;padding:6px;border:0;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}',
10205
+ '.dig-lf-tool svg{width:15px;height:15px}',
10206
+ '.dig-lf-tool:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}',
10207
+ '.dig-lf-tool[aria-pressed=true] .dig-lf-compareIcon{transform:rotate(90deg)}',
9531
10208
  ].join('\n')
9532
10209
 
9533
10210
  /* ============================== plugin ============================== */
@@ -9896,10 +10573,6 @@ window.__ModuleLoader__.load({
9896
10573
  E('div', { className: 'dig-preview-head' },
9897
10574
  E('span', { className: 'dig-preview-title', title: title }, title === '' ? t('preview.title') : title),
9898
10575
  seed === null ? null : E('div', { className: 'dig-preview-actions' },
9899
- seed.path !== undefined && seed.path !== null && seed.path !== ''
9900
- ? E('button', { type: 'button', className: 'dig-preview-btn', 'aria-label': t('preview.openFile'), title: t('preview.openFile'), onClick: openInFilePreview },
9901
- E(Icon, { name: 'file', size: 13 }))
9902
- : null,
9903
10576
  E('button', { type: 'button', className: 'dig-preview-btn', 'aria-label': t('diff.copy'), title: t('diff.copy'), onClick: copyPatch },
9904
10577
  E(Icon, { name: copied === true ? 'check' : 'copy', size: 13 })),
9905
10578
  E('button', { type: 'button', className: 'dig-preview-btn', 'aria-label': t('toolbar.refresh'), title: t('toolbar.refresh'), onClick: () => { setRefreshTick((n) => n + 1) } },
@@ -9908,7 +10581,14 @@ window.__ModuleLoader__.load({
9908
10581
  ? E('div', { className: 'dig-preview-body' }, E('div', { className: 'dig-empty' }, t('preview.hint')))
9909
10582
  : state.error !== null
9910
10583
  ? E('div', { className: 'dig-preview-body' }, E('div', { className: 'dig-empty' }, state.error))
9911
- : E('div', { className: 'dig-preview-body' }, E(DiffBody, { patch: state.patch, loading: state.loading, binary: false, t: t })))
10584
+ : E('div', { className: 'dig-preview-body' }, E(LineDiff, {
10585
+ patch: state.patch,
10586
+ loading: state.loading,
10587
+ t: t,
10588
+ /* worktree seeds carry the file path: the review's open-file
10589
+ tool jumps to the host's whole-file preview. */
10590
+ onOpenFile: seed.path !== undefined && seed.path !== null && seed.path !== '' ? openInFilePreview : undefined,
10591
+ })))
9912
10592
  }
9913
10593
 
9914
10594
  function apply(ctx) {