on main help sign in
Merge branch 'main' into spinner-improvements
commite03608881816e74e8d5d5edb6e4c25240e39c288 authorRaphael Amorim <rapha850@gmail.com> dateTue 17 Jun 2025, 17:18 (1 year ago) parentea94910d8a4fa0
382 files changed+6251−797
cmd/root.go+1 cspell.json+1−1 go.mod+3−2 go.sum+4−4 internal/db/db.go−10 internal/db/files.sql.go+7−38 internal/db/migrations/20250424200609_initial.sql+1−1 internal/db/models.go+1−1 internal/db/querier.go−1 internal/db/sql/files.sql+6−15 internal/exp/diffview/Taskfile.yaml+102 internal/exp/diffview/chroma.go+52 internal/exp/diffview/diffview.go+681 internal/exp/diffview/diffview_test.go+300 internal/exp/diffview/split.go+74 internal/exp/diffview/style.go+124 internal/exp/diffview/testdata/TestDefault.after+10 internal/exp/diffview/testdata/TestDefault.before+9 internal/exp/diffview/testdata/TestDiffView/Split/CustomContextLines/DarkMode.golden+16 internal/exp/diffview/testdata/TestDiffView/Split/CustomContextLines/LightMode.golden+16 internal/exp/diffview/testdata/TestDiffView/Split/Default/DarkMode.golden+7 internal/exp/diffview/testdata/TestDiffView/Split/Default/LightMode.golden+7 internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/DarkMode.golden+15 internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/LightMode.golden+15 internal/exp/diffview/testdata/TestDiffView/Split/MultipleHunks/DarkMode.golden+15 internal/exp/diffview/testdata/TestDiffView/Split/MultipleHunks/LightMode.golden+15 internal/exp/diffview/testdata/TestDiffView/Split/Narrow/DarkMode.golden+4 internal/exp/diffview/testdata/TestDiffView/Split/Narrow/LightMode.golden+4 internal/exp/diffview/testdata/TestDiffView/Split/NoLineNumbers/DarkMode.golden+7 internal/exp/diffview/testdata/TestDiffView/Split/NoLineNumbers/LightMode.golden+7 internal/exp/diffview/testdata/TestDiffView/Split/NoSyntaxHighlight/DarkMode.golden+15 internal/exp/diffview/testdata/TestDiffView/Split/NoSyntaxHighlight/LightMode.golden+15 internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/DarkMode.golden+15 internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/LightMode.golden+15 internal/exp/diffview/testdata/TestDiffView/Unified/CustomContextLines/DarkMode.golden+17 internal/exp/diffview/testdata/TestDiffView/Unified/CustomContextLines/LightMode.golden+17 internal/exp/diffview/testdata/TestDiffView/Unified/Default/DarkMode.golden+8 internal/exp/diffview/testdata/TestDiffView/Unified/Default/LightMode.golden+8 internal/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/DarkMode.golden+16 internal/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/LightMode.golden+16 internal/exp/diffview/testdata/TestDiffView/Unified/MultipleHunks/DarkMode.golden+16 internal/exp/diffview/testdata/TestDiffView/Unified/MultipleHunks/LightMode.golden+16 internal/exp/diffview/testdata/TestDiffView/Unified/Narrow/DarkMode.golden+7 internal/exp/diffview/testdata/TestDiffView/Unified/Narrow/LightMode.golden+7 internal/exp/diffview/testdata/TestDiffView/Unified/NoLineNumbers/DarkMode.golden+8 internal/exp/diffview/testdata/TestDiffView/Unified/NoLineNumbers/LightMode.golden+8 internal/exp/diffview/testdata/TestDiffView/Unified/NoSyntaxHighlight/DarkMode.golden+16 internal/exp/diffview/testdata/TestDiffView/Unified/NoSyntaxHighlight/LightMode.golden+16 internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/DarkMode.golden+16 internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/LightMode.golden+16 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf001.golden+1 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf002.golden+2 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf003.golden+3 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf004.golden+4 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf005.golden+5 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf006.golden+6 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf007.golden+7 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf008.golden+8 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf009.golden+9 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf010.golden+10 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf011.golden+11 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf012.golden+12 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf013.golden+13 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf014.golden+14 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf015.golden+15 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf016.golden+16 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf017.golden+17 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf018.golden+18 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf019.golden+19 internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf020.golden+20 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf001.golden+1 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf002.golden+2 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf003.golden+3 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf004.golden+4 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf005.golden+5 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf006.golden+6 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf007.golden+7 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf008.golden+8 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf009.golden+9 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf010.golden+10 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf011.golden+11 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf012.golden+12 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf013.golden+13 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf014.golden+14 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf015.golden+15 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf016.golden+16 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf017.golden+17 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf018.golden+18 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf019.golden+19 internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf020.golden+20 internal/exp/diffview/testdata/TestDiffViewTabs/Split.golden+15 internal/exp/diffview/testdata/TestDiffViewTabs/Unified.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf001.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf002.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf003.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf004.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf005.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf006.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf007.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf008.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf009.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf010.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf011.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf012.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf013.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf014.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf015.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf016.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf017.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf018.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf019.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf020.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf021.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf022.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf023.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf024.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf025.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf026.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf027.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf028.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf029.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf030.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf031.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf032.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf033.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf034.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf035.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf036.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf037.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf038.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf039.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf040.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf041.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf042.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf043.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf044.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf045.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf046.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf047.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf048.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf049.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf050.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf051.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf052.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf053.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf054.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf055.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf056.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf057.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf058.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf059.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf060.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf061.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf062.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf063.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf064.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf065.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf066.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf067.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf068.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf069.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf070.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf071.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf072.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf073.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf074.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf075.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf076.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf077.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf078.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf079.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf080.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf081.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf082.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf083.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf084.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf085.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf086.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf087.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf088.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf089.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf090.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf091.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf092.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf093.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf094.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf095.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf096.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf097.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf098.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf099.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf100.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf101.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf102.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf103.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf104.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf105.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf106.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf107.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf108.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf109.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf110.golden+15 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf001.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf002.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf003.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf004.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf005.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf006.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf007.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf008.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf009.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf010.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf011.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf012.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf013.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf014.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf015.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf016.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf017.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf018.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf019.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf020.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf021.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf022.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf023.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf024.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf025.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf026.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf027.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf028.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf029.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf030.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf031.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf032.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf033.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf034.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf035.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf036.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf037.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf038.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf039.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf040.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf041.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf042.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf043.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf044.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf045.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf046.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf047.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf048.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf049.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf050.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf051.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf052.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf053.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf054.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf055.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf056.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf057.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf058.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf059.golden+16 internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf060.golden+16 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf00.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf01.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf02.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf03.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf04.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf05.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf06.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf07.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf08.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf09.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf10.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf11.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf12.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf13.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf14.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf15.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf16.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf17.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf18.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf19.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf20.golden+7 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf00.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf01.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf02.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf03.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf04.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf05.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf06.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf07.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf08.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf09.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf10.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf11.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf12.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf13.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf14.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf15.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf16.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf17.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf18.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf19.golden+8 internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf20.golden+8 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf00.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf01.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf02.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf03.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf04.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf05.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf06.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf07.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf08.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf09.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf10.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf11.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf12.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf13.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf14.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf15.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf16.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf00.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf01.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf02.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf03.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf04.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf05.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf06.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf07.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf08.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf09.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf10.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf11.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf12.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf13.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf14.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf15.golden+5 internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf16.golden+5 internal/exp/diffview/testdata/TestMultipleHunks.after+15 internal/exp/diffview/testdata/TestMultipleHunks.before+13 internal/exp/diffview/testdata/TestNarrow.after+3 internal/exp/diffview/testdata/TestNarrow.before+3 internal/exp/diffview/testdata/TestTabs.after+15 internal/exp/diffview/testdata/TestTabs.before+13 internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLines/Content.golden+11 internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLines/JSON.golden+40 internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusOne/Content.golden+12 internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusOne/JSON.golden+44 internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusTwo/Content.golden+13 internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusTwo/JSON.golden+48 internal/exp/diffview/testdata/TestUdiff/Unified.golden+11 internal/exp/diffview/udiff_test.go+92 internal/exp/diffview/util.go+39 internal/exp/diffview/util_test.go+27 internal/fileutil/fileutil.go → internal/fsext/fileutil.go+25−1 internal/fileutil/ls.go → internal/fsext/ls.go+1−1 internal/history/file.go+7−49 internal/llm/agent/agent.go+16−5 internal/llm/agent/tools.go−1 internal/llm/tools/edit.go+31−23 internal/llm/tools/fetch.go+15−49 internal/llm/tools/glob.go+4−4 internal/llm/tools/grep.go+13−12 internal/llm/tools/ls.go+2−2 internal/llm/tools/patch.go−372 internal/llm/tools/shell/comparison_test.go−30 internal/llm/tools/shell/shell_test.go−25 internal/llm/tools/sourcegraph.go+15−48 internal/lsp/watcher/watcher.go+25−9 internal/tui/components/chat/editor/editor.go+2−2 internal/tui/components/chat/messages/messages.go+1−1 internal/tui/components/chat/messages/renderer.go+12−44 internal/tui/components/chat/sidebar/sidebar.go+306−7 internal/tui/components/core/helpers.go+31−7 internal/tui/components/dialogs/commands/commands.go+2−1 internal/tui/components/dialogs/init/init.go+2−1 internal/tui/components/dialogs/permissions/permissions.go+10−23 internal/tui/page/chat/chat.go+1−1 internal/tui/styles/crush.go+1−1 internal/tui/styles/icons.go+3−2 internal/version/version.go+1−1 todos.md+8−2
cmd/root.go+1
@@ -256,6 +256,7 @@ func setupSubscriptions(app *app.App, parentCtx context.Context) (chan tea.Msg,
256256 setupSubscriber(ctx, &wg, "messages", app.Messages.Subscribe, ch)
257257 setupSubscriber(ctx, &wg, "permissions", app.Permissions.Subscribe, ch)
258258 setupSubscriber(ctx, &wg, "coderAgent", app.CoderAgent.Subscribe, ch)
259+ setupSubscriber(ctx, &wg, "history", app.History.Subscribe, ch)
259260
260261 cleanupFunc := func() {
261262 logging.Info("Cancelling all subscriptions")
cspell.json+1−1
@@ -1 +1 @@
1−{"flagWords":[],"language":"en","words":["crush","charmbracelet","lipgloss","bubbletea","textinput","Focusable","lsps","Sourcegraph","filepicker","imageorient","rasterx","oksvg","termenv","trashhalo","lucasb","nfnt","srwiley","Lanczos"],"version":"0.2"}
1+{"version":"0.2","language":"en","flagWords":[],"words":["crush","charmbracelet","lipgloss","bubbletea","textinput","Focusable","lsps","Sourcegraph","filepicker","imageorient","rasterx","oksvg","termenv","trashhalo","lucasb","nfnt","srwiley","Lanczos","fsext"]}
go.mod+3−2
@@ -9,7 +9,7 @@ require (
99 github.com/PuerkitoBio/goquery v1.9.2
1010 github.com/alecthomas/chroma/v2 v2.15.0
1111 github.com/anthropics/anthropic-sdk-go v1.4.0
12− github.com/aymanbagabas/go-udiff v0.2.0
12+ github.com/aymanbagabas/go-udiff v0.3.1
1313 github.com/bmatcuk/doublestar/v4 v4.8.1
1414 github.com/charlievieth/fastwalk v1.0.11
1515 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250607113720-eb5e1cf3b09e
@@ -18,6 +18,7 @@ require (
1818 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1.0.20250523195325-2d1af06b557c
1919 github.com/charmbracelet/x/ansi v0.9.3-0.20250602153603-fb931ed90413
2020 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250530202730-6ba1785cd7b9
21+ github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a
2122 github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec
2223 github.com/fsnotify/fsnotify v1.8.0
2324 github.com/go-logfmt/logfmt v0.6.0
@@ -65,7 +66,7 @@ require (
6566 github.com/aymerick/douceur v0.2.0 // indirect
6667 github.com/charmbracelet/colorprofile v0.3.1 // indirect
6768 github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa // indirect
68− github.com/charmbracelet/x/exp/slice v0.0.0-20250528180458-2d5d6cb84620
69+ github.com/charmbracelet/x/exp/slice v0.0.0-20250611152503-f53cdd7e01ef
6970 github.com/charmbracelet/x/input v0.3.5-0.20250509021451-13796e822d86 // indirect
7071 github.com/charmbracelet/x/term v0.2.1 // indirect
7172 github.com/charmbracelet/x/windows v0.2.1 // indirect
go.sum+4−4
@@ -60,8 +60,8 @@ github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
6060github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
6161github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
6262github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
63−github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
64−github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
63+github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
64+github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
6565github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
6666github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
6767github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
@@ -86,8 +86,8 @@ github.com/charmbracelet/x/exp/charmtone v0.0.0-20250530202730-6ba1785cd7b9 h1:f
8686github.com/charmbracelet/x/exp/charmtone v0.0.0-20250530202730-6ba1785cd7b9/go.mod h1:vr+xCFylsPYq2qSz+n5/jItjcK2/PgrKFMTI7VRR6CI=
8787github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a h1:FsHEJ52OC4VuTzU8t+n5frMjLvpYWEznSr/u8tnkCYw=
8888github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
89−github.com/charmbracelet/x/exp/slice v0.0.0-20250528180458-2d5d6cb84620 h1:/PN4jqP3ki9NvtHRrYZ9ewCutKZB6DK8frTW+Dj/MWs=
90−github.com/charmbracelet/x/exp/slice v0.0.0-20250528180458-2d5d6cb84620/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms=
89+github.com/charmbracelet/x/exp/slice v0.0.0-20250611152503-f53cdd7e01ef h1:v7qwsZ2OxzlwvpKwz8dtZXp7fIJlcDEUOyFBNE4fz4Q=
90+github.com/charmbracelet/x/exp/slice v0.0.0-20250611152503-f53cdd7e01ef/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms=
9191github.com/charmbracelet/x/input v0.3.5-0.20250509021451-13796e822d86 h1:BxAEmOBIDajkgao3EsbBxKQCYvgYPGdT62WASLvtf4Y=
9292github.com/charmbracelet/x/input v0.3.5-0.20250509021451-13796e822d86/go.mod h1:62Rp/6EtTxoeJDSdtpA3tJp3y3ZRpsiekBSje+K8htA=
9393github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
internal/db/db.go−10
@@ -78,9 +78,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
7878 if q.listSessionsStmt, err = db.PrepareContext(ctx, listSessions); err != nil {
7979 return nil, fmt.Errorf("error preparing query ListSessions: %w", err)
8080 }
81− if q.updateFileStmt, err = db.PrepareContext(ctx, updateFile); err != nil {
82− return nil, fmt.Errorf("error preparing query UpdateFile: %w", err)
83− }
8481 if q.updateMessageStmt, err = db.PrepareContext(ctx, updateMessage); err != nil {
8582 return nil, fmt.Errorf("error preparing query UpdateMessage: %w", err)
8683 }
@@ -182,11 +179,6 @@ func (q *Queries) Close() error {
182179 err = fmt.Errorf("error closing listSessionsStmt: %w", cerr)
183180 }
184181 }
185− if q.updateFileStmt != nil {
186− if cerr := q.updateFileStmt.Close(); cerr != nil {
187− err = fmt.Errorf("error closing updateFileStmt: %w", cerr)
188− }
189− }
190182 if q.updateMessageStmt != nil {
191183 if cerr := q.updateMessageStmt.Close(); cerr != nil {
192184 err = fmt.Errorf("error closing updateMessageStmt: %w", cerr)
@@ -254,7 +246,6 @@ type Queries struct {
254246 listMessagesBySessionStmt *sql.Stmt
255247 listNewFilesStmt *sql.Stmt
256248 listSessionsStmt *sql.Stmt
257− updateFileStmt *sql.Stmt
258249 updateMessageStmt *sql.Stmt
259250 updateSessionStmt *sql.Stmt
260251}
@@ -281,7 +272,6 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries {
281272 listMessagesBySessionStmt: q.listMessagesBySessionStmt,
282273 listNewFilesStmt: q.listNewFilesStmt,
283274 listSessionsStmt: q.listSessionsStmt,
284− updateFileStmt: q.updateFileStmt,
285275 updateMessageStmt: q.updateMessageStmt,
286276 updateSessionStmt: q.updateSessionStmt,
287277 }
internal/db/files.sql.go+7−38
@@ -29,7 +29,7 @@ type CreateFileParams struct {
2929 SessionID string `json:"session_id"`
3030 Path string `json:"path"`
3131 Content string `json:"content"`
32− Version string `json:"version"`
32+ Version int64 `json:"version"`
3333}
3434
3535func (q *Queries) CreateFile(ctx context.Context, arg CreateFileParams) (File, error) {
@@ -98,7 +98,7 @@ const getFileByPathAndSession = `-- name: GetFileByPathAndSession :one
9898SELECT id, session_id, path, content, version, created_at, updated_at
9999FROM files
100100WHERE path = ? AND session_id = ?
101−ORDER BY created_at DESC
101+ORDER BY version DESC, created_at DESC
102102LIMIT 1
103103`
104104
@@ -126,7 +126,7 @@ const listFilesByPath = `-- name: ListFilesByPath :many
126126SELECT id, session_id, path, content, version, created_at, updated_at
127127FROM files
128128WHERE path = ?
129−ORDER BY created_at DESC
129+ORDER BY version DESC, created_at DESC
130130`
131131
132132func (q *Queries) ListFilesByPath(ctx context.Context, path string) ([]File, error) {
@@ -164,7 +164,7 @@ const listFilesBySession = `-- name: ListFilesBySession :many
164164SELECT id, session_id, path, content, version, created_at, updated_at
165165FROM files
166166WHERE session_id = ?
167−ORDER BY created_at ASC
167+ORDER BY version ASC, created_at ASC
168168`
169169
170170func (q *Queries) ListFilesBySession(ctx context.Context, sessionID string) ([]File, error) {
@@ -202,10 +202,10 @@ const listLatestSessionFiles = `-- name: ListLatestSessionFiles :many
202202SELECT f.id, f.session_id, f.path, f.content, f.version, f.created_at, f.updated_at
203203FROM files f
204204INNER JOIN (
205− SELECT path, MAX(created_at) as max_created_at
205+ SELECT path, MAX(version) as max_version, MAX(created_at) as max_created_at
206206 FROM files
207207 GROUP BY path
208−) latest ON f.path = latest.path AND f.created_at = latest.max_created_at
208+) latest ON f.path = latest.path AND f.version = latest.max_version AND f.created_at = latest.max_created_at
209209WHERE f.session_id = ?
210210ORDER BY f.path
211211`
@@ -245,7 +245,7 @@ const listNewFiles = `-- name: ListNewFiles :many
245245SELECT id, session_id, path, content, version, created_at, updated_at
246246FROM files
247247WHERE is_new = 1
248−ORDER BY created_at DESC
248+ORDER BY version DESC, created_at DESC
249249`
250250
251251func (q *Queries) ListNewFiles(ctx context.Context) ([]File, error) {
@@ -278,34 +278,3 @@ func (q *Queries) ListNewFiles(ctx context.Context) ([]File, error) {
278278 }
279279 return items, nil
280280}
281−
282−const updateFile = `-- name: UpdateFile :one
283−UPDATE files
284−SET
285− content = ?,
286− version = ?,
287− updated_at = strftime('%s', 'now')
288−WHERE id = ?
289−RETURNING id, session_id, path, content, version, created_at, updated_at
290−`
291−
292−type UpdateFileParams struct {
293− Content string `json:"content"`
294− Version string `json:"version"`
295− ID string `json:"id"`
296−}
297−
298−func (q *Queries) UpdateFile(ctx context.Context, arg UpdateFileParams) (File, error) {
299− row := q.queryRow(ctx, q.updateFileStmt, updateFile, arg.Content, arg.Version, arg.ID)
300− var i File
301− err := row.Scan(
302− &i.ID,
303− &i.SessionID,
304− &i.Path,
305− &i.Content,
306− &i.Version,
307− &i.CreatedAt,
308− &i.UpdatedAt,
309− )
310− return i, err
311−}
internal/db/migrations/20250424200609_initial.sql+1−1
@@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS files (
2626 session_id TEXT NOT NULL,
2727 path TEXT NOT NULL,
2828 content TEXT NOT NULL,
29− version TEXT NOT NULL,
29+ version INTEGER NOT NULL DEFAULT 0,
3030 created_at INTEGER NOT NULL, -- Unix timestamp in milliseconds
3131 updated_at INTEGER NOT NULL, -- Unix timestamp in milliseconds
3232 FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE,
internal/db/models.go+1−1
@@ -13,7 +13,7 @@ type File struct {
1313 SessionID string `json:"session_id"`
1414 Path string `json:"path"`
1515 Content string `json:"content"`
16− Version string `json:"version"`
16+ Version int64 `json:"version"`
1717 CreatedAt int64 `json:"created_at"`
1818 UpdatedAt int64 `json:"updated_at"`
1919}
internal/db/querier.go−1
@@ -27,7 +27,6 @@ type Querier interface {
2727 ListMessagesBySession(ctx context.Context, sessionID string) ([]Message, error)
2828 ListNewFiles(ctx context.Context) ([]File, error)
2929 ListSessions(ctx context.Context) ([]Session, error)
30− UpdateFile(ctx context.Context, arg UpdateFileParams) (File, error)
3130 UpdateMessage(ctx context.Context, arg UpdateMessageParams) error
3231 UpdateSession(ctx context.Context, arg UpdateSessionParams) (Session, error)
3332}
internal/db/sql/files.sql+6−15
@@ -7,20 +7,20 @@ WHERE id = ? LIMIT 1;
77SELECT *
88FROM files
99WHERE path = ? AND session_id = ?
10−ORDER BY created_at DESC
10+ORDER BY version DESC, created_at DESC
1111LIMIT 1;
1212
1313-- name: ListFilesBySession :many
1414SELECT *
1515FROM files
1616WHERE session_id = ?
17−ORDER BY created_at ASC;
17+ORDER BY version ASC, created_at ASC;
1818
1919-- name: ListFilesByPath :many
2020SELECT *
2121FROM files
2222WHERE path = ?
23−ORDER BY created_at DESC;
23+ORDER BY version DESC, created_at DESC;
2424
2525-- name: CreateFile :one
2626INSERT INTO files (
@@ -36,15 +36,6 @@ INSERT INTO files (
3636)
3737RETURNING *;
3838
39−-- name: UpdateFile :one
40−UPDATE files
41−SET
42− content = ?,
43− version = ?,
44− updated_at = strftime('%s', 'now')
45−WHERE id = ?
46−RETURNING *;
47−
4839-- name: DeleteFile :exec
4940DELETE FROM files
5041WHERE id = ?;
@@ -57,10 +48,10 @@ WHERE session_id = ?;
5748SELECT f.*
5849FROM files f
5950INNER JOIN (
60− SELECT path, MAX(created_at) as max_created_at
51+ SELECT path, MAX(version) as max_version, MAX(created_at) as max_created_at
6152 FROM files
6253 GROUP BY path
63−) latest ON f.path = latest.path AND f.created_at = latest.max_created_at
54+) latest ON f.path = latest.path AND f.version = latest.max_version AND f.created_at = latest.max_created_at
6455WHERE f.session_id = ?
6556ORDER BY f.path;
6657
@@ -68,4 +59,4 @@ ORDER BY f.path;
6859SELECT *
6960FROM files
7061WHERE is_new = 1
71−ORDER BY created_at DESC;
62+ORDER BY version DESC, created_at DESC;
internal/exp/diffview/Taskfile.yamlnew file+102
@@ -0,0 +1,102 @@
1+version: "3"
2+
3+tasks:
4+ test:
5+ desc: Run tests
6+ cmds:
7+ - go test ./... {{.CLI_ARGS}}
8+
9+ test:update:
10+ desc: Run tests and update golden files
11+ cmds:
12+ - go test ./... -update {{.CLI_ARGS}}
13+
14+ test:print:
15+ desc: Print golden files for debugging
16+ method: none
17+ sources:
18+ - ./testdata/TestDiffView/**/*.golden
19+ cmds:
20+ - for: sources
21+ cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
22+ silent: true
23+
24+ test:print:width:unified:
25+ desc: Print golden files for debugging
26+ method: none
27+ sources:
28+ - ./testdata/TestDiffViewWidth/Unified/*.golden
29+ cmds:
30+ - for: sources
31+ cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
32+ silent: true
33+
34+ test:print:width:split:
35+ desc: Print golden files for debugging
36+ method: none
37+ sources:
38+ - ./testdata/TestDiffViewWidth/Split/*.golden
39+ cmds:
40+ - for: sources
41+ cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
42+ silent: true
43+
44+ test:print:height:unified:
45+ desc: Print golden files for debugging
46+ method: none
47+ sources:
48+ - ./testdata/TestDiffViewHeight/Unified/*.golden
49+ cmds:
50+ - for: sources
51+ cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
52+ silent: true
53+
54+ test:print:height:split:
55+ desc: Print golden files for debugging
56+ method: none
57+ sources:
58+ - ./testdata/TestDiffViewHeight/Split/*.golden
59+ cmds:
60+ - for: sources
61+ cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
62+ silent: true
63+
64+ test:print:xoffset:unified:
65+ desc: Print golden files for debugging
66+ method: none
67+ sources:
68+ - ./testdata/TestDiffViewXOffset/Unified/*.golden
69+ cmds:
70+ - for: sources
71+ cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
72+ silent: true
73+
74+ test:print:xoffset:split:
75+ desc: Print golden files for debugging
76+ method: none
77+ sources:
78+ - ./testdata/TestDiffViewXOffset/Split/*.golden
79+ cmds:
80+ - for: sources
81+ cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
82+ silent: true
83+
84+ test:print:yoffset:unified:
85+ desc: Print golden files for debugging
86+ method: none
87+ sources:
88+ - ./testdata/TestDiffViewYOffset/Unified/*.golden
89+ cmds:
90+ - for: sources
91+ cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
92+ silent: true
93+
94+ test:print:yoffset:split:
95+ desc: Print golden files for debugging
96+ method: none
97+ sources:
98+ - ./testdata/TestDiffViewYOffset/Split/*.golden
99+ cmds:
100+ - for: sources
101+ cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
102+ silent: true
internal/exp/diffview/chroma.gonew file+52
@@ -0,0 +1,52 @@
1+package diffview
2+
3+import (
4+ "fmt"
5+ "image/color"
6+ "io"
7+
8+ "github.com/alecthomas/chroma/v2"
9+ "github.com/charmbracelet/lipgloss/v2"
10+)
11+
12+var _ chroma.Formatter = chromaFormatter{}
13+
14+// chromaFormatter is a custom formatter for Chroma that uses Lip Gloss for
15+// foreground styling, while keeping a forced background color.
16+type chromaFormatter struct {
17+ bgColor color.Color
18+}
19+
20+// Format implements the chroma.Formatter interface.
21+func (c chromaFormatter) Format(w io.Writer, style *chroma.Style, it chroma.Iterator) error {
22+ for token := it(); token != chroma.EOF; token = it() {
23+ entry := style.Get(token.Type)
24+ if entry.IsZero() {
25+ if _, err := fmt.Fprint(w, token.Value); err != nil {
26+ return err
27+ }
28+ continue
29+ }
30+
31+ s := lipgloss.NewStyle().
32+ Background(c.bgColor)
33+
34+ if entry.Bold == chroma.Yes {
35+ s = s.Bold(true)
36+ }
37+ if entry.Underline == chroma.Yes {
38+ s = s.Underline(true)
39+ }
40+ if entry.Italic == chroma.Yes {
41+ s = s.Italic(true)
42+ }
43+ if entry.Colour.IsSet() {
44+ s = s.Foreground(lipgloss.Color(entry.Colour.String()))
45+ }
46+
47+ if _, err := fmt.Fprint(w, s.Render(token.Value)); err != nil {
48+ return err
49+ }
50+ }
51+ return nil
52+}
internal/exp/diffview/diffview.gonew file+681
@@ -0,0 +1,681 @@
1+package diffview
2+
3+import (
4+ "fmt"
5+ "image/color"
6+ "strconv"
7+ "strings"
8+
9+ "github.com/alecthomas/chroma/v2"
10+ "github.com/alecthomas/chroma/v2/lexers"
11+ "github.com/aymanbagabas/go-udiff"
12+ "github.com/aymanbagabas/go-udiff/myers"
13+ "github.com/charmbracelet/lipgloss/v2"
14+ "github.com/charmbracelet/x/ansi"
15+)
16+
17+const (
18+ leadingSymbolsSize = 2
19+ lineNumPadding = 1
20+)
21+
22+type file struct {
23+ path string
24+ content string
25+}
26+
27+type layout int
28+
29+const (
30+ layoutUnified layout = iota + 1
31+ layoutSplit
32+)
33+
34+// DiffView represents a view for displaying differences between two files.
35+type DiffView struct {
36+ layout layout
37+ before file
38+ after file
39+ contextLines int
40+ lineNumbers bool
41+ highlight bool
42+ height int
43+ width int
44+ xOffset int
45+ yOffset int
46+ style Style
47+ tabWidth int
48+ chromaStyle *chroma.Style
49+
50+ isComputed bool
51+ err error
52+ unified udiff.UnifiedDiff
53+ edits []udiff.Edit
54+
55+ splitHunks []splitHunk
56+
57+ codeWidth int
58+ fullCodeWidth int // with leading symbols
59+ extraColOnAfter bool // add extra column on after panel
60+ beforeNumDigits int
61+ afterNumDigits int
62+}
63+
64+// New creates a new DiffView with default settings.
65+func New() *DiffView {
66+ dv := &DiffView{
67+ layout: layoutUnified,
68+ contextLines: udiff.DefaultContextLines,
69+ lineNumbers: true,
70+ tabWidth: 8,
71+ }
72+ dv.style = DefaultDarkStyle()
73+ return dv
74+}
75+
76+// Unified sets the layout of the DiffView to unified.
77+func (dv *DiffView) Unified() *DiffView {
78+ dv.layout = layoutUnified
79+ return dv
80+}
81+
82+// Split sets the layout of the DiffView to split (side-by-side).
83+func (dv *DiffView) Split() *DiffView {
84+ dv.layout = layoutSplit
85+ return dv
86+}
87+
88+// Before sets the "before" file for the DiffView.
89+func (dv *DiffView) Before(path, content string) *DiffView {
90+ dv.before = file{path: path, content: content}
91+ return dv
92+}
93+
94+// After sets the "after" file for the DiffView.
95+func (dv *DiffView) After(path, content string) *DiffView {
96+ dv.after = file{path: path, content: content}
97+ return dv
98+}
99+
100+// ContextLines sets the number of context lines for the DiffView.
101+func (dv *DiffView) ContextLines(contextLines int) *DiffView {
102+ dv.contextLines = contextLines
103+ return dv
104+}
105+
106+// Style sets the style for the DiffView.
107+func (dv *DiffView) Style(style Style) *DiffView {
108+ dv.style = style
109+ return dv
110+}
111+
112+// LineNumbers sets whether to display line numbers in the DiffView.
113+func (dv *DiffView) LineNumbers(lineNumbers bool) *DiffView {
114+ dv.lineNumbers = lineNumbers
115+ return dv
116+}
117+
118+// SyntaxHightlight sets whether to enable syntax highlighting in the DiffView.
119+func (dv *DiffView) SyntaxHightlight(highlight bool) *DiffView {
120+ dv.highlight = highlight
121+ return dv
122+}
123+
124+// Height sets the height of the DiffView.
125+func (dv *DiffView) Height(height int) *DiffView {
126+ dv.height = height
127+ return dv
128+}
129+
130+// Width sets the width of the DiffView.
131+func (dv *DiffView) Width(width int) *DiffView {
132+ dv.width = width
133+ return dv
134+}
135+
136+// XOffset sets the horizontal offset for the DiffView.
137+func (dv *DiffView) XOffset(xOffset int) *DiffView {
138+ dv.xOffset = xOffset
139+ return dv
140+}
141+
142+// YOffset sets the vertical offset for the DiffView.
143+func (dv *DiffView) YOffset(yOffset int) *DiffView {
144+ dv.yOffset = yOffset
145+ return dv
146+}
147+
148+// TabWidth sets the tab width. Only relevant for code that contains tabs, like
149+// Go code.
150+func (dv *DiffView) TabWidth(tabWidth int) *DiffView {
151+ dv.tabWidth = tabWidth
152+ return dv
153+}
154+
155+// ChromaStyle sets the chroma style for syntax highlighting.
156+// If nil, no syntax highlighting will be applied.
157+func (dv *DiffView) ChromaStyle(style *chroma.Style) *DiffView {
158+ dv.chromaStyle = style
159+ return dv
160+}
161+
162+// String returns the string representation of the DiffView.
163+func (dv *DiffView) String() string {
164+ dv.replaceTabs()
165+ if err := dv.computeDiff(); err != nil {
166+ return err.Error()
167+ }
168+ dv.convertDiffToSplit()
169+ dv.adjustStyles()
170+ dv.detectNumDigits()
171+
172+ if dv.width <= 0 {
173+ dv.detectCodeWidth()
174+ } else {
175+ dv.resizeCodeWidth()
176+ }
177+
178+ style := lipgloss.NewStyle()
179+ if dv.width > 0 {
180+ style = style.MaxWidth(dv.width)
181+ }
182+ if dv.height > 0 {
183+ style = style.MaxHeight(dv.height)
184+ }
185+
186+ switch dv.layout {
187+ case layoutUnified:
188+ return style.Render(strings.TrimSuffix(dv.renderUnified(), "\n"))
189+ case layoutSplit:
190+ return style.Render(strings.TrimSuffix(dv.renderSplit(), "\n"))
191+ default:
192+ panic("unknown diffview layout")
193+ }
194+}
195+
196+// replaceTabs replaces tabs in the before and after file contents with spaces
197+// according to the specified tab width.
198+func (dv *DiffView) replaceTabs() {
199+ spaces := strings.Repeat(" ", dv.tabWidth)
200+ dv.before.content = strings.ReplaceAll(dv.before.content, "\t", spaces)
201+ dv.after.content = strings.ReplaceAll(dv.after.content, "\t", spaces)
202+}
203+
204+// computeDiff computes the differences between the "before" and "after" files.
205+func (dv *DiffView) computeDiff() error {
206+ if dv.isComputed {
207+ return dv.err
208+ }
209+ dv.isComputed = true
210+ dv.edits = myers.ComputeEdits( //nolint:staticcheck
211+ dv.before.content,
212+ dv.after.content,
213+ )
214+ dv.unified, dv.err = udiff.ToUnifiedDiff(
215+ dv.before.path,
216+ dv.after.path,
217+ dv.before.content,
218+ dv.edits,
219+ dv.contextLines,
220+ )
221+ return dv.err
222+}
223+
224+// convertDiffToSplit converts the unified diff to a split diff if the layout is
225+// set to split.
226+func (dv *DiffView) convertDiffToSplit() {
227+ if dv.layout != layoutSplit {
228+ return
229+ }
230+
231+ dv.splitHunks = make([]splitHunk, len(dv.unified.Hunks))
232+ for i, h := range dv.unified.Hunks {
233+ dv.splitHunks[i] = hunkToSplit(h)
234+ }
235+}
236+
237+// adjustStyles adjusts adds padding and alignment to the styles.
238+func (dv *DiffView) adjustStyles() {
239+ setPadding := func(s lipgloss.Style) lipgloss.Style {
240+ return s.Padding(0, lineNumPadding).Align(lipgloss.Right)
241+ }
242+ dv.style.MissingLine.LineNumber = setPadding(dv.style.MissingLine.LineNumber)
243+ dv.style.DividerLine.LineNumber = setPadding(dv.style.DividerLine.LineNumber)
244+ dv.style.EqualLine.LineNumber = setPadding(dv.style.EqualLine.LineNumber)
245+ dv.style.InsertLine.LineNumber = setPadding(dv.style.InsertLine.LineNumber)
246+ dv.style.DeleteLine.LineNumber = setPadding(dv.style.DeleteLine.LineNumber)
247+}
248+
249+// detectNumDigits calculates the maximum number of digits needed for before and
250+// after line numbers.
251+func (dv *DiffView) detectNumDigits() {
252+ dv.beforeNumDigits = 0
253+ dv.afterNumDigits = 0
254+
255+ for _, h := range dv.unified.Hunks {
256+ dv.beforeNumDigits = max(dv.beforeNumDigits, len(strconv.Itoa(h.FromLine+len(h.Lines))))
257+ dv.afterNumDigits = max(dv.afterNumDigits, len(strconv.Itoa(h.ToLine+len(h.Lines))))
258+ }
259+}
260+
261+// detectCodeWidth calculates the maximum width of code lines in the diff view.
262+func (dv *DiffView) detectCodeWidth() {
263+ switch dv.layout {
264+ case layoutUnified:
265+ dv.detectUnifiedCodeWidth()
266+ case layoutSplit:
267+ dv.detectSplitCodeWidth()
268+ }
269+ dv.fullCodeWidth = dv.codeWidth + leadingSymbolsSize
270+}
271+
272+// detectUnifiedCodeWidth calculates the maximum width of code lines in a
273+// unified diff.
274+func (dv *DiffView) detectUnifiedCodeWidth() {
275+ dv.codeWidth = 0
276+
277+ for _, h := range dv.unified.Hunks {
278+ shownLines := ansi.StringWidth(dv.hunkLineFor(h))
279+
280+ for _, l := range h.Lines {
281+ lineWidth := ansi.StringWidth(strings.TrimSuffix(l.Content, "\n")) + 1
282+ dv.codeWidth = max(dv.codeWidth, lineWidth, shownLines)
283+ }
284+ }
285+}
286+
287+// detectSplitCodeWidth calculates the maximum width of code lines in a
288+// split diff.
289+func (dv *DiffView) detectSplitCodeWidth() {
290+ dv.codeWidth = 0
291+
292+ for i, h := range dv.splitHunks {
293+ shownLines := ansi.StringWidth(dv.hunkLineFor(dv.unified.Hunks[i]))
294+
295+ for _, l := range h.lines {
296+ if l.before != nil {
297+ codeWidth := ansi.StringWidth(strings.TrimSuffix(l.before.Content, "\n")) + 1
298+ dv.codeWidth = max(dv.codeWidth, codeWidth, shownLines)
299+ }
300+ if l.after != nil {
301+ codeWidth := ansi.StringWidth(strings.TrimSuffix(l.after.Content, "\n")) + 1
302+ dv.codeWidth = max(dv.codeWidth, codeWidth, shownLines)
303+ }
304+ }
305+ }
306+}
307+
308+// resizeCodeWidth resizes the code width to fit within the specified width.
309+func (dv *DiffView) resizeCodeWidth() {
310+ fullNumWidth := dv.beforeNumDigits + dv.afterNumDigits
311+ fullNumWidth += lineNumPadding * 4 // left and right padding for both line numbers
312+
313+ switch dv.layout {
314+ case layoutUnified:
315+ dv.codeWidth = dv.width - fullNumWidth - leadingSymbolsSize
316+ case layoutSplit:
317+ remainingWidth := dv.width - fullNumWidth - leadingSymbolsSize*2
318+ dv.codeWidth = remainingWidth / 2
319+ dv.extraColOnAfter = isOdd(remainingWidth)
320+ }
321+
322+ dv.fullCodeWidth = dv.codeWidth + leadingSymbolsSize
323+}
324+
325+// renderUnified renders the unified diff view as a string.
326+func (dv *DiffView) renderUnified() string {
327+ var b strings.Builder
328+
329+ fullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth)
330+ printedLines := -dv.yOffset
331+
332+ write := func(s string) {
333+ if printedLines >= 0 {
334+ b.WriteString(s)
335+ }
336+ }
337+
338+outer:
339+ for i, h := range dv.unified.Hunks {
340+ ls := dv.style.DividerLine
341+ if dv.lineNumbers {
342+ write(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
343+ write(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
344+ }
345+ content := ansi.Truncate(dv.hunkLineFor(h), dv.fullCodeWidth, "…")
346+ write(ls.Code.Width(dv.fullCodeWidth).Render(content))
347+ write("\n")
348+ printedLines++
349+
350+ beforeLine := h.FromLine
351+ afterLine := h.ToLine
352+
353+ for j, l := range h.Lines {
354+ // print ellipis if we don't have enough space to print the rest of the diff
355+ hasReachedHeight := dv.height > 0 && printedLines+1 == dv.height
356+ isLastHunk := i+1 == len(dv.unified.Hunks)
357+ isLastLine := j+1 == len(h.Lines)
358+ if hasReachedHeight && (!isLastHunk || !isLastLine) {
359+ ls := dv.lineStyleForType(l.Kind)
360+ if dv.lineNumbers {
361+ write(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
362+ write(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
363+ }
364+ write(fullContentStyle.Render(
365+ ls.Code.Width(dv.fullCodeWidth).Render(" …"),
366+ ))
367+ write("\n")
368+ break outer
369+ }
370+
371+ getContent := func(ls LineStyle) string {
372+ content := strings.TrimSuffix(l.Content, "\n")
373+ content = dv.hightlightCode(content, ls.Code.GetBackground())
374+ content = ansi.GraphemeWidth.Cut(content, dv.xOffset, len(content))
375+ content = ansi.Truncate(content, dv.codeWidth, "…")
376+ return content
377+ }
378+
379+ leadingEllipsis := dv.xOffset > 0 && strings.TrimSpace(content) != ""
380+
381+ switch l.Kind {
382+ case udiff.Equal:
383+ ls := dv.style.EqualLine
384+ content := getContent(ls)
385+ if dv.lineNumbers {
386+ write(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
387+ write(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
388+ }
389+ write(fullContentStyle.Render(
390+ ls.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", " ") + content),
391+ ))
392+ beforeLine++
393+ afterLine++
394+ case udiff.Insert:
395+ ls := dv.style.InsertLine
396+ content := getContent(ls)
397+ if dv.lineNumbers {
398+ write(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
399+ write(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
400+ }
401+ write(fullContentStyle.Render(
402+ ls.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) +
403+ ls.Code.Width(dv.codeWidth).Render(content),
404+ ))
405+ afterLine++
406+ case udiff.Delete:
407+ ls := dv.style.DeleteLine
408+ content := getContent(ls)
409+ if dv.lineNumbers {
410+ write(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
411+ write(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
412+ }
413+ write(fullContentStyle.Render(
414+ ls.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) +
415+ ls.Code.Width(dv.codeWidth).Render(content),
416+ ))
417+ beforeLine++
418+ }
419+ write("\n")
420+
421+ printedLines++
422+ }
423+ }
424+
425+ for printedLines < dv.height {
426+ ls := dv.style.MissingLine
427+ if dv.lineNumbers {
428+ write(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
429+ write(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
430+ }
431+ write(ls.Code.Width(dv.fullCodeWidth).Render(" "))
432+ write("\n")
433+ printedLines++
434+ }
435+
436+ return b.String()
437+}
438+
439+// renderSplit renders the split (side-by-side) diff view as a string.
440+func (dv *DiffView) renderSplit() string {
441+ var b strings.Builder
442+
443+ beforeFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth)
444+ afterFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth + btoi(dv.extraColOnAfter))
445+ printedLines := -dv.yOffset
446+
447+ write := func(s string) {
448+ if printedLines >= 0 {
449+ b.WriteString(s)
450+ }
451+ }
452+
453+outer:
454+ for i, h := range dv.splitHunks {
455+ ls := dv.style.DividerLine
456+ if dv.lineNumbers {
457+ write(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
458+ }
459+ content := ansi.Truncate(dv.hunkLineFor(dv.unified.Hunks[i]), dv.fullCodeWidth, "…")
460+ write(ls.Code.Width(dv.fullCodeWidth).Render(content))
461+ if dv.lineNumbers {
462+ write(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
463+ }
464+ write(ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
465+ write("\n")
466+ printedLines++
467+
468+ beforeLine := h.fromLine
469+ afterLine := h.toLine
470+
471+ for j, l := range h.lines {
472+ // print ellipis if we don't have enough space to print the rest of the diff
473+ hasReachedHeight := dv.height > 0 && printedLines+1 == dv.height
474+ isLastHunk := i+1 == len(dv.unified.Hunks)
475+ isLastLine := j+1 == len(h.lines)
476+ if hasReachedHeight && (!isLastHunk || !isLastLine) {
477+ ls := dv.style.MissingLine
478+ if l.before != nil {
479+ ls = dv.lineStyleForType(l.before.Kind)
480+ }
481+ if dv.lineNumbers {
482+ write(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
483+ }
484+ write(beforeFullContentStyle.Render(
485+ ls.Code.Width(dv.fullCodeWidth).Render(" …"),
486+ ))
487+ ls = dv.style.MissingLine
488+ if l.after != nil {
489+ ls = dv.lineStyleForType(l.after.Kind)
490+ }
491+ if dv.lineNumbers {
492+ write(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
493+ }
494+ write(afterFullContentStyle.Render(
495+ ls.Code.Width(dv.fullCodeWidth).Render(" …"),
496+ ))
497+ write("\n")
498+ break outer
499+ }
500+
501+ getContent := func(content string, ls LineStyle) string {
502+ content = strings.TrimSuffix(content, "\n")
503+ content = dv.hightlightCode(content, ls.Code.GetBackground())
504+ content = ansi.GraphemeWidth.Cut(content, dv.xOffset, len(content))
505+ content = ansi.Truncate(content, dv.codeWidth, "…")
506+ return content
507+ }
508+ getLeadingEllipsis := func(content string) bool {
509+ return dv.xOffset > 0 && strings.TrimSpace(content) != ""
510+ }
511+
512+ switch {
513+ case l.before == nil:
514+ ls := dv.style.MissingLine
515+ if dv.lineNumbers {
516+ write(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
517+ }
518+ write(beforeFullContentStyle.Render(
519+ ls.Code.Width(dv.fullCodeWidth).Render(" "),
520+ ))
521+ case l.before.Kind == udiff.Equal:
522+ ls := dv.style.EqualLine
523+ content := getContent(l.before.Content, ls)
524+ leadingEllipsis := getLeadingEllipsis(content)
525+ if dv.lineNumbers {
526+ write(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
527+ }
528+ write(beforeFullContentStyle.Render(
529+ ls.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", " ") + content),
530+ ))
531+ beforeLine++
532+ case l.before.Kind == udiff.Delete:
533+ ls := dv.style.DeleteLine
534+ content := getContent(l.before.Content, ls)
535+ leadingEllipsis := getLeadingEllipsis(content)
536+ if dv.lineNumbers {
537+ write(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
538+ }
539+ write(beforeFullContentStyle.Render(
540+ ls.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) +
541+ ls.Code.Width(dv.codeWidth).Render(content),
542+ ))
543+ beforeLine++
544+ }
545+
546+ switch {
547+ case l.after == nil:
548+ ls := dv.style.MissingLine
549+ if dv.lineNumbers {
550+ write(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
551+ }
552+ write(afterFullContentStyle.Render(
553+ ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "),
554+ ))
555+ case l.after.Kind == udiff.Equal:
556+ ls := dv.style.EqualLine
557+ content := getContent(l.after.Content, ls)
558+ leadingEllipsis := getLeadingEllipsis(content)
559+ if dv.lineNumbers {
560+ write(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
561+ }
562+ write(afterFullContentStyle.Render(
563+ ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(ternary(leadingEllipsis, " …", " ") + content),
564+ ))
565+ afterLine++
566+ case l.after.Kind == udiff.Insert:
567+ ls := dv.style.InsertLine
568+ content := getContent(l.after.Content, ls)
569+ leadingEllipsis := getLeadingEllipsis(content)
570+ if dv.lineNumbers {
571+ write(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
572+ }
573+ write(afterFullContentStyle.Render(
574+ ls.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) +
575+ ls.Code.Width(dv.codeWidth+btoi(dv.extraColOnAfter)).Render(content),
576+ ))
577+ afterLine++
578+ }
579+
580+ write("\n")
581+
582+ printedLines++
583+ }
584+ }
585+
586+ for printedLines < dv.height {
587+ ls := dv.style.MissingLine
588+ if dv.lineNumbers {
589+ write(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
590+ }
591+ write(ls.Code.Width(dv.fullCodeWidth).Render(" "))
592+ if dv.lineNumbers {
593+ write(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
594+ }
595+ write(ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
596+ write("\n")
597+ printedLines++
598+ }
599+
600+ return b.String()
601+}
602+
603+// hunkLineFor formats the header line for a hunk in the unified diff view.
604+func (dv *DiffView) hunkLineFor(h *udiff.Hunk) string {
605+ beforeShownLines, afterShownLines := dv.hunkShownLines(h)
606+
607+ return fmt.Sprintf(
608+ " @@ -%d,%d +%d,%d @@ ",
609+ h.FromLine,
610+ beforeShownLines,
611+ h.ToLine,
612+ afterShownLines,
613+ )
614+}
615+
616+// hunkShownLines calculates the number of lines shown in a hunk for both before
617+// and after versions.
618+func (dv *DiffView) hunkShownLines(h *udiff.Hunk) (before, after int) {
619+ for _, l := range h.Lines {
620+ switch l.Kind {
621+ case udiff.Equal:
622+ before++
623+ after++
624+ case udiff.Insert:
625+ after++
626+ case udiff.Delete:
627+ before++
628+ }
629+ }
630+ return
631+}
632+
633+func (dv *DiffView) lineStyleForType(t udiff.OpKind) LineStyle {
634+ switch t {
635+ case udiff.Equal:
636+ return dv.style.EqualLine
637+ case udiff.Insert:
638+ return dv.style.InsertLine
639+ case udiff.Delete:
640+ return dv.style.DeleteLine
641+ default:
642+ return dv.style.MissingLine
643+ }
644+}
645+
646+func (dv *DiffView) hightlightCode(source string, bgColor color.Color) string {
647+ if dv.chromaStyle == nil {
648+ return source
649+ }
650+
651+ l := dv.getChromaLexer(source)
652+ f := dv.getChromaFormatter(bgColor)
653+
654+ it, err := l.Tokenise(nil, source)
655+ if err != nil {
656+ return source
657+ }
658+
659+ var b strings.Builder
660+ if err := f.Format(&b, dv.chromaStyle, it); err != nil {
661+ return source
662+ }
663+ return b.String()
664+}
665+
666+func (dv *DiffView) getChromaLexer(source string) chroma.Lexer {
667+ l := lexers.Match(dv.before.path)
668+ if l == nil {
669+ l = lexers.Analyse(source)
670+ }
671+ if l == nil {
672+ l = lexers.Fallback
673+ }
674+ return chroma.Coalesce(l)
675+}
676+
677+func (dv *DiffView) getChromaFormatter(gbColor color.Color) chroma.Formatter {
678+ return chromaFormatter{
679+ bgColor: gbColor,
680+ }
681+}
internal/exp/diffview/diffview_test.gonew file+300
@@ -0,0 +1,300 @@
1+package diffview_test
2+
3+import (
4+ _ "embed"
5+ "fmt"
6+ "strings"
7+ "testing"
8+
9+ "github.com/alecthomas/chroma/v2/styles"
10+ "github.com/charmbracelet/crush/internal/exp/diffview"
11+ "github.com/charmbracelet/x/ansi"
12+ "github.com/charmbracelet/x/exp/golden"
13+)
14+
15+//go:embed testdata/TestDefault.before
16+var TestDefaultBefore string
17+
18+//go:embed testdata/TestDefault.after
19+var TestDefaultAfter string
20+
21+//go:embed testdata/TestMultipleHunks.before
22+var TestMultipleHunksBefore string
23+
24+//go:embed testdata/TestMultipleHunks.after
25+var TestMultipleHunksAfter string
26+
27+//go:embed testdata/TestNarrow.before
28+var TestNarrowBefore string
29+
30+//go:embed testdata/TestNarrow.after
31+var TestNarrowAfter string
32+
33+//go:embed testdata/TestTabs.before
34+var TestTabsBefore string
35+
36+//go:embed testdata/TestTabs.after
37+var TestTabsAfter string
38+
39+type (
40+ TestFunc func(dv *diffview.DiffView) *diffview.DiffView
41+ TestFuncs map[string]TestFunc
42+)
43+
44+var (
45+ UnifiedFunc = func(dv *diffview.DiffView) *diffview.DiffView {
46+ return dv.Unified()
47+ }
48+ SplitFunc = func(dv *diffview.DiffView) *diffview.DiffView {
49+ return dv.Split()
50+ }
51+
52+ DefaultFunc = func(dv *diffview.DiffView) *diffview.DiffView {
53+ return dv.
54+ Before("main.go", TestDefaultBefore).
55+ After("main.go", TestDefaultAfter)
56+ }
57+ NoLineNumbersFunc = func(dv *diffview.DiffView) *diffview.DiffView {
58+ return dv.
59+ Before("main.go", TestDefaultBefore).
60+ After("main.go", TestDefaultAfter).
61+ LineNumbers(false)
62+ }
63+ MultipleHunksFunc = func(dv *diffview.DiffView) *diffview.DiffView {
64+ return dv.
65+ Before("main.go", TestMultipleHunksBefore).
66+ After("main.go", TestMultipleHunksAfter)
67+ }
68+ CustomContextLinesFunc = func(dv *diffview.DiffView) *diffview.DiffView {
69+ return dv.
70+ Before("main.go", TestMultipleHunksBefore).
71+ After("main.go", TestMultipleHunksAfter).
72+ ContextLines(4)
73+ }
74+ NarrowFunc = func(dv *diffview.DiffView) *diffview.DiffView {
75+ return dv.
76+ Before("text.txt", TestNarrowBefore).
77+ After("text.txt", TestNarrowAfter)
78+ }
79+ SmallWidthFunc = func(dv *diffview.DiffView) *diffview.DiffView {
80+ return dv.
81+ Before("main.go", TestMultipleHunksBefore).
82+ After("main.go", TestMultipleHunksAfter).
83+ Width(40)
84+ }
85+ LargeWidthFunc = func(dv *diffview.DiffView) *diffview.DiffView {
86+ return dv.
87+ Before("main.go", TestMultipleHunksBefore).
88+ After("main.go", TestMultipleHunksAfter).
89+ Width(120)
90+ }
91+ NoSyntaxHighlightFunc = func(dv *diffview.DiffView) *diffview.DiffView {
92+ return dv.
93+ Before("main.go", TestMultipleHunksBefore).
94+ After("main.go", TestMultipleHunksAfter).
95+ ChromaStyle(nil)
96+ }
97+
98+ LightModeFunc = func(dv *diffview.DiffView) *diffview.DiffView {
99+ return dv.
100+ Style(diffview.DefaultLightStyle()).
101+ ChromaStyle(styles.Get("catppuccin-latte"))
102+ }
103+ DarkModeFunc = func(dv *diffview.DiffView) *diffview.DiffView {
104+ return dv.
105+ Style(diffview.DefaultDarkStyle()).
106+ ChromaStyle(styles.Get("catppuccin-macchiato"))
107+ }
108+
109+ LayoutFuncs = TestFuncs{
110+ "Unified": UnifiedFunc,
111+ "Split": SplitFunc,
112+ }
113+ BehaviorFuncs = TestFuncs{
114+ "Default": DefaultFunc,
115+ "NoLineNumbers": NoLineNumbersFunc,
116+ "MultipleHunks": MultipleHunksFunc,
117+ "CustomContextLines": CustomContextLinesFunc,
118+ "Narrow": NarrowFunc,
119+ "SmallWidth": SmallWidthFunc,
120+ "LargeWidth": LargeWidthFunc,
121+ "NoSyntaxHighlight": NoSyntaxHighlightFunc,
122+ }
123+ ThemeFuncs = TestFuncs{
124+ "LightMode": LightModeFunc,
125+ "DarkMode": DarkModeFunc,
126+ }
127+)
128+
129+func TestDiffView(t *testing.T) {
130+ for layoutName, layoutFunc := range LayoutFuncs {
131+ t.Run(layoutName, func(t *testing.T) {
132+ for behaviorName, behaviorFunc := range BehaviorFuncs {
133+ t.Run(behaviorName, func(t *testing.T) {
134+ for themeName, themeFunc := range ThemeFuncs {
135+ t.Run(themeName, func(t *testing.T) {
136+ t.Parallel()
137+
138+ dv := diffview.New()
139+ dv = layoutFunc(dv)
140+ dv = themeFunc(dv)
141+ dv = behaviorFunc(dv)
142+
143+ output := dv.String()
144+ golden.RequireEqual(t, []byte(output))
145+
146+ switch behaviorName {
147+ case "SmallWidth":
148+ assertLineWidth(t, 40, output)
149+ case "LargeWidth":
150+ assertLineWidth(t, 120, output)
151+ }
152+ })
153+ }
154+ })
155+ }
156+ })
157+ }
158+}
159+
160+func TestDiffViewTabs(t *testing.T) {
161+ for layoutName, layoutFunc := range LayoutFuncs {
162+ t.Run(layoutName, func(t *testing.T) {
163+ t.Parallel()
164+
165+ dv := diffview.New().
166+ Before("main.go", TestTabsBefore).
167+ After("main.go", TestTabsAfter).
168+ Style(diffview.DefaultLightStyle()).
169+ ChromaStyle(styles.Get("catppuccin-latte"))
170+ dv = layoutFunc(dv)
171+
172+ output := dv.String()
173+ golden.RequireEqual(t, []byte(output))
174+ })
175+ }
176+}
177+
178+func TestDiffViewWidth(t *testing.T) {
179+ for layoutName, layoutFunc := range LayoutFuncs {
180+ t.Run(layoutName, func(t *testing.T) {
181+ for width := 1; width <= 110; width++ {
182+ if layoutName == "Unified" && width > 60 {
183+ continue
184+ }
185+
186+ t.Run(fmt.Sprintf("WidthOf%03d", width), func(t *testing.T) {
187+ t.Parallel()
188+
189+ dv := diffview.New().
190+ Before("main.go", TestMultipleHunksBefore).
191+ After("main.go", TestMultipleHunksAfter).
192+ Width(width).
193+ Style(diffview.DefaultLightStyle()).
194+ ChromaStyle(styles.Get("catppuccin-latte"))
195+ dv = layoutFunc(dv)
196+
197+ output := dv.String()
198+ golden.RequireEqual(t, []byte(output))
199+
200+ assertLineWidth(t, width, output)
201+ })
202+ }
203+ })
204+ }
205+}
206+
207+func TestDiffViewHeight(t *testing.T) {
208+ for layoutName, layoutFunc := range LayoutFuncs {
209+ t.Run(layoutName, func(t *testing.T) {
210+ for height := 1; height <= 20; height++ {
211+ t.Run(fmt.Sprintf("HeightOf%03d", height), func(t *testing.T) {
212+ t.Parallel()
213+
214+ dv := diffview.New().
215+ Before("main.go", TestMultipleHunksBefore).
216+ After("main.go", TestMultipleHunksAfter).
217+ Height(height).
218+ Style(diffview.DefaultLightStyle()).
219+ ChromaStyle(styles.Get("catppuccin-latte"))
220+ dv = layoutFunc(dv)
221+
222+ output := dv.String()
223+ golden.RequireEqual(t, []byte(output))
224+
225+ assertHeight(t, height, output)
226+ })
227+ }
228+ })
229+ }
230+}
231+
232+func TestDiffViewXOffset(t *testing.T) {
233+ for layoutName, layoutFunc := range LayoutFuncs {
234+ t.Run(layoutName, func(t *testing.T) {
235+ for xOffset := range 21 {
236+ t.Run(fmt.Sprintf("XOffsetOf%02d", xOffset), func(t *testing.T) {
237+ t.Parallel()
238+
239+ dv := diffview.New().
240+ Before("main.go", TestDefaultBefore).
241+ After("main.go", TestDefaultAfter).
242+ Style(diffview.DefaultLightStyle()).
243+ ChromaStyle(styles.Get("catppuccin-latte")).
244+ Width(60).
245+ XOffset(xOffset)
246+ dv = layoutFunc(dv)
247+
248+ output := dv.String()
249+ golden.RequireEqual(t, []byte(output))
250+
251+ assertLineWidth(t, 60, output)
252+ })
253+ }
254+ })
255+ }
256+}
257+
258+func TestDiffViewYOffset(t *testing.T) {
259+ for layoutName, layoutFunc := range LayoutFuncs {
260+ t.Run(layoutName, func(t *testing.T) {
261+ for yOffset := range 17 {
262+ t.Run(fmt.Sprintf("YOffsetOf%02d", yOffset), func(t *testing.T) {
263+ t.Parallel()
264+
265+ dv := diffview.New().
266+ Before("main.go", TestMultipleHunksBefore).
267+ After("main.go", TestMultipleHunksAfter).
268+ Style(diffview.DefaultLightStyle()).
269+ ChromaStyle(styles.Get("catppuccin-latte")).
270+ Height(5).
271+ YOffset(yOffset)
272+ dv = layoutFunc(dv)
273+
274+ output := dv.String()
275+ golden.RequireEqual(t, []byte(output))
276+
277+ assertHeight(t, 5, output)
278+ })
279+ }
280+ })
281+ }
282+}
283+
284+func assertLineWidth(t *testing.T, expected int, output string) {
285+ var lineWidth int
286+ for line := range strings.SplitSeq(output, "\n") {
287+ lineWidth = max(lineWidth, ansi.StringWidth(line))
288+ }
289+ if lineWidth != expected {
290+ t.Errorf("expected output width to be == %d, got %d", expected, lineWidth)
291+ }
292+}
293+
294+func assertHeight(t *testing.T, expected int, output string) {
295+ output = strings.TrimSuffix(output, "\n")
296+ lines := strings.Count(output, "\n") + 1
297+ if lines != expected {
298+ t.Errorf("expected output height to be == %d, got %d", expected, lines)
299+ }
300+}
internal/exp/diffview/split.gonew file+74
@@ -0,0 +1,74 @@
1+package diffview
2+
3+import (
4+ "slices"
5+
6+ "github.com/aymanbagabas/go-udiff"
7+ "github.com/charmbracelet/x/exp/slice"
8+)
9+
10+type splitHunk struct {
11+ fromLine int
12+ toLine int
13+ lines []*splitLine
14+}
15+
16+type splitLine struct {
17+ before *udiff.Line
18+ after *udiff.Line
19+}
20+
21+func hunkToSplit(h *udiff.Hunk) (sh splitHunk) {
22+ lines := slices.Clone(h.Lines)
23+ sh = splitHunk{
24+ fromLine: h.FromLine,
25+ toLine: h.ToLine,
26+ lines: make([]*splitLine, 0, len(lines)),
27+ }
28+
29+ for {
30+ var ul udiff.Line
31+ var ok bool
32+ ul, lines, ok = slice.Shift(lines)
33+ if !ok {
34+ break
35+ }
36+
37+ var sl splitLine
38+
39+ switch ul.Kind {
40+
41+ // For equal lines, add as is
42+ case udiff.Equal:
43+ sl.before = &ul
44+ sl.after = &ul
45+
46+ // For inserted lines, set after and keep before as nil
47+ case udiff.Insert:
48+ sl.before = nil
49+ sl.after = &ul
50+
51+ // For deleted lines, set before and loop over the next lines
52+ // searching for the equivalent after line.
53+ case udiff.Delete:
54+ sl.before = &ul
55+
56+ inner:
57+ for i, l := range lines {
58+ switch l.Kind {
59+ case udiff.Insert:
60+ var ll udiff.Line
61+ ll, lines, _ = slice.DeleteAt(lines, i)
62+ sl.after = &ll
63+ break inner
64+ case udiff.Equal:
65+ break inner
66+ }
67+ }
68+ }
69+
70+ sh.lines = append(sh.lines, &sl)
71+ }
72+
73+ return
74+}
internal/exp/diffview/style.gonew file+124
@@ -0,0 +1,124 @@
1+package diffview
2+
3+import (
4+ "github.com/charmbracelet/lipgloss/v2"
5+ "github.com/charmbracelet/x/exp/charmtone"
6+)
7+
8+// LineStyle defines the styles for a given line type in the diff view.
9+type LineStyle struct {
10+ LineNumber lipgloss.Style
11+ Symbol lipgloss.Style
12+ Code lipgloss.Style
13+}
14+
15+// Style defines the overall style for the diff view, including styles for
16+// different line types such as divider, missing, equal, insert, and delete
17+// lines.
18+type Style struct {
19+ DividerLine LineStyle
20+ MissingLine LineStyle
21+ EqualLine LineStyle
22+ InsertLine LineStyle
23+ DeleteLine LineStyle
24+}
25+
26+// DefaultLightStyle provides a default light theme style for the diff view.
27+func DefaultLightStyle() Style {
28+ return Style{
29+ DividerLine: LineStyle{
30+ LineNumber: lipgloss.NewStyle().
31+ Foreground(charmtone.Iron).
32+ Background(charmtone.Thunder),
33+ Code: lipgloss.NewStyle().
34+ Foreground(charmtone.Oyster).
35+ Background(charmtone.Anchovy),
36+ },
37+ MissingLine: LineStyle{
38+ LineNumber: lipgloss.NewStyle().
39+ Background(charmtone.Ash),
40+ Code: lipgloss.NewStyle().
41+ Background(charmtone.Ash),
42+ },
43+ EqualLine: LineStyle{
44+ LineNumber: lipgloss.NewStyle().
45+ Foreground(charmtone.Charcoal).
46+ Background(charmtone.Ash),
47+ Code: lipgloss.NewStyle().
48+ Foreground(charmtone.Pepper).
49+ Background(charmtone.Salt),
50+ },
51+ InsertLine: LineStyle{
52+ LineNumber: lipgloss.NewStyle().
53+ Foreground(charmtone.Turtle).
54+ Background(lipgloss.Color("#c8e6c9")),
55+ Symbol: lipgloss.NewStyle().
56+ Foreground(charmtone.Turtle).
57+ Background(lipgloss.Color("#e8f5e9")),
58+ Code: lipgloss.NewStyle().
59+ Foreground(charmtone.Pepper).
60+ Background(lipgloss.Color("#e8f5e9")),
61+ },
62+ DeleteLine: LineStyle{
63+ LineNumber: lipgloss.NewStyle().
64+ Foreground(charmtone.Cherry).
65+ Background(lipgloss.Color("#ffcdd2")),
66+ Symbol: lipgloss.NewStyle().
67+ Foreground(charmtone.Cherry).
68+ Background(lipgloss.Color("#ffebee")),
69+ Code: lipgloss.NewStyle().
70+ Foreground(charmtone.Pepper).
71+ Background(lipgloss.Color("#ffebee")),
72+ },
73+ }
74+}
75+
76+// DefaultDarkStyle provides a default dark theme style for the diff view.
77+func DefaultDarkStyle() Style {
78+ return Style{
79+ DividerLine: LineStyle{
80+ LineNumber: lipgloss.NewStyle().
81+ Foreground(charmtone.Smoke).
82+ Background(charmtone.Sapphire),
83+ Code: lipgloss.NewStyle().
84+ Foreground(charmtone.Smoke).
85+ Background(charmtone.Ox),
86+ },
87+ MissingLine: LineStyle{
88+ LineNumber: lipgloss.NewStyle().
89+ Background(charmtone.Charcoal),
90+ Code: lipgloss.NewStyle().
91+ Background(charmtone.Charcoal),
92+ },
93+ EqualLine: LineStyle{
94+ LineNumber: lipgloss.NewStyle().
95+ Foreground(charmtone.Ash).
96+ Background(charmtone.Charcoal),
97+ Code: lipgloss.NewStyle().
98+ Foreground(charmtone.Salt).
99+ Background(charmtone.Pepper),
100+ },
101+ InsertLine: LineStyle{
102+ LineNumber: lipgloss.NewStyle().
103+ Foreground(charmtone.Turtle).
104+ Background(lipgloss.Color("#293229")),
105+ Symbol: lipgloss.NewStyle().
106+ Foreground(charmtone.Turtle).
107+ Background(lipgloss.Color("#303a30")),
108+ Code: lipgloss.NewStyle().
109+ Foreground(charmtone.Salt).
110+ Background(lipgloss.Color("#303a30")),
111+ },
112+ DeleteLine: LineStyle{
113+ LineNumber: lipgloss.NewStyle().
114+ Foreground(charmtone.Cherry).
115+ Background(lipgloss.Color("#332929")),
116+ Symbol: lipgloss.NewStyle().
117+ Foreground(charmtone.Cherry).
118+ Background(lipgloss.Color("#3a3030")),
119+ Code: lipgloss.NewStyle().
120+ Foreground(charmtone.Salt).
121+ Background(lipgloss.Color("#3a3030")),
122+ },
123+ }
124+}
internal/exp/diffview/testdata/TestDefault.afternew file+10
@@ -0,0 +1,10 @@
1+package main
2+
3+import (
4+ "fmt"
5+)
6+
7+func main() {
8+ content := "Hello, world!"
9+ fmt.Println(content)
10+}
internal/exp/diffview/testdata/TestDefault.beforenew file+9
@@ -0,0 +1,9 @@
1+package main
2+
3+import (
4+ "fmt"
5+)
6+
7+func main() {
8+ fmt.Println("Hello, world!")
9+}
internal/exp/diffview/testdata/TestDiffView/Split/CustomContextLines/DarkMode.goldennew file+16
@@ -0,0 +1,16 @@
1+  …  @@ -1,13 +1,15 @@    …   
2+  1  package main   1  package main 
3+  2     2   
4+  3  import (   3  import ( 
5+  4   "fmt"   4   "fmt" 
6+       5 +  "strings" 
7+  5  )   6  ) 
8+  6     7   
9+  7  func main() {   8  func main() { 
10+  8   fmt.Println(getContent())   9   fmt.Println(getContent()) 
11+  9  }  10  } 
12+ 10    11   
13+ 11  func getContent() string {  12  func getContent() string { 
14+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
15+      14 +  return content 
16+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffView/Split/CustomContextLines/LightMode.goldennew file+16
@@ -0,0 +1,16 @@
1+  …  @@ -1,13 +1,15 @@    …   
2+  1  package main   1  package main 
3+  2     2   
4+  3  import (   3  import ( 
5+  4   "fmt"   4   "fmt" 
6+       5 +  "strings" 
7+  5  )   6  ) 
8+  6     7   
9+  7  func main() {   8  func main() { 
10+  8   fmt.Println(getContent())   9   fmt.Println(getContent()) 
11+  9  }  10  } 
12+ 10    11   
13+ 11  func getContent() string {  12  func getContent() string { 
14+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
15+      14 +  return content 
16+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffView/Split/Default/DarkMode.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  )   5  ) 
3+  6     6   
4+  7  func main() {   7  func main() { 
5+  8 -  fmt.Println("Hello, world!")   8 +  content := "Hello, world!" 
6+       9 +  fmt.Println(content) 
7+  9  }  10  } 
internal/exp/diffview/testdata/TestDiffView/Split/Default/LightMode.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  )   5  ) 
3+  6     6   
4+  7  func main() {   7  func main() { 
5+  8 -  fmt.Println("Hello, world!")   8 +  content := "Hello, world!" 
6+       9 +  fmt.Println(content) 
7+  9  }  10  } 
internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/DarkMode.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/LightMode.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffView/Split/MultipleHunks/DarkMode.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffView/Split/MultipleHunks/LightMode.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffView/Split/Narrow/DarkMode.goldennew file+4
@@ -0,0 +1,4 @@
1+ …  @@ -1,3 +1,3 @@   …   
2+ 1 - a  1 + d 
3+ 2 - b  2 + e 
4+ 3 - c  3 + f 
internal/exp/diffview/testdata/TestDiffView/Split/Narrow/LightMode.goldennew file+4
@@ -0,0 +1,4 @@
1+ …  @@ -1,3 +1,3 @@   …   
2+ 1 - a  1 + d 
3+ 2 - b  2 + e 
4+ 3 - c  3 + f 
internal/exp/diffview/testdata/TestDiffView/Split/NoLineNumbers/DarkMode.goldennew file+7
@@ -0,0 +1,7 @@
1+ @@ -5,5 +5,6 @@    
2+ )  ) 
3+    
4+ func main() {  func main() { 
5+-  fmt.Println("Hello, world!") +  content := "Hello, world!" 
6+  +  fmt.Println(content) 
7+ }  } 
internal/exp/diffview/testdata/TestDiffView/Split/NoLineNumbers/LightMode.goldennew file+7
@@ -0,0 +1,7 @@
1+ @@ -5,5 +5,6 @@    
2+ )  ) 
3+    
4+ func main() {  func main() { 
5+-  fmt.Println("Hello, world!") +  content := "Hello, world!" 
6+  +  fmt.Println(content) 
7+ }  } 
internal/exp/diffview/testdata/TestDiffView/Split/NoSyntaxHighlight/DarkMode.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4  "fmt"   4  "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffView/Split/NoSyntaxHighlight/LightMode.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4  "fmt"   4  "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/DarkMode.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 …  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getConte… 12  func getConte…
13+ 12 -  return "H… 13 +  content :…
14+      14 +  return co…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/LightMode.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 …  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getConte… 12  func getConte…
13+ 12 -  return "H… 13 +  content :…
14+      14 +  return co…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffView/Unified/CustomContextLines/DarkMode.goldennew file+17
@@ -0,0 +1,17 @@
1+  …   …  @@ -1,13 +1,15 @@  
2+  1   1  package main 
3+  2   2   
4+  3   3  import ( 
5+  4   4   "fmt" 
6+     5 +  "strings" 
7+  5   6  ) 
8+  6   7   
9+  7   8  func main() { 
10+  8   9   fmt.Println(getContent()) 
11+  9  10  } 
12+ 10  11   
13+ 11  12  func getContent() string { 
14+ 12    -  return "Hello, world!" 
15+    13 +  content := strings.ToUpper("Hello, World!") 
16+    14 +  return content 
17+ 13  15  } 
internal/exp/diffview/testdata/TestDiffView/Unified/CustomContextLines/LightMode.goldennew file+17
@@ -0,0 +1,17 @@
1+  …   …  @@ -1,13 +1,15 @@  
2+  1   1  package main 
3+  2   2   
4+  3   3  import ( 
5+  4   4   "fmt" 
6+     5 +  "strings" 
7+  5   6  ) 
8+  6   7   
9+  7   8  func main() { 
10+  8   9   fmt.Println(getContent()) 
11+  9  10  } 
12+ 10  11   
13+ 11  12  func getContent() string { 
14+ 12    -  return "Hello, world!" 
15+    13 +  content := strings.ToUpper("Hello, World!") 
16+    14 +  return content 
17+ 13  15  } 
internal/exp/diffview/testdata/TestDiffView/Unified/Default/DarkMode.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  ) 
3+  6   6   
4+  7   7  func main() { 
5+  8    -  fmt.Println("Hello, world!") 
6+     8 +  content := "Hello, world!" 
7+     9 +  fmt.Println(content) 
8+  9  10  } 
internal/exp/diffview/testdata/TestDiffView/Unified/Default/LightMode.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  ) 
3+  6   6   
4+  7   7  func main() { 
5+  8    -  fmt.Println("Hello, world!") 
6+     8 +  content := "Hello, world!" 
7+     9 +  fmt.Println(content) 
8+  9  10  } 
internal/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/DarkMode.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/LightMode.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffView/Unified/MultipleHunks/DarkMode.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffView/Unified/MultipleHunks/LightMode.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffView/Unified/Narrow/DarkMode.goldennew file+7
@@ -0,0 +1,7 @@
1+ …  …  @@ -1,3 +1,3 @@  
2+ 1    - a 
3+ 2    - b 
4+ 3    - c 
5+    1 + d 
6+    2 + e 
7+    3 + f 
internal/exp/diffview/testdata/TestDiffView/Unified/Narrow/LightMode.goldennew file+7
@@ -0,0 +1,7 @@
1+ …  …  @@ -1,3 +1,3 @@  
2+ 1    - a 
3+ 2    - b 
4+ 3    - c 
5+    1 + d 
6+    2 + e 
7+    3 + f 
internal/exp/diffview/testdata/TestDiffView/Unified/NoLineNumbers/DarkMode.goldennew file+8
@@ -0,0 +1,8 @@
1+ @@ -5,5 +5,6 @@  
2+ ) 
3+  
4+ func main() { 
5+-  fmt.Println("Hello, world!") 
6++  content := "Hello, world!" 
7++  fmt.Println(content) 
8+ } 
internal/exp/diffview/testdata/TestDiffView/Unified/NoLineNumbers/LightMode.goldennew file+8
@@ -0,0 +1,8 @@
1+ @@ -5,5 +5,6 @@  
2+ ) 
3+  
4+ func main() { 
5+-  fmt.Println("Hello, world!") 
6++  content := "Hello, world!" 
7++  fmt.Println(content) 
8+ } 
internal/exp/diffview/testdata/TestDiffView/Unified/NoSyntaxHighlight/DarkMode.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4  "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffView/Unified/NoSyntaxHighlight/LightMode.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4  "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/DarkMode.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUppe…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/LightMode.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUppe…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf001.goldennew file+1
@@ -0,0 +1 @@
1+  …  @@ -2,6 +2,7 @@    …   
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf002.goldennew file+2
@@ -0,0 +1,2 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf003.goldennew file+3
@@ -0,0 +1,3 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf004.goldennew file+4
@@ -0,0 +1,4 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf005.goldennew file+5
@@ -0,0 +1,5 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf006.goldennew file+6
@@ -0,0 +1,6 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf007.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf008.goldennew file+8
@@ -0,0 +1,8 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf009.goldennew file+9
@@ -0,0 +1,9 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf010.goldennew file+10
@@ -0,0 +1,10 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf011.goldennew file+11
@@ -0,0 +1,11 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf012.goldennew file+12
@@ -0,0 +1,12 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf013.goldennew file+13
@@ -0,0 +1,13 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf014.goldennew file+14
@@ -0,0 +1,14 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf015.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf016.goldennew file+16
@@ -0,0 +1,16 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
16+          
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf017.goldennew file+17
@@ -0,0 +1,17 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
16+          
17+          
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf018.goldennew file+18
@@ -0,0 +1,18 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
16+          
17+          
18+          
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf019.goldennew file+19
@@ -0,0 +1,19 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
16+          
17+          
18+          
19+          
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf020.goldennew file+20
@@ -0,0 +1,20 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
16+          
17+          
18+          
19+          
20+          
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf001.goldennew file+1
@@ -0,0 +1 @@
1+  …   …  @@ -2,6 +2,7 @@  
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf002.goldennew file+2
@@ -0,0 +1,2 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf003.goldennew file+3
@@ -0,0 +1,3 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf004.goldennew file+4
@@ -0,0 +1,4 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf005.goldennew file+5
@@ -0,0 +1,5 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf006.goldennew file+6
@@ -0,0 +1,6 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf007.goldennew file+7
@@ -0,0 +1,7 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf008.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf009.goldennew file+9
@@ -0,0 +1,9 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf010.goldennew file+10
@@ -0,0 +1,10 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf011.goldennew file+11
@@ -0,0 +1,11 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf012.goldennew file+12
@@ -0,0 +1,12 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf013.goldennew file+13
@@ -0,0 +1,13 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf014.goldennew file+14
@@ -0,0 +1,14 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf015.goldennew file+15
@@ -0,0 +1,15 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf016.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf017.goldennew file+17
@@ -0,0 +1,17 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
17+        
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf018.goldennew file+18
@@ -0,0 +1,18 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
17+        
18+        
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf019.goldennew file+19
@@ -0,0 +1,19 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
17+        
18+        
19+        
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf020.goldennew file+20
@@ -0,0 +1,20 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
17+        
18+        
19+        
20+        
internal/exp/diffview/testdata/TestDiffViewTabs/Split.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewTabs/Unified.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf001.goldennew file+15
@@ -0,0 +1,15 @@
1+ 
2+ 
3+ 
4+ 
5+ 
6+ 
7+ 
8+ 
9+ 
10+ 
11+ 
12+ 
13+ 
14+ 
15+ 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf002.goldennew file+15
@@ -0,0 +1,15 @@
1+  
2+  
3+  
4+  
5+  
6+  
7+  
8+  
9+  
10+  
11+ 1
12+ 1
13+ 1
14+  
15+ 1
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf003.goldennew file+15
@@ -0,0 +1,15 @@
1+  …
2+  2
3+  3
4+  4
5+  
6+  5
7+  6
8+  7
9+  …
10+  9
11+ 10
12+ 11
13+ 12
14+  
15+ 13
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf004.goldennew file+15
@@ -0,0 +1,15 @@
1+  … 
2+  2 
3+  3 
4+  4 
5+   
6+  5 
7+  6 
8+  7 
9+  … 
10+  9 
11+ 10 
12+ 11 
13+ 12 
14+   
15+ 13 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf005.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  
2+  2  
3+  3  
4+  4  
5+    
6+  5  
7+  6  
8+  7  
9+  …  
10+  9  
11+ 10  
12+ 11  
13+ 12 -
14+    
15+ 13  
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf006.goldennew file+15
@@ -0,0 +1,15 @@
1+  …   
2+  2  
3+  3  
4+  4  
5+    
6+  5  
7+  6  
8+  7  
9+  …   
10+  9  
11+ 10  
12+ 11  
13+ 12 - 
14+    
15+ 13  
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf007.goldennew file+15
@@ -0,0 +1,15 @@
1+  …   …
2+  2   
3+  3   
4+  4   
5+     
6+  5   
7+  6   
8+  7   
9+  …   …
10+  9   
11+ 10   
12+ 11   
13+ 12 -  
14+     
15+ 13   
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf008.goldennew file+15
@@ -0,0 +1,15 @@
1+  …   … 
2+  2    
3+  3    
4+  4    
5+      
6+  5    
7+  6    
8+  7    
9+  …   … 
10+  9   1
11+ 10   1
12+ 11   1
13+ 12 -  1
14+     1
15+ 13   1
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf009.goldennew file+15
@@ -0,0 +1,15 @@
1+  … …  … 
2+  2    2 
3+  3    3 
4+  4    4 
5+      5 
6+  5    6 
7+  6    7 
8+  7    8 
9+  … …  … 
10+  9   10 
11+ 10   11 
12+ 11   12 
13+ 12 - 13 
14+     14 
15+ 13   15 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf010.goldennew file+15
@@ -0,0 +1,15 @@
1+  … …  …  
2+  2    2  
3+  3    3  
4+  4    4  
5+      5 +
6+  5    6  
7+  6    7  
8+  7    8  
9+  … …  …  
10+  9   10  
11+ 10   11  
12+ 11   12  
13+ 12 - 13 +
14+     14 +
15+ 13   15  
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf011.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  …  …  
2+  2    2  
3+  3    3  
4+  4    4  
5+      5 +
6+  5    6  
7+  6    7  
8+  7    8  
9+  …  …  …  
10+  9   10  
11+ 10   11  
12+ 11   12  
13+ 12 -  13 +
14+     14 +
15+ 13   15  
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf012.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  …  …   
2+  2    2  
3+  3    3  
4+  4    4  
5+      5 + 
6+  5    6  
7+  6    7  
8+  7    8  
9+  …  …  …   
10+  9   10  
11+ 10   11  
12+ 11   12  
13+ 12 -  13 + 
14+     14 + 
15+ 13   15  
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf013.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  …  …   
2+  2    2   
3+  3    3   
4+  4    4   
5+      5 +  
6+  5    6   
7+  6    7   
8+  7    8   
9+  …  …  …   
10+  9   10   
11+ 10   11   
12+ 11   12   
13+ 12 -  13 +  
14+     14 +  
15+ 13   15   
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf014.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  …  …   
2+  2     2   
3+  3  …  3  …
4+  4  …  4  …
5+       5 + …
6+  5  )  6  )
7+  6     7   
8+  7  …  8  …
9+  …  …  …   
10+  9  } 10  }
11+ 10    11   
12+ 11  … 12  …
13+ 12 - … 13 + …
14+      14 + …
15+ 13  } 15  }
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf015.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  …  …   
2+  2     2   
3+  3  …  3  … 
4+  4  …  4  … 
5+       5 + … 
6+  5  )  6  ) 
7+  6     7   
8+  7  …  8  … 
9+  …  …  …   
10+  9  } 10  } 
11+ 10    11   
12+ 11  … 12  … 
13+ 12 - … 13 + … 
14+      14 + … 
15+ 13  } 15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf016.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @…  …   
2+  2     2   
3+  3  i…  3  i…
4+  4   …  4   …
5+       5 +  …
6+  5  )   6  ) 
7+  6     7   
8+  7  f…  8  f…
9+  …  @…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  f… 12  f…
13+ 12 -  … 13 +  …
14+      14 +  …
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf017.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @…  …   
2+  2     2   
3+  3  i…  3  i… 
4+  4   …  4   … 
5+       5 +  … 
6+  5  )   6  ) 
7+  6     7   
8+  7  f…  8  f… 
9+  …  @…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  f… 12  f… 
13+ 12 -  … 13 +  … 
14+      14 +  … 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf018.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@…  …   
2+  2     2   
3+  3  im…  3  im…
4+  4   …  4   …
5+       5 +  …
6+  5  )   6  ) 
7+  6     7   
8+  7  fu…  8  fu…
9+  …  @@…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  fu… 12  fu…
13+ 12 -  … 13 +  …
14+      14 +  …
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf019.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@…  …   
2+  2     2   
3+  3  im…  3  im… 
4+  4   …  4   … 
5+       5 +  … 
6+  5  )   6  ) 
7+  6     7   
8+  7  fu…  8  fu… 
9+  …  @@…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  fu… 12  fu… 
13+ 12 -  … 13 +  … 
14+      14 +  … 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf020.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ …  …   
2+  2     2   
3+  3  imp…  3  imp…
4+  4   …  4   …
5+       5 +  …
6+  5  )   6  ) 
7+  6     7   
8+  7  fun…  8  fun…
9+  …  @@ …  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  fun… 12  fun…
13+ 12 -  … 13 +  …
14+      14 +  …
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf021.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ …  …   
2+  2     2   
3+  3  imp…  3  imp… 
4+  4   …  4   … 
5+       5 +  … 
6+  5  )   6  ) 
7+  6     7   
8+  7  fun…  8  fun… 
9+  …  @@ …  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  fun… 12  fun… 
13+ 12 -  … 13 +  … 
14+      14 +  … 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf022.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -…  …   
2+  2     2   
3+  3  impo…  3  impo…
4+  4   …  4   …
5+       5 +  …
6+  5  )   6  ) 
7+  6     7   
8+  7  func…  8  func…
9+  …  @@ -…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func… 12  func…
13+ 12 -  … 13 +  …
14+      14 +  …
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf023.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -…  …   
2+  2     2   
3+  3  impo…  3  impo… 
4+  4   …  4   … 
5+       5 +  … 
6+  5  )   6  ) 
7+  6     7   
8+  7  func…  8  func… 
9+  …  @@ -…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func… 12  func… 
13+ 12 -  … 13 +  … 
14+      14 +  … 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf024.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2…  …   
2+  2     2   
3+  3  impor…  3  impor…
4+  4   "…  4   "…
5+       5 +  "…
6+  5  )   6  ) 
7+  6     7   
8+  7  func …  8  func …
9+  …  @@ -9…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func … 12  func …
13+ 12 -  r… 13 +  c…
14+      14 +  r…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf025.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2…  …   
2+  2     2   
3+  3  impor…  3  impor… 
4+  4   "…  4   "… 
5+       5 +  "… 
6+  5  )   6  ) 
7+  6     7   
8+  7  func …  8  func … 
9+  …  @@ -9…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func … 12  func … 
13+ 12 -  r… 13 +  c… 
14+      14 +  r… 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf026.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,…  …   
2+  2     2   
3+  3  import…  3  import…
4+  4   "f…  4   "f…
5+       5 +  "s…
6+  5  )   6  ) 
7+  6     7   
8+  7  func m…  8  func m…
9+  …  @@ -9,…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func g… 12  func g…
13+ 12 -  re… 13 +  co…
14+      14 +  re…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf027.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,…  …   
2+  2     2   
3+  3  import…  3  import… 
4+  4   "f…  4   "f… 
5+       5 +  "s… 
6+  5  )   6  ) 
7+  6     7   
8+  7  func m…  8  func m… 
9+  …  @@ -9,…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func g… 12  func g… 
13+ 12 -  re… 13 +  co… 
14+      14 +  re… 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf028.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6…  …   
2+  2     2   
3+  3  import (  3  import (
4+  4   "fm…  4   "fm…
5+       5 +  "st…
6+  5  )   6  ) 
7+  6     7   
8+  7  func ma…  8  func ma…
9+  …  @@ -9,5…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func ge… 12  func ge…
13+ 12 -  ret… 13 +  con…
14+      14 +  ret…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf029.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6…  …   
2+  2     2   
3+  3  import (  3  import ( 
4+  4   "fm…  4   "fm… 
5+       5 +  "st… 
6+  5  )   6  ) 
7+  6     7   
8+  7  func ma…  8  func ma… 
9+  …  @@ -9,5…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func ge… 12  func ge… 
13+ 12 -  ret… 13 +  con… 
14+      14 +  ret… 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf030.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 …  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"  4   "fmt"
5+       5 +  "str…
6+  5  )   6  ) 
7+  6     7   
8+  7  func mai…  8  func mai…
9+  …  @@ -9,5 …  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func get… 12  func get…
13+ 12 -  retu… 13 +  cont…
14+      14 +  retu…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf031.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 …  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"  4   "fmt" 
5+       5 +  "str… 
6+  5  )   6  ) 
7+  6     7   
8+  7  func mai…  8  func mai… 
9+  …  @@ -9,5 …  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func get… 12  func get… 
13+ 12 -  retu… 13 +  cont… 
14+      14 +  retu… 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf032.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +…  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "stri…
6+  5  )   6  ) 
7+  6     7   
8+  7  func main…  8  func main…
9+  …  @@ -9,5 +…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getC… 12  func getC…
13+ 12 -  retur… 13 +  conte…
14+      14 +  retur…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf033.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +…  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "stri… 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main…  8  func main… 
9+  …  @@ -9,5 +…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getC… 12  func getC… 
13+ 12 -  retur… 13 +  conte… 
14+      14 +  retur… 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf034.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2…  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strin…
6+  5  )   6  ) 
7+  6     7   
8+  7  func main(…  8  func main(…
9+  …  @@ -9,5 +1…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getCo… 12  func getCo…
13+ 12 -  return… 13 +  conten…
14+      14 +  return…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf035.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2…  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strin… 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main(…  8  func main(… 
9+  …  @@ -9,5 +1…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getCo… 12  func getCo… 
13+ 12 -  return… 13 +  conten… 
14+      14 +  return… 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf036.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,…  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "string…
6+  5  )   6  ) 
7+  6     7   
8+  7  func main()…  8  func main()…
9+  …  @@ -9,5 +10…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getCon… 12  func getCon…
13+ 12 -  return … 13 +  content…
14+      14 +  return …
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf037.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,…  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "string… 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main()…  8  func main()… 
9+  …  @@ -9,5 +10…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getCon… 12  func getCon… 
13+ 12 -  return … 13 +  content… 
14+      14 +  return … 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf038.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7…  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings"
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {  8  func main() {
9+  …  @@ -9,5 +10,…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getCont… 12  func getCont…
13+ 12 -  return "… 13 +  content …
14+      14 +  return c…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf039.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7…  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {  8  func main() { 
9+  …  @@ -9,5 +10,…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getCont… 12  func getCont… 
13+ 12 -  return "… 13 +  content … 
14+      14 +  return c… 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf040.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 …  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getConte… 12  func getConte…
13+ 12 -  return "H… 13 +  content :…
14+      14 +  return co…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf041.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 …  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getConte… 12  func getConte… 
13+ 12 -  return "H… 13 +  content :… 
14+      14 +  return co… 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf042.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @…  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 …  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getConten… 12  func getConten…
13+ 12 -  return "He… 13 +  content :=…
14+      14 +  return con…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf043.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @…  …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 …  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getConten… 12  func getConten… 
13+ 12 -  return "He… 13 +  content :=… 
14+      14 +  return con… 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf044.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@   …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent… 12  func getContent…
13+ 12 -  return "Hel… 13 +  content := …
14+      14 +  return cont…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf045.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@   …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @…  …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent… 12  func getContent… 
13+ 12 -  return "Hel… 13 +  content := … 
14+      14 +  return cont… 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf046.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@   …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent(… 12  func getContent(…
13+ 12 -  return "Hell… 13 +  content := s…
14+      14 +  return conte…
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf047.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@   …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent(… 12  func getContent(… 
13+ 12 -  return "Hell… 13 +  content := s… 
14+      14 +  return conte… 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf048.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent()… 12  func getContent()…
13+ 12 -  return "Hello… 13 +  content := st…
14+      14 +  return content
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf049.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent()… 12  func getContent()… 
13+ 12 -  return "Hello… 13 +  content := st… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf050.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() … 12  func getContent() …
13+ 12 -  return "Hello,… 13 +  content := str…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf051.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() … 12  func getContent() … 
13+ 12 -  return "Hello,… 13 +  content := str… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf052.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() s… 12  func getContent() s…
13+ 12 -  return "Hello, … 13 +  content := stri…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf053.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() s… 12  func getContent() s… 
13+ 12 -  return "Hello, … 13 +  content := stri… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf054.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() st… 12  func getContent() st…
13+ 12 -  return "Hello, w… 13 +  content := strin…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf055.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() st… 12  func getContent() st… 
13+ 12 -  return "Hello, w… 13 +  content := strin… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf056.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() str… 12  func getContent() str…
13+ 12 -  return "Hello, wo… 13 +  content := string…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf057.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() str… 12  func getContent() str… 
13+ 12 -  return "Hello, wo… 13 +  content := string… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf058.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() stri… 12  func getContent() stri…
13+ 12 -  return "Hello, wor… 13 +  content := strings…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf059.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() stri… 12  func getContent() stri… 
13+ 12 -  return "Hello, wor… 13 +  content := strings… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf060.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() strin… 12  func getContent() strin…
13+ 12 -  return "Hello, worl… 13 +  content := strings.…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf061.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() strin… 12  func getContent() strin… 
13+ 12 -  return "Hello, worl… 13 +  content := strings.… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf062.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string… 12  func getContent() string…
13+ 12 -  return "Hello, world… 13 +  content := strings.T…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf063.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string… 12  func getContent() string… 
13+ 12 -  return "Hello, world… 13 +  content := strings.T… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf064.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string { 12  func getContent() string {
13+ 12 -  return "Hello, world!" 13 +  content := strings.To…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf065.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string { 12  func getContent() string { 
13+ 12 -  return "Hello, world!" 13 +  content := strings.To… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf066.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToU…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf067.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToU… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf068.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUp…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf069.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUp… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf070.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpp…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf071.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpp… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf072.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUppe…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf073.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUppe… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf074.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf075.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf076.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper(…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf077.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper(… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf078.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf079.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf080.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("H…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf081.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("H… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf082.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("He…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf083.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("He… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf084.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hel…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf085.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hel… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf086.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hell…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf087.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hell… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf088.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf089.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf090.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello,…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf091.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello,… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf092.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, …
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf093.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, … 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf094.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, W…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf095.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, W… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf096.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Wo…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf097.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Wo… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf098.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Wor…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf099.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Wor… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf100.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Worl…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf101.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Worl… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf102.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf103.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf104.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!…
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf105.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!… 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf106.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!")
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf107.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf108.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf109.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf110.goldennew file+15
@@ -0,0 +1,15 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+       5 +  "strings" 
6+  5  )   6  ) 
7+  6     7   
8+  7  func main() {   8  func main() { 
9+  …  @@ -9,5 +10,6 @@    …   
10+  9  }  10  } 
11+ 10    11   
12+ 11  func getContent() string {  12  func getContent() string { 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
15+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf001.goldennew file+16
@@ -0,0 +1,16 @@
1+ 
2+ 
3+ 
4+ 
5+ 
6+ 
7+ 
8+ 
9+ 
10+ 
11+ 
12+ 
13+ 
14+ 
15+ 
16+ 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf002.goldennew file+16
@@ -0,0 +1,16 @@
1+  
2+  
3+  
4+  
5+  
6+  
7+  
8+  
9+  
10+  
11+ 1
12+ 1
13+ 1
14+  
15+  
16+ 1
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf003.goldennew file+16
@@ -0,0 +1,16 @@
1+  …
2+  2
3+  3
4+  4
5+  
6+  5
7+  6
8+  7
9+  …
10+  9
11+ 10
12+ 11
13+ 12
14+  
15+  
16+ 13
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf004.goldennew file+16
@@ -0,0 +1,16 @@
1+  … 
2+  2 
3+  3 
4+  4 
5+   
6+  5 
7+  6 
8+  7 
9+  … 
10+  9 
11+ 10 
12+ 11 
13+ 12 
14+   
15+   
16+ 13 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf005.goldennew file+16
@@ -0,0 +1,16 @@
1+  …  
2+  2  
3+  3  
4+  4  
5+    
6+  5  
7+  6  
8+  7  
9+  …  
10+  9  
11+ 10  
12+ 11  
13+ 12  
14+    
15+    
16+ 13  
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf006.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   
2+  2   
3+  3   
4+  4   
5+     
6+  5   
7+  6   
8+  7   
9+  …   
10+  9  1
11+ 10  1
12+ 11  1
13+ 12   
14+    1
15+    1
16+ 13  1
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf007.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …
2+  2   2
3+  3   3
4+  4   4
5+     5
6+  5   6
7+  6   7
8+  7   8
9+  …   …
10+  9  10
11+ 10  11
12+ 11  12
13+ 12   
14+    13
15+    14
16+ 13  15
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf008.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   … 
2+  2   2 
3+  3   3 
4+  4   4 
5+     5 
6+  5   6 
7+  6   7 
8+  7   8 
9+  …   … 
10+  9  10 
11+ 10  11 
12+ 11  12 
13+ 12    
14+    13 
15+    14 
16+ 13  15 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf009.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   … …
2+  2   2  
3+  3   3  
4+  4   4  
5+     5 +
6+  5   6  
7+  6   7  
8+  7   8  
9+  …   … …
10+  9  10  
11+ 10  11  
12+ 11  12  
13+ 12    -
14+    13 +
15+    14 +
16+ 13  15  
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf010.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  …
2+  2   2  
3+  3   3  
4+  4   4  
5+     5 + 
6+  5   6  
7+  6   7  
8+  7   8  
9+  …   …  …
10+  9  10  
11+ 10  11  
12+ 11  12  
13+ 12    - 
14+    13 + 
15+    14 + 
16+ 13  15  
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf011.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  …
2+  2   2   
3+  3   3  …
4+  4   4  …
5+     5 + …
6+  5   6  )
7+  6   7   
8+  7   8  …
9+  …   …  …
10+  9  10  }
11+ 10  11   
12+ 11  12  …
13+ 12    - …
14+    13 + …
15+    14 + …
16+ 13  15  }
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf012.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @…
2+  2   2   
3+  3   3  i…
4+  4   4   …
5+     5 +  …
6+  5   6  ) 
7+  6   7   
8+  7   8  f…
9+  …   …  @…
10+  9  10  } 
11+ 10  11   
12+ 11  12  f…
13+ 12    -  …
14+    13 +  …
15+    14 +  …
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf013.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@…
2+  2   2   
3+  3   3  im…
4+  4   4   …
5+     5 +  …
6+  5   6  ) 
7+  6   7   
8+  7   8  fu…
9+  …   …  @@…
10+  9  10  } 
11+ 10  11   
12+ 11  12  fu…
13+ 12    -  …
14+    13 +  …
15+    14 +  …
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf014.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ …
2+  2   2   
3+  3   3  imp…
4+  4   4   …
5+     5 +  …
6+  5   6  ) 
7+  6   7   
8+  7   8  fun…
9+  …   …  @@ …
10+  9  10  } 
11+ 10  11   
12+ 11  12  fun…
13+ 12    -  …
14+    13 +  …
15+    14 +  …
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf015.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -…
2+  2   2   
3+  3   3  impo…
4+  4   4   …
5+     5 +  …
6+  5   6  ) 
7+  6   7   
8+  7   8  func…
9+  …   …  @@ -…
10+  9  10  } 
11+ 10  11   
12+ 11  12  func…
13+ 12    -  …
14+    13 +  …
15+    14 +  …
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf016.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2…
2+  2   2   
3+  3   3  impor…
4+  4   4   "…
5+     5 +  "…
6+  5   6  ) 
7+  6   7   
8+  7   8  func …
9+  …   …  @@ -9…
10+  9  10  } 
11+ 10  11   
12+ 11  12  func …
13+ 12    -  r…
14+    13 +  c…
15+    14 +  r…
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf017.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,…
2+  2   2   
3+  3   3  import…
4+  4   4   "f…
5+     5 +  "s…
6+  5   6  ) 
7+  6   7   
8+  7   8  func m…
9+  …   …  @@ -9,…
10+  9  10  } 
11+ 10  11   
12+ 11  12  func g…
13+ 12    -  re…
14+    13 +  co…
15+    14 +  re…
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf018.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6…
2+  2   2   
3+  3   3  import (
4+  4   4   "fm…
5+     5 +  "st…
6+  5   6  ) 
7+  6   7   
8+  7   8  func ma…
9+  …   …  @@ -9,5…
10+  9  10  } 
11+ 10  11   
12+ 11  12  func ge…
13+ 12    -  ret…
14+    13 +  con…
15+    14 +  ret…
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf019.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 …
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt"
5+     5 +  "str…
6+  5   6  ) 
7+  6   7   
8+  7   8  func mai…
9+  …   …  @@ -9,5 …
10+  9  10  } 
11+ 10  11   
12+ 11  12  func get…
13+ 12    -  retu…
14+    13 +  cont…
15+    14 +  retu…
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf020.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +…
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "stri…
6+  5   6  ) 
7+  6   7   
8+  7   8  func main…
9+  …   …  @@ -9,5 +…
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getC…
13+ 12    -  retur…
14+    13 +  conte…
15+    14 +  retur…
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf021.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2…
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strin…
6+  5   6  ) 
7+  6   7   
8+  7   8  func main(…
9+  …   …  @@ -9,5 +1…
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getCo…
13+ 12    -  return…
14+    13 +  conten…
15+    14 +  return…
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf022.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,…
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "string…
6+  5   6  ) 
7+  6   7   
8+  7   8  func main()…
9+  …   …  @@ -9,5 +10…
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getCon…
13+ 12    -  return …
14+    13 +  content…
15+    14 +  return …
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf023.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7…
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings"
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() {
9+  …   …  @@ -9,5 +10,…
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getCont…
13+ 12    -  return "…
14+    13 +  content …
15+    14 +  return c…
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf024.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 …
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6…
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getConte…
13+ 12    -  return "H…
14+    13 +  content :…
15+    14 +  return co…
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf025.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @…
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 …
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getConten…
13+ 12    -  return "He…
14+    13 +  content :=…
15+    14 +  return con…
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf026.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@ 
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @…
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent…
13+ 12    -  return "Hel…
14+    13 +  content := …
15+    14 +  return cont…
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf027.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@ 
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent(…
13+ 12    -  return "Hell…
14+    13 +  content := s…
15+    14 +  return conte…
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf028.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent()…
13+ 12    -  return "Hello…
14+    13 +  content := st…
15+    14 +  return content
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf029.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() …
13+ 12    -  return "Hello,…
14+    13 +  content := str…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf030.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() s…
13+ 12    -  return "Hello, …
14+    13 +  content := stri…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf031.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() st…
13+ 12    -  return "Hello, w…
14+    13 +  content := strin…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf032.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() str…
13+ 12    -  return "Hello, wo…
14+    13 +  content := string…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf033.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() stri…
13+ 12    -  return "Hello, wor…
14+    13 +  content := strings…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf034.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() strin…
13+ 12    -  return "Hello, worl…
14+    13 +  content := strings.…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf035.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string…
13+ 12    -  return "Hello, world…
14+    13 +  content := strings.T…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf036.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string {
13+ 12    -  return "Hello, world!"
14+    13 +  content := strings.To…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf037.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToU…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf038.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUp…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf039.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpp…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf040.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUppe…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf041.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf042.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper(…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf043.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf044.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("H…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf045.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("He…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf046.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hel…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf047.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hell…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf048.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf049.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello,…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf050.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, …
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf051.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, W…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf052.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, Wo…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf053.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, Wor…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf054.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, Worl…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf055.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf056.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!…
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf057.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!")
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf058.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf059.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf060.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@  
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf00.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  )   5  ) 
3+  6     6   
4+  7  func main() {   7  func main() { 
5+  8 -  fmt.Println("Hello,…  8 +  content := "Hello, …
6+       9 +  fmt.Println(content)
7+  9  }  10  } 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf01.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …unc main() {   7  …unc main() { 
5+  8 -… fmt.Println("Hello, …  8 +… content := "Hello, w…
6+       9 +… fmt.Println(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf02.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …nc main() {   7  …nc main() { 
5+  8 -… fmt.Println("Hello, w…  8 +… content := "Hello, wo…
6+       9 +… fmt.Println(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf03.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …c main() {   7  …c main() { 
5+  8 -… fmt.Println("Hello, wo…  8 +… content := "Hello, wor…
6+       9 +… fmt.Println(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf04.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  … main() {   7  … main() { 
5+  8 -…fmt.Println("Hello, wor…  8 +…content := "Hello, worl…
6+       9 +…fmt.Println(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf05.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …main() {   7  …main() { 
5+  8 -…mt.Println("Hello, worl…  8 +…ontent := "Hello, world…
6+       9 +…mt.Println(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf06.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …ain() {   7  …ain() { 
5+  8 -…t.Println("Hello, world…  8 +…ntent := "Hello, world!"
6+       9 +…t.Println(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf07.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …in() {   7  …in() { 
5+  8 -….Println("Hello, world!…  8 +…tent := "Hello, world!" 
6+       9 +….Println(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf08.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …n() {   7  …n() { 
5+  8 -…Println("Hello, world!")  8 +…ent := "Hello, world!" 
6+       9 +…Println(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf09.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …() {   7  …() { 
5+  8 -…rintln("Hello, world!")   8 +…nt := "Hello, world!" 
6+       9 +…rintln(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf10.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …) {   7  …) { 
5+  8 -…intln("Hello, world!")   8 +…t := "Hello, world!" 
6+       9 +…intln(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf11.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  … {   7  … { 
5+  8 -…ntln("Hello, world!")   8 +… := "Hello, world!" 
6+       9 +…ntln(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf12.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …{   7  …{ 
5+  8 -…tln("Hello, world!")   8 +…:= "Hello, world!" 
6+       9 +…tln(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf13.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …   7  … 
5+  8 -…ln("Hello, world!")   8 +…= "Hello, world!" 
6+       9 +…ln(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf14.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …   7  … 
5+  8 -…n("Hello, world!")   8 +… "Hello, world!" 
6+       9 +…n(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf15.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …   7  … 
5+  8 -…("Hello, world!")   8 +…"Hello, world!" 
6+       9 +…(content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf16.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …   7  … 
5+  8 -…"Hello, world!")   8 +…Hello, world!" 
6+       9 +…content) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf17.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …   7  … 
5+  8 -…Hello, world!")   8 +…ello, world!" 
6+       9 +…ontent) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf18.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …   7  … 
5+  8 -…ello, world!")   8 +…llo, world!" 
6+       9 +…ntent) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf19.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …   7  … 
5+  8 -…llo, world!")   8 +…lo, world!" 
6+       9 +…tent) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf20.goldennew file+7
@@ -0,0 +1,7 @@
1+  …  @@ -5,5 +5,6 @@    …   
2+  5  …   5  … 
3+  6     6   
4+  7  …   7  … 
5+  8 -…lo, world!")   8 +…o, world!" 
6+       9 +…ent) 
7+  9  …  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf00.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  ) 
3+  6   6   
4+  7   7  func main() { 
5+  8    -  fmt.Println("Hello, world!") 
6+     8 +  content := "Hello, world!" 
7+     9 +  fmt.Println(content) 
8+  9  10  } 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf01.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  …unc main() { 
5+  8    -… fmt.Println("Hello, world!") 
6+     8 +… content := "Hello, world!" 
7+     9 +… fmt.Println(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf02.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  …nc main() { 
5+  8    -… fmt.Println("Hello, world!") 
6+     8 +… content := "Hello, world!" 
7+     9 +… fmt.Println(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf03.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  …c main() { 
5+  8    -… fmt.Println("Hello, world!") 
6+     8 +… content := "Hello, world!" 
7+     9 +… fmt.Println(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf04.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  … main() { 
5+  8    -…fmt.Println("Hello, world!") 
6+     8 +…content := "Hello, world!" 
7+     9 +…fmt.Println(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf05.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  …main() { 
5+  8    -…mt.Println("Hello, world!") 
6+     8 +…ontent := "Hello, world!" 
7+     9 +…mt.Println(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf06.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  …ain() { 
5+  8    -…t.Println("Hello, world!") 
6+     8 +…ntent := "Hello, world!" 
7+     9 +…t.Println(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf07.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  …in() { 
5+  8    -….Println("Hello, world!") 
6+     8 +…tent := "Hello, world!" 
7+     9 +….Println(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf08.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  …n() { 
5+  8    -…Println("Hello, world!") 
6+     8 +…ent := "Hello, world!" 
7+     9 +…Println(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf09.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  …() { 
5+  8    -…rintln("Hello, world!") 
6+     8 +…nt := "Hello, world!" 
7+     9 +…rintln(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf10.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  …) { 
5+  8    -…intln("Hello, world!") 
6+     8 +…t := "Hello, world!" 
7+     9 +…intln(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf11.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  … { 
5+  8    -…ntln("Hello, world!") 
6+     8 +… := "Hello, world!" 
7+     9 +…ntln(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf12.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  …{ 
5+  8    -…tln("Hello, world!") 
6+     8 +…:= "Hello, world!" 
7+     9 +…tln(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf13.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  … 
5+  8    -…ln("Hello, world!") 
6+     8 +…= "Hello, world!" 
7+     9 +…ln(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf14.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  … 
5+  8    -…n("Hello, world!") 
6+     8 +… "Hello, world!" 
7+     9 +…n(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf15.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  … 
5+  8    -…("Hello, world!") 
6+     8 +…"Hello, world!" 
7+     9 +…(content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf16.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  … 
5+  8    -…"Hello, world!") 
6+     8 +…Hello, world!" 
7+     9 +…content) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf17.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  … 
5+  8    -…Hello, world!") 
6+     8 +…ello, world!" 
7+     9 +…ontent) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf18.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  … 
5+  8    -…ello, world!") 
6+     8 +…llo, world!" 
7+     9 +…ntent) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf19.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  … 
5+  8    -…llo, world!") 
6+     8 +…lo, world!" 
7+     9 +…tent) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf20.goldennew file+8
@@ -0,0 +1,8 @@
1+  …   …  @@ -5,5 +5,6 @@  
2+  5   5  … 
3+  6   6  … 
4+  7   7  … 
5+  8    -…lo, world!") 
6+     8 +…o, world!" 
7+     9 +…ent) 
8+  9  10  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf00.goldennew file+5
@@ -0,0 +1,5 @@
1+  …  @@ -2,6 +2,7 @@    …   
2+  2     2   
3+  3  import (   3  import ( 
4+  4   "fmt"   4   "fmt" 
5+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf01.goldennew file+5
@@ -0,0 +1,5 @@
1+  2     2   
2+  3  import (   3  import ( 
3+  4   "fmt"   4   "fmt" 
4+       5 +  "strings" 
5+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf02.goldennew file+5
@@ -0,0 +1,5 @@
1+  3  import (   3  import ( 
2+  4   "fmt"   4   "fmt" 
3+       5 +  "strings" 
4+  5  )   6  ) 
5+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf03.goldennew file+5
@@ -0,0 +1,5 @@
1+  4   "fmt"   4   "fmt" 
2+       5 +  "strings" 
3+  5  )   6  ) 
4+  6     7   
5+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf04.goldennew file+5
@@ -0,0 +1,5 @@
1+       5 +  "strings" 
2+  5  )   6  ) 
3+  6     7   
4+  7  func main() {   8  func main() { 
5+  …  @@ -9,5 +10,6 @@    …   
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf05.goldennew file+5
@@ -0,0 +1,5 @@
1+  5  )   6  ) 
2+  6     7   
3+  7  func main() {   8  func main() { 
4+  …  @@ -9,5 +10,6 @@    …   
5+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf06.goldennew file+5
@@ -0,0 +1,5 @@
1+  6     7   
2+  7  func main() {   8  func main() { 
3+  …  @@ -9,5 +10,6 @@    …   
4+  9  }  10  } 
5+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf07.goldennew file+5
@@ -0,0 +1,5 @@
1+  7  func main() {   8  func main() { 
2+  …  @@ -9,5 +10,6 @@    …   
3+  9  }  10  } 
4+ 10    11   
5+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf08.goldennew file+5
@@ -0,0 +1,5 @@
1+  …  @@ -9,5 +10,6 @@    …   
2+  9  }  10  } 
3+ 10    11   
4+ 11  func getContent() string {  12  func getContent() string { 
5+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf09.goldennew file+5
@@ -0,0 +1,5 @@
1+  9  }  10  } 
2+ 10    11   
3+ 11  func getContent() string {  12  func getContent() string { 
4+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
5+  …  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf10.goldennew file+5
@@ -0,0 +1,5 @@
1+ 10    11   
2+ 11  func getContent() string {  12  func getContent() string { 
3+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4+      14 +  return content 
5+ 13  }  15  } 
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf11.goldennew file+5
@@ -0,0 +1,5 @@
1+ 11  func getContent() string {  12  func getContent() string { 
2+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
3+      14 +  return content 
4+ 13  }  15  } 
5+          
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf12.goldennew file+5
@@ -0,0 +1,5 @@
1+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
2+      14 +  return content 
3+ 13  }  15  } 
4+          
5+          
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf13.goldennew file+5
@@ -0,0 +1,5 @@
1+      14 +  return content 
2+ 13  }  15  } 
3+          
4+          
5+          
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf14.goldennew file+5
@@ -0,0 +1,5 @@
1+ 13  }  15  } 
2+          
3+          
4+          
5+          
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf15.goldennew file+5
@@ -0,0 +1,5 @@
1+          
2+          
3+          
4+          
5+          
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf16.goldennew file+5
@@ -0,0 +1,5 @@
1+          
2+          
3+          
4+          
5+          
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf00.goldennew file+5
@@ -0,0 +1,5 @@
1+  …   …  @@ -2,6 +2,7 @@  
2+  2   2   
3+  3   3  import ( 
4+  4   4   "fmt" 
5+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf01.goldennew file+5
@@ -0,0 +1,5 @@
1+  2   2   
2+  3   3  import ( 
3+  4   4   "fmt" 
4+     5 +  "strings" 
5+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf02.goldennew file+5
@@ -0,0 +1,5 @@
1+  3   3  import ( 
2+  4   4   "fmt" 
3+     5 +  "strings" 
4+  5   6  ) 
5+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf03.goldennew file+5
@@ -0,0 +1,5 @@
1+  4   4   "fmt" 
2+     5 +  "strings" 
3+  5   6  ) 
4+  6   7   
5+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf04.goldennew file+5
@@ -0,0 +1,5 @@
1+     5 +  "strings" 
2+  5   6  ) 
3+  6   7   
4+  7   8  func main() { 
5+  …   …  @@ -9,5 +10,6 @@  
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf05.goldennew file+5
@@ -0,0 +1,5 @@
1+  5   6  ) 
2+  6   7   
3+  7   8  func main() { 
4+  …   …  @@ -9,5 +10,6 @@  
5+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf06.goldennew file+5
@@ -0,0 +1,5 @@
1+  6   7   
2+  7   8  func main() { 
3+  …   …  @@ -9,5 +10,6 @@  
4+  9  10  } 
5+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf07.goldennew file+5
@@ -0,0 +1,5 @@
1+  7   8  func main() { 
2+  …   …  @@ -9,5 +10,6 @@  
3+  9  10  } 
4+ 10  11   
5+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf08.goldennew file+5
@@ -0,0 +1,5 @@
1+  …   …  @@ -9,5 +10,6 @@  
2+  9  10  } 
3+ 10  11   
4+ 11  12  func getContent() string { 
5+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf09.goldennew file+5
@@ -0,0 +1,5 @@
1+  9  10  } 
2+ 10  11   
3+ 11  12  func getContent() string { 
4+ 12    -  return "Hello, world!" 
5+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf10.goldennew file+5
@@ -0,0 +1,5 @@
1+ 10  11   
2+ 11  12  func getContent() string { 
3+ 12    -  return "Hello, world!" 
4+    13 +  content := strings.ToUpper("Hello, World!") 
5+  …   …  … 
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf11.goldennew file+5
@@ -0,0 +1,5 @@
1+ 11  12  func getContent() string { 
2+ 12    -  return "Hello, world!" 
3+    13 +  content := strings.ToUpper("Hello, World!") 
4+    14 +  return content 
5+ 13  15  } 
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf12.goldennew file+5
@@ -0,0 +1,5 @@
1+ 12    -  return "Hello, world!" 
2+    13 +  content := strings.ToUpper("Hello, World!") 
3+    14 +  return content 
4+ 13  15  } 
5+        
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf13.goldennew file+5
@@ -0,0 +1,5 @@
1+    13 +  content := strings.ToUpper("Hello, World!") 
2+    14 +  return content 
3+ 13  15  } 
4+        
5+        
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf14.goldennew file+5
@@ -0,0 +1,5 @@
1+    14 +  return content 
2+ 13  15  } 
3+        
4+        
5+        
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf15.goldennew file+5
@@ -0,0 +1,5 @@
1+ 13  15  } 
2+        
3+        
4+        
5+        
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf16.goldennew file+5
@@ -0,0 +1,5 @@
1+        
2+        
3+        
4+        
5+        
internal/exp/diffview/testdata/TestMultipleHunks.afternew file+15
@@ -0,0 +1,15 @@
1+package main
2+
3+import (
4+ "fmt"
5+ "strings"
6+)
7+
8+func main() {
9+ fmt.Println(getContent())
10+}
11+
12+func getContent() string {
13+ content := strings.ToUpper("Hello, World!")
14+ return content
15+}
internal/exp/diffview/testdata/TestMultipleHunks.beforenew file+13
@@ -0,0 +1,13 @@
1+package main
2+
3+import (
4+ "fmt"
5+)
6+
7+func main() {
8+ fmt.Println(getContent())
9+}
10+
11+func getContent() string {
12+ return "Hello, world!"
13+}
internal/exp/diffview/testdata/TestNarrow.afternew file+3
@@ -0,0 +1,3 @@
1+d
2+e
3+f
internal/exp/diffview/testdata/TestNarrow.beforenew file+3
@@ -0,0 +1,3 @@
1+a
2+b
3+c
internal/exp/diffview/testdata/TestTabs.afternew file+15
@@ -0,0 +1,15 @@
1+package main
2+
3+import (
4+ "fmt"
5+ "strings"
6+)
7+
8+func main() {
9+ fmt.Println(getContent())
10+}
11+
12+func getContent() string {
13+ content := strings.ToUpper("Hello, World!")
14+ return content
15+}
internal/exp/diffview/testdata/TestTabs.beforenew file+13
@@ -0,0 +1,13 @@
1+package main
2+
3+import (
4+ "fmt"
5+)
6+
7+func main() {
8+ fmt.Println(getContent())
9+}
10+
11+func getContent() string {
12+ return "Hello, world!"
13+}
internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLines/Content.goldennew file+11
@@ -0,0 +1,11 @@
1+--- main.go
2++++ main.go
3+@@ -5,5 +5,6 @@
4+ )
5+
6+ func main() {
7+- fmt.Println("Hello, World!")
8++ content := "Hello, World!"
9++ fmt.Println(content)
10+ }
11+\ No newline at end of file
internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLines/JSON.goldennew file+40
@@ -0,0 +1,40 @@
1+{
2+ "From": "main.go",
3+ "To": "main.go",
4+ "Hunks": [
5+ {
6+ "FromLine": 5,
7+ "ToLine": 5,
8+ "Lines": [
9+ {
10+ "Kind": 2,
11+ "Content": "\t)\n"
12+ },
13+ {
14+ "Kind": 2,
15+ "Content": "\n"
16+ },
17+ {
18+ "Kind": 2,
19+ "Content": "\tfunc main() {\n"
20+ },
21+ {
22+ "Kind": 0,
23+ "Content": "\t\tfmt.Println(\"Hello, World!\")\n"
24+ },
25+ {
26+ "Kind": 1,
27+ "Content": "\t\tcontent := \"Hello, World!\"\n"
28+ },
29+ {
30+ "Kind": 1,
31+ "Content": "\t\tfmt.Println(content)\n"
32+ },
33+ {
34+ "Kind": 2,
35+ "Content": "\t}"
36+ }
37+ ]
38+ }
39+ ]
40+}
internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusOne/Content.goldennew file+12
@@ -0,0 +1,12 @@
1+--- main.go
2++++ main.go
3+@@ -4,6 +4,7 @@
4+ "fmt"
5+ )
6+
7+ func main() {
8+- fmt.Println("Hello, World!")
9++ content := "Hello, World!"
10++ fmt.Println(content)
11+ }
12+\ No newline at end of file
internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusOne/JSON.goldennew file+44
@@ -0,0 +1,44 @@
1+{
2+ "From": "main.go",
3+ "To": "main.go",
4+ "Hunks": [
5+ {
6+ "FromLine": 4,
7+ "ToLine": 4,
8+ "Lines": [
9+ {
10+ "Kind": 2,
11+ "Content": "\t\t\"fmt\"\n"
12+ },
13+ {
14+ "Kind": 2,
15+ "Content": "\t)\n"
16+ },
17+ {
18+ "Kind": 2,
19+ "Content": "\n"
20+ },
21+ {
22+ "Kind": 2,
23+ "Content": "\tfunc main() {\n"
24+ },
25+ {
26+ "Kind": 0,
27+ "Content": "\t\tfmt.Println(\"Hello, World!\")\n"
28+ },
29+ {
30+ "Kind": 1,
31+ "Content": "\t\tcontent := \"Hello, World!\"\n"
32+ },
33+ {
34+ "Kind": 1,
35+ "Content": "\t\tfmt.Println(content)\n"
36+ },
37+ {
38+ "Kind": 2,
39+ "Content": "\t}"
40+ }
41+ ]
42+ }
43+ ]
44+}
internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusTwo/Content.goldennew file+13
@@ -0,0 +1,13 @@
1+--- main.go
2++++ main.go
3+@@ -3,7 +3,8 @@
4+ import (
5+ "fmt"
6+ )
7+
8+ func main() {
9+- fmt.Println("Hello, World!")
10++ content := "Hello, World!"
11++ fmt.Println(content)
12+ }
13+\ No newline at end of file
internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusTwo/JSON.goldennew file+48
@@ -0,0 +1,48 @@
1+{
2+ "From": "main.go",
3+ "To": "main.go",
4+ "Hunks": [
5+ {
6+ "FromLine": 3,
7+ "ToLine": 3,
8+ "Lines": [
9+ {
10+ "Kind": 2,
11+ "Content": "\timport (\n"
12+ },
13+ {
14+ "Kind": 2,
15+ "Content": "\t\t\"fmt\"\n"
16+ },
17+ {
18+ "Kind": 2,
19+ "Content": "\t)\n"
20+ },
21+ {
22+ "Kind": 2,
23+ "Content": "\n"
24+ },
25+ {
26+ "Kind": 2,
27+ "Content": "\tfunc main() {\n"
28+ },
29+ {
30+ "Kind": 0,
31+ "Content": "\t\tfmt.Println(\"Hello, World!\")\n"
32+ },
33+ {
34+ "Kind": 1,
35+ "Content": "\t\tcontent := \"Hello, World!\"\n"
36+ },
37+ {
38+ "Kind": 1,
39+ "Content": "\t\tfmt.Println(content)\n"
40+ },
41+ {
42+ "Kind": 2,
43+ "Content": "\t}"
44+ }
45+ ]
46+ }
47+ ]
48+}
internal/exp/diffview/testdata/TestUdiff/Unified.goldennew file+11
@@ -0,0 +1,11 @@
1+--- main.go
2++++ main.go
3+@@ -5,5 +5,6 @@
4+ )
5+
6+ func main() {
7+- fmt.Println("Hello, World!")
8++ content := "Hello, World!"
9++ fmt.Println(content)
10+ }
11+\ No newline at end of file
internal/exp/diffview/udiff_test.gonew file+92
@@ -0,0 +1,92 @@
1+package diffview_test
2+
3+import (
4+ "bytes"
5+ "encoding/json"
6+ "testing"
7+
8+ "github.com/aymanbagabas/go-udiff"
9+ "github.com/aymanbagabas/go-udiff/myers"
10+ "github.com/charmbracelet/x/exp/golden"
11+)
12+
13+func TestUdiff(t *testing.T) {
14+ before := `package main
15+
16+ import (
17+ "fmt"
18+ )
19+
20+ func main() {
21+ fmt.Println("Hello, World!")
22+ }`
23+
24+ after := `package main
25+
26+ import (
27+ "fmt"
28+ )
29+
30+ func main() {
31+ content := "Hello, World!"
32+ fmt.Println(content)
33+ }`
34+
35+ t.Run("Unified", func(t *testing.T) {
36+ content := udiff.Unified("main.go", "main.go", before, after)
37+ golden.RequireEqual(t, []byte(content))
38+ })
39+
40+ t.Run("ToUnifiedDiff", func(t *testing.T) {
41+ toUnifiedDiff := func(t *testing.T, before, after string, contextLines int) udiff.UnifiedDiff {
42+ edits := myers.ComputeEdits(before, after) //nolint:staticcheck
43+ unifiedDiff, err := udiff.ToUnifiedDiff("main.go", "main.go", before, edits, contextLines)
44+ if err != nil {
45+ t.Fatalf("ToUnifiedDiff failed: %v", err)
46+ }
47+ return unifiedDiff
48+ }
49+ toJSON := func(t *testing.T, unifiedDiff udiff.UnifiedDiff) []byte {
50+ var buff bytes.Buffer
51+ encoder := json.NewEncoder(&buff)
52+ encoder.SetIndent("", " ")
53+ if err := encoder.Encode(unifiedDiff); err != nil {
54+ t.Fatalf("Failed to encode unified diff: %v", err)
55+ }
56+ return buff.Bytes()
57+ }
58+
59+ t.Run("DefaultContextLines", func(t *testing.T) {
60+ unifiedDiff := toUnifiedDiff(t, before, after, udiff.DefaultContextLines)
61+
62+ t.Run("Content", func(t *testing.T) {
63+ golden.RequireEqual(t, []byte(unifiedDiff.String()))
64+ })
65+ t.Run("JSON", func(t *testing.T) {
66+ golden.RequireEqual(t, toJSON(t, unifiedDiff))
67+ })
68+ })
69+
70+ t.Run("DefaultContextLinesPlusOne", func(t *testing.T) {
71+ unifiedDiff := toUnifiedDiff(t, before, after, udiff.DefaultContextLines+1)
72+
73+ t.Run("Content", func(t *testing.T) {
74+ golden.RequireEqual(t, []byte(unifiedDiff.String()))
75+ })
76+ t.Run("JSON", func(t *testing.T) {
77+ golden.RequireEqual(t, toJSON(t, unifiedDiff))
78+ })
79+ })
80+
81+ t.Run("DefaultContextLinesPlusTwo", func(t *testing.T) {
82+ unifiedDiff := toUnifiedDiff(t, before, after, udiff.DefaultContextLines+2)
83+
84+ t.Run("Content", func(t *testing.T) {
85+ golden.RequireEqual(t, []byte(unifiedDiff.String()))
86+ })
87+ t.Run("JSON", func(t *testing.T) {
88+ golden.RequireEqual(t, toJSON(t, unifiedDiff))
89+ })
90+ })
91+ })
92+}
internal/exp/diffview/util.gonew file+39
@@ -0,0 +1,39 @@
1+package diffview
2+
3+import (
4+ "fmt"
5+ "strings"
6+
7+ "github.com/charmbracelet/x/ansi"
8+)
9+
10+func pad(v any, width int) string {
11+ s := fmt.Sprintf("%v", v)
12+ w := ansi.StringWidth(s)
13+ if w >= width {
14+ return s
15+ }
16+ return strings.Repeat(" ", width-w) + s
17+}
18+
19+func isEven(n int) bool {
20+ return n%2 == 0
21+}
22+
23+func isOdd(n int) bool {
24+ return !isEven(n)
25+}
26+
27+func btoi(b bool) int {
28+ if b {
29+ return 1
30+ }
31+ return 0
32+}
33+
34+func ternary[T any](cond bool, t, f T) T {
35+ if cond {
36+ return t
37+ }
38+ return f
39+}
internal/exp/diffview/util_test.gonew file+27
@@ -0,0 +1,27 @@
1+package diffview
2+
3+import (
4+ "testing"
5+)
6+
7+func TestPad(t *testing.T) {
8+ tests := []struct {
9+ input any
10+ width int
11+ expected string
12+ }{
13+ {7, 2, " 7"},
14+ {7, 3, " 7"},
15+ {"a", 2, " a"},
16+ {"a", 3, " a"},
17+ {"…", 2, " …"},
18+ {"…", 3, " …"},
19+ }
20+
21+ for _, tt := range tests {
22+ result := pad(tt.input, tt.width)
23+ if result != tt.expected {
24+ t.Errorf("expected %q, got %q", tt.expected, result)
25+ }
26+ }
27+}
internal/fileutil/fileutil.go → internal/fsext/fileutil.gorenamed+25−1
@@ -1,4 +1,4 @@
1−package fileutil
1+package fsext
22
33import (
44 "fmt"
@@ -211,3 +211,27 @@ func PrettyPath(path string) string {
211211 }
212212 return path
213213}
214+
215+func DirTrim(pwd string, lim int) string {
216+ var (
217+ out string
218+ sep = string(filepath.Separator)
219+ )
220+ dirs := strings.Split(pwd, sep)
221+ if lim > len(dirs)-1 || lim <= 0 {
222+ return pwd
223+ }
224+ for i := len(dirs) - 1; i > 0; i-- {
225+ out = sep + out
226+ if i == len(dirs)-1 {
227+ out = dirs[i]
228+ } else if i >= len(dirs)-lim {
229+ out = string(dirs[i][0]) + out
230+ } else {
231+ out = "..." + out
232+ break
233+ }
234+ }
235+ out = filepath.Join("~", out)
236+ return out
237+}
internal/fileutil/ls.go → internal/fsext/ls.gorenamed+1−1
@@ -1,4 +1,4 @@
1−package fileutil
1+package fsext
22
33import (
44 "os"
internal/history/file.go+7−49
@@ -4,9 +4,7 @@ import (
44 "context"
55 "database/sql"
66 "fmt"
7− "strconv"
87 "strings"
9− "time"
108
119 "github.com/charmbracelet/crush/internal/db"
1210 "github.com/charmbracelet/crush/internal/pubsub"
@@ -14,7 +12,7 @@ import (
1412)
1513
1614const (
17− InitialVersion = "initial"
15+ InitialVersion = 0
1816)
1917
2018type File struct {
@@ -22,7 +20,7 @@ type File struct {
2220 SessionID string
2321 Path string
2422 Content string
25− Version string
23+ Version int64
2624 CreatedAt int64
2725 UpdatedAt int64
2826}
@@ -35,7 +33,6 @@ type Service interface {
3533 GetByPathAndSession(ctx context.Context, path, sessionID string) (File, error)
3634 ListBySession(ctx context.Context, sessionID string) ([]File, error)
3735 ListLatestSessionFiles(ctx context.Context, sessionID string) ([]File, error)
38− Update(ctx context.Context, file File) (File, error)
3936 Delete(ctx context.Context, id string) error
4037 DeleteSessionFiles(ctx context.Context, sessionID string) error
4138}
@@ -71,30 +68,13 @@ func (s *service) CreateVersion(ctx context.Context, sessionID, path, content st
7168 }
7269
7370 // Get the latest version
74− latestFile := files[0] // Files are ordered by created_at DESC
75− latestVersion := latestFile.Version
76−
77− // Generate the next version
78− var nextVersion string
79− if latestVersion == InitialVersion {
80− nextVersion = "v1"
81− } else if strings.HasPrefix(latestVersion, "v") {
82− versionNum, err := strconv.Atoi(latestVersion[1:])
83− if err != nil {
84− // If we can't parse the version, just use a timestamp-based version
85− nextVersion = fmt.Sprintf("v%d", latestFile.CreatedAt)
86− } else {
87− nextVersion = fmt.Sprintf("v%d", versionNum+1)
88− }
89− } else {
90− // If the version format is unexpected, use a timestamp-based version
91− nextVersion = fmt.Sprintf("v%d", latestFile.CreatedAt)
92− }
71+ latestFile := files[0] // Files are ordered by version DESC, created_at DESC
72+ nextVersion := latestFile.Version + 1
9373
9474 return s.createWithVersion(ctx, sessionID, path, content, nextVersion)
9575}
9676
97−func (s *service) createWithVersion(ctx context.Context, sessionID, path, content, version string) (File, error) {
77+func (s *service) createWithVersion(ctx context.Context, sessionID, path, content string, version int64) (File, error) {
9878 // Maximum number of retries for transaction conflicts
9979 const maxRetries = 3
10080 var file File
@@ -126,16 +106,8 @@ func (s *service) createWithVersion(ctx context.Context, sessionID, path, conten
126106 // Check if this is a uniqueness constraint violation
127107 if strings.Contains(txErr.Error(), "UNIQUE constraint failed") {
128108 if attempt < maxRetries-1 {
129− // If we have retries left, generate a new version and try again
130− if strings.HasPrefix(version, "v") {
131− versionNum, parseErr := strconv.Atoi(version[1:])
132− if parseErr == nil {
133− version = fmt.Sprintf("v%d", versionNum+1)
134− continue
135− }
136− }
137− // If we can't parse the version, use a timestamp-based version
138− version = fmt.Sprintf("v%d", time.Now().Unix())
109+ // If we have retries left, increment version and try again
110+ version++
139111 continue
140112 }
141113 }
@@ -198,20 +170,6 @@ func (s *service) ListLatestSessionFiles(ctx context.Context, sessionID string)
198170 return files, nil
199171}
200172
201−func (s *service) Update(ctx context.Context, file File) (File, error) {
202− dbFile, err := s.q.UpdateFile(ctx, db.UpdateFileParams{
203− ID: file.ID,
204− Content: file.Content,
205− Version: file.Version,
206− })
207− if err != nil {
208− return File{}, err
209− }
210− updatedFile := s.fromDBItem(dbFile)
211− s.Publish(pubsub.UpdatedEvent, updatedFile)
212− return updatedFile, nil
213−}
214−
215173func (s *service) Delete(ctx context.Context, id string) error {
216174 file, err := s.Get(ctx, id)
217175 if err != nil {
internal/llm/agent/agent.go+16−5
@@ -163,7 +163,9 @@ func (a *agent) generateTitle(ctx context.Context, sessionID string, content str
163163 return err
164164 }
165165 parts := []message.ContentPart{message.TextContent{Text: content}}
166− response, err := a.titleProvider.SendMessages(
166+
167+ // Use streaming approach like summarization
168+ response := a.titleProvider.StreamResponse(
167169 ctx,
168170 []message.Message{
169171 {
@@ -173,11 +175,20 @@ func (a *agent) generateTitle(ctx context.Context, sessionID string, content str
173175 },
174176 make([]tools.BaseTool, 0),
175177 )
176− if err != nil {
177− return err
178+
179+ var finalResponse *provider.ProviderResponse
180+ for r := range response {
181+ if r.Error != nil {
182+ return r.Error
183+ }
184+ finalResponse = r.Response
185+ }
186+
187+ if finalResponse == nil {
188+ return fmt.Errorf("no response received from title provider")
178189 }
179190
180− title := strings.TrimSpace(strings.ReplaceAll(response.Content, "\n", " "))
191+ title := strings.TrimSpace(strings.ReplaceAll(finalResponse.Content, "\n", " "))
181192 if title == "" {
182193 return nil
183194 }
@@ -241,7 +252,7 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
241252 logging.ErrorPersist("panic while generating title")
242253 })
243254 titleErr := a.generateTitle(context.Background(), sessionID, content)
244− if titleErr != nil {
255+ if titleErr != nil && !errors.Is(titleErr, context.Canceled) && !errors.Is(titleErr, context.DeadlineExceeded) {
245256 logging.ErrorPersist(fmt.Sprintf("failed to generate title: %v", titleErr))
246257 }
247258 }()
internal/llm/agent/tools.go−1
@@ -33,7 +33,6 @@ func CoderAgentTools(
3333 tools.NewLsTool(),
3434 tools.NewSourcegraphTool(),
3535 tools.NewViewTool(lspClients),
36− tools.NewPatchTool(lspClients, permissions, history),
3736 tools.NewWriteTool(lspClients, permissions, history),
3837 NewAgentTool(sessions, messages, lspClients),
3938 }, otherTools...,
internal/llm/tools/edit.go+31−23
@@ -24,14 +24,16 @@ type EditParams struct {
2424}
2525
2626type EditPermissionsParams struct {
27− FilePath string `json:"file_path"`
28− Diff string `json:"diff"`
27+ FilePath string `json:"file_path"`
28+ OldContent string `json:"old_content,omitempty"`
29+ NewContent string `json:"new_content,omitempty"`
2930}
3031
3132type EditResponseMetadata struct {
32− Diff string `json:"diff"`
33− Additions int `json:"additions"`
34− Removals int `json:"removals"`
33+ Additions int `json:"additions"`
34+ Removals int `json:"removals"`
35+ OldContent string `json:"old_content,omitempty"`
36+ NewContent string `json:"new_content,omitempty"`
3537}
3638
3739type editTool struct {
@@ -191,7 +193,7 @@ func (e *editTool) createNewFile(ctx context.Context, filePath, content string)
191193 return ToolResponse{}, fmt.Errorf("session ID and message ID are required for creating a new file")
192194 }
193195
194− diff, additions, removals := diff.GenerateDiff(
196+ _, additions, removals := diff.GenerateDiff(
195197 "",
196198 content,
197199 filePath,
@@ -209,8 +211,9 @@ func (e *editTool) createNewFile(ctx context.Context, filePath, content string)
209211 Action: "write",
210212 Description: fmt.Sprintf("Create file %s", filePath),
211213 Params: EditPermissionsParams{
212− FilePath: filePath,
213− Diff: diff,
214+ FilePath: filePath,
215+ OldContent: "",
216+ NewContent: content,
214217 },
215218 },
216219 )
@@ -243,9 +246,10 @@ func (e *editTool) createNewFile(ctx context.Context, filePath, content string)
243246 return WithResponseMetadata(
244247 NewTextResponse("File created: "+filePath),
245248 EditResponseMetadata{
246− Diff: diff,
247− Additions: additions,
248− Removals: removals,
249+ OldContent: "",
250+ NewContent: content,
251+ Additions: additions,
252+ Removals: removals,
249253 },
250254 ), nil
251255}
@@ -301,7 +305,7 @@ func (e *editTool) deleteContent(ctx context.Context, filePath, oldString string
301305 return ToolResponse{}, fmt.Errorf("session ID and message ID are required for creating a new file")
302306 }
303307
304− diff, additions, removals := diff.GenerateDiff(
308+ _, additions, removals := diff.GenerateDiff(
305309 oldContent,
306310 newContent,
307311 filePath,
@@ -320,8 +324,9 @@ func (e *editTool) deleteContent(ctx context.Context, filePath, oldString string
320324 Action: "write",
321325 Description: fmt.Sprintf("Delete content from file %s", filePath),
322326 Params: EditPermissionsParams{
323− FilePath: filePath,
324− Diff: diff,
327+ FilePath: filePath,
328+ OldContent: oldContent,
329+ NewContent: newContent,
325330 },
326331 },
327332 )
@@ -362,9 +367,10 @@ func (e *editTool) deleteContent(ctx context.Context, filePath, oldString string
362367 return WithResponseMetadata(
363368 NewTextResponse("Content deleted from file: "+filePath),
364369 EditResponseMetadata{
365− Diff: diff,
366− Additions: additions,
367− Removals: removals,
370+ OldContent: oldContent,
371+ NewContent: newContent,
372+ Additions: additions,
373+ Removals: removals,
368374 },
369375 ), nil
370376}
@@ -422,7 +428,7 @@ func (e *editTool) replaceContent(ctx context.Context, filePath, oldString, newS
422428 if sessionID == "" || messageID == "" {
423429 return ToolResponse{}, fmt.Errorf("session ID and message ID are required for creating a new file")
424430 }
425− diff, additions, removals := diff.GenerateDiff(
431+ _, additions, removals := diff.GenerateDiff(
426432 oldContent,
427433 newContent,
428434 filePath,
@@ -440,8 +446,9 @@ func (e *editTool) replaceContent(ctx context.Context, filePath, oldString, newS
440446 Action: "write",
441447 Description: fmt.Sprintf("Replace content in file %s", filePath),
442448 Params: EditPermissionsParams{
443− FilePath: filePath,
444− Diff: diff,
449+ FilePath: filePath,
450+ OldContent: oldContent,
451+ NewContent: newContent,
445452 },
446453 },
447454 )
@@ -482,8 +489,9 @@ func (e *editTool) replaceContent(ctx context.Context, filePath, oldString, newS
482489 return WithResponseMetadata(
483490 NewTextResponse("Content replaced in file: "+filePath),
484491 EditResponseMetadata{
485− Diff: diff,
486− Additions: additions,
487− Removals: removals,
492+ OldContent: oldContent,
493+ NewContent: newContent,
494+ Additions: additions,
495+ Removals: removals,
488496 }), nil
489497}
internal/llm/tools/fetch.go+15−49
@@ -7,7 +7,6 @@ import (
77 "io"
88 "net/http"
99 "strings"
10− "sync"
1110 "time"
1211
1312 md "github.com/JohannesKaufmann/html-to-markdown"
@@ -29,10 +28,8 @@ type FetchPermissionsParams struct {
2928}
3029
3130type fetchTool struct {
32− client *http.Client
33− clientPool map[int]*http.Client
34− clientPoolMu sync.RWMutex
35− permissions permission.Service
31+ client *http.Client
32+ permissions permission.Service
3633}
3734
3835const (
@@ -78,51 +75,10 @@ func NewFetchTool(permissions permission.Service) BaseTool {
7875 IdleConnTimeout: 90 * time.Second,
7976 },
8077 },
81− clientPool: make(map[int]*http.Client),
8278 permissions: permissions,
8379 }
8480}
8581
86−// getClientForTimeout returns a cached client for the given timeout or the default client
87−func (t *fetchTool) getClientForTimeout(timeout int) *http.Client {
88− if timeout <= 0 {
89− return t.client
90− }
91−
92− maxTimeout := 120 // 2 minutes
93− if timeout > maxTimeout {
94− timeout = maxTimeout
95− }
96−
97− // Check if we have a cached client for this timeout
98− t.clientPoolMu.RLock()
99− if client, exists := t.clientPool[timeout]; exists {
100− t.clientPoolMu.RUnlock()
101− return client
102− }
103− t.clientPoolMu.RUnlock()
104−
105− // Create and cache a new client
106− t.clientPoolMu.Lock()
107− defer t.clientPoolMu.Unlock()
108−
109− // Double-check in case another goroutine created it
110− if client, exists := t.clientPool[timeout]; exists {
111− return client
112− }
113−
114− client := &http.Client{
115− Timeout: time.Duration(timeout) * time.Second,
116− Transport: &http.Transport{
117− MaxIdleConns: 100,
118− MaxIdleConnsPerHost: 10,
119− IdleConnTimeout: 90 * time.Second,
120− },
121− }
122− t.clientPool[timeout] = client
123− return client
124−}
125−
12682func (t *fetchTool) Info() ToolInfo {
12783 return ToolInfo{
12884 Name: FetchToolName,
@@ -185,16 +141,26 @@ func (t *fetchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
185141 return ToolResponse{}, permission.ErrorPermissionDenied
186142 }
187143
188− client := t.getClientForTimeout(params.Timeout)
144+ // Handle timeout with context
145+ requestCtx := ctx
146+ if params.Timeout > 0 {
147+ maxTimeout := 120 // 2 minutes
148+ if params.Timeout > maxTimeout {
149+ params.Timeout = maxTimeout
150+ }
151+ var cancel context.CancelFunc
152+ requestCtx, cancel = context.WithTimeout(ctx, time.Duration(params.Timeout)*time.Second)
153+ defer cancel()
154+ }
189155
190− req, err := http.NewRequestWithContext(ctx, "GET", params.URL, nil)
156+ req, err := http.NewRequestWithContext(requestCtx, "GET", params.URL, nil)
191157 if err != nil {
192158 return ToolResponse{}, fmt.Errorf("failed to create request: %w", err)
193159 }
194160
195161 req.Header.Set("User-Agent", "crush/1.0")
196162
197− resp, err := client.Do(req)
163+ resp, err := t.client.Do(req)
198164 if err != nil {
199165 return ToolResponse{}, fmt.Errorf("failed to fetch URL: %w", err)
200166 }
internal/llm/tools/glob.go+4−4
@@ -11,7 +11,7 @@ import (
1111 "strings"
1212
1313 "github.com/charmbracelet/crush/internal/config"
14− "github.com/charmbracelet/crush/internal/fileutil"
14+ "github.com/charmbracelet/crush/internal/fsext"
1515 "github.com/charmbracelet/crush/internal/logging"
1616)
1717
@@ -127,7 +127,7 @@ func (g *globTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
127127}
128128
129129func globFiles(pattern, searchPath string, limit int) ([]string, bool, error) {
130− cmdRg := fileutil.GetRgCmd(pattern)
130+ cmdRg := fsext.GetRgCmd(pattern)
131131 if cmdRg != nil {
132132 cmdRg.Dir = searchPath
133133 matches, err := runRipgrep(cmdRg, searchPath, limit)
@@ -137,7 +137,7 @@ func globFiles(pattern, searchPath string, limit int) ([]string, bool, error) {
137137 logging.Warn(fmt.Sprintf("Ripgrep execution failed: %v. Falling back to doublestar.", err))
138138 }
139139
140− return fileutil.GlobWithDoubleStar(pattern, searchPath, limit)
140+ return fsext.GlobWithDoubleStar(pattern, searchPath, limit)
141141}
142142
143143func runRipgrep(cmd *exec.Cmd, searchRoot string, limit int) ([]string, error) {
@@ -158,7 +158,7 @@ func runRipgrep(cmd *exec.Cmd, searchRoot string, limit int) ([]string, error) {
158158 if !filepath.IsAbs(absPath) {
159159 absPath = filepath.Join(searchRoot, absPath)
160160 }
161− if fileutil.SkipHidden(absPath) {
161+ if fsext.SkipHidden(absPath) {
162162 continue
163163 }
164164 matches = append(matches, absPath)
internal/llm/tools/grep.go+13−12
@@ -17,7 +17,7 @@ import (
1717 "time"
1818
1919 "github.com/charmbracelet/crush/internal/config"
20− "github.com/charmbracelet/crush/internal/fileutil"
20+ "github.com/charmbracelet/crush/internal/fsext"
2121)
2222
2323// regexCache provides thread-safe caching of compiled regex patterns
@@ -342,7 +342,7 @@ func searchFilesWithRegex(pattern, rootPath, include string) ([]grepMatch, error
342342 return nil // Skip directories
343343 }
344344
345− if fileutil.SkipHidden(path) {
345+ if fsext.SkipHidden(path) {
346346 return nil
347347 }
348348
@@ -402,19 +402,20 @@ func fileContainsPattern(filePath string, pattern *regexp.Regexp) (bool, int, st
402402 return false, 0, "", scanner.Err()
403403}
404404
405+var binaryExts = map[string]struct{}{
406+ ".exe": {}, ".dll": {}, ".so": {}, ".dylib": {},
407+ ".bin": {}, ".obj": {}, ".o": {}, ".a": {},
408+ ".zip": {}, ".tar": {}, ".gz": {}, ".bz2": {},
409+ ".jpg": {}, ".jpeg": {}, ".png": {}, ".gif": {},
410+ ".pdf": {}, ".doc": {}, ".docx": {}, ".xls": {},
411+ ".mp3": {}, ".mp4": {}, ".avi": {}, ".mov": {},
412+}
413+
405414// isBinaryFile performs a quick check to determine if a file is binary
406415func isBinaryFile(filePath string) bool {
407416 // Check file extension first (fastest)
408417 ext := strings.ToLower(filepath.Ext(filePath))
409− binaryExts := map[string]bool{
410− ".exe": true, ".dll": true, ".so": true, ".dylib": true,
411− ".bin": true, ".obj": true, ".o": true, ".a": true,
412− ".zip": true, ".tar": true, ".gz": true, ".bz2": true,
413− ".jpg": true, ".jpeg": true, ".png": true, ".gif": true,
414− ".pdf": true, ".doc": true, ".docx": true, ".xls": true,
415− ".mp3": true, ".mp4": true, ".avi": true, ".mov": true,
416− }
417− if binaryExts[ext] {
418+ if _, isBinary := binaryExts[ext]; isBinary {
418419 return true
419420 }
420421
@@ -433,7 +434,7 @@ func isBinaryFile(filePath string) bool {
433434 }
434435
435436 // Check for null bytes (common in binary files)
436− for i := 0; i < n; i++ {
437+ for i := range n {
437438 if buffer[i] == 0 {
438439 return true
439440 }
internal/llm/tools/ls.go+2−2
@@ -9,7 +9,7 @@ import (
99 "strings"
1010
1111 "github.com/charmbracelet/crush/internal/config"
12− "github.com/charmbracelet/crush/internal/fileutil"
12+ "github.com/charmbracelet/crush/internal/fsext"
1313)
1414
1515type LSParams struct {
@@ -108,7 +108,7 @@ func (l *lsTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
108108 return NewTextErrorResponse(fmt.Sprintf("path does not exist: %s", searchPath)), nil
109109 }
110110
111− files, truncated, err := fileutil.ListDirectory(searchPath, params.Ignore, MaxLSFiles)
111+ files, truncated, err := fsext.ListDirectory(searchPath, params.Ignore, MaxLSFiles)
112112 if err != nil {
113113 return ToolResponse{}, fmt.Errorf("error listing directory: %w", err)
114114 }
internal/llm/tools/patch.godeleted−372
@@ -1,372 +0,0 @@
1−package tools
2−
3−import (
4− "context"
5− "encoding/json"
6− "fmt"
7− "os"
8− "path/filepath"
9− "time"
10−
11− "github.com/charmbracelet/crush/internal/config"
12− "github.com/charmbracelet/crush/internal/diff"
13− "github.com/charmbracelet/crush/internal/history"
14− "github.com/charmbracelet/crush/internal/logging"
15− "github.com/charmbracelet/crush/internal/lsp"
16− "github.com/charmbracelet/crush/internal/permission"
17−)
18−
19−type PatchParams struct {
20− PatchText string `json:"patch_text"`
21−}
22−
23−type PatchResponseMetadata struct {
24− FilesChanged []string `json:"files_changed"`
25− Additions int `json:"additions"`
26− Removals int `json:"removals"`
27−}
28−
29−type patchTool struct {
30− lspClients map[string]*lsp.Client
31− permissions permission.Service
32− files history.Service
33−}
34−
35−const (
36− PatchToolName = "patch"
37− patchDescription = `Applies a patch to multiple files in one operation. This tool is useful for making coordinated changes across multiple files.
38−
39−The patch text must follow this format:
40−*** Begin Patch
41−*** Update File: /path/to/file
42−@@ Context line (unique within the file)
43− Line to keep
44−-Line to remove
45−+Line to add
46− Line to keep
47−*** Add File: /path/to/new/file
48−+Content of the new file
49−+More content
50−*** Delete File: /path/to/file/to/delete
51−*** End Patch
52−
53−Before using this tool:
54−1. Use the FileRead tool to understand the files' contents and context
55−2. Verify all file paths are correct (use the LS tool)
56−
57−CRITICAL REQUIREMENTS FOR USING THIS TOOL:
58−
59−1. UNIQUENESS: Context lines MUST uniquely identify the specific sections you want to change
60−2. PRECISION: All whitespace, indentation, and surrounding code must match exactly
61−3. VALIDATION: Ensure edits result in idiomatic, correct code
62−4. PATHS: Always use absolute file paths (starting with /)
63−
64−The tool will apply all changes in a single atomic operation.`
65−)
66−
67−func NewPatchTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service) BaseTool {
68− return &patchTool{
69− lspClients: lspClients,
70− permissions: permissions,
71− files: files,
72− }
73−}
74−
75−func (p *patchTool) Info() ToolInfo {
76− return ToolInfo{
77− Name: PatchToolName,
78− Description: patchDescription,
79− Parameters: map[string]any{
80− "patch_text": map[string]any{
81− "type": "string",
82− "description": "The full patch text that describes all changes to be made",
83− },
84− },
85− Required: []string{"patch_text"},
86− }
87−}
88−
89−func (p *patchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
90− var params PatchParams
91− if err := json.Unmarshal([]byte(call.Input), &params); err != nil {
92− return NewTextErrorResponse("invalid parameters"), nil
93− }
94−
95− if params.PatchText == "" {
96− return NewTextErrorResponse("patch_text is required"), nil
97− }
98−
99− // Identify all files needed for the patch and verify they've been read
100− filesToRead := diff.IdentifyFilesNeeded(params.PatchText)
101− for _, filePath := range filesToRead {
102− absPath := filePath
103− if !filepath.IsAbs(absPath) {
104− wd := config.WorkingDirectory()
105− absPath = filepath.Join(wd, absPath)
106− }
107−
108− if getLastReadTime(absPath).IsZero() {
109− return NewTextErrorResponse(fmt.Sprintf("you must read the file %s before patching it. Use the FileRead tool first", filePath)), nil
110− }
111−
112− fileInfo, err := os.Stat(absPath)
113− if err != nil {
114− if os.IsNotExist(err) {
115− return NewTextErrorResponse(fmt.Sprintf("file not found: %s", absPath)), nil
116− }
117− return ToolResponse{}, fmt.Errorf("failed to access file: %w", err)
118− }
119−
120− if fileInfo.IsDir() {
121− return NewTextErrorResponse(fmt.Sprintf("path is a directory, not a file: %s", absPath)), nil
122− }
123−
124− modTime := fileInfo.ModTime()
125− lastRead := getLastReadTime(absPath)
126− if modTime.After(lastRead) {
127− return NewTextErrorResponse(
128− fmt.Sprintf("file %s has been modified since it was last read (mod time: %s, last read: %s)",
129− absPath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339),
130− )), nil
131− }
132− }
133−
134− // Check for new files to ensure they don't already exist
135− filesToAdd := diff.IdentifyFilesAdded(params.PatchText)
136− for _, filePath := range filesToAdd {
137− absPath := filePath
138− if !filepath.IsAbs(absPath) {
139− wd := config.WorkingDirectory()
140− absPath = filepath.Join(wd, absPath)
141− }
142−
143− _, err := os.Stat(absPath)
144− if err == nil {
145− return NewTextErrorResponse(fmt.Sprintf("file already exists and cannot be added: %s", absPath)), nil
146− } else if !os.IsNotExist(err) {
147− return ToolResponse{}, fmt.Errorf("failed to check file: %w", err)
148− }
149− }
150−
151− // Load all required files
152− currentFiles := make(map[string]string)
153− for _, filePath := range filesToRead {
154− absPath := filePath
155− if !filepath.IsAbs(absPath) {
156− wd := config.WorkingDirectory()
157− absPath = filepath.Join(wd, absPath)
158− }
159−
160− content, err := os.ReadFile(absPath)
161− if err != nil {
162− return ToolResponse{}, fmt.Errorf("failed to read file %s: %w", absPath, err)
163− }
164− currentFiles[filePath] = string(content)
165− }
166−
167− // Process the patch
168− patch, fuzz, err := diff.TextToPatch(params.PatchText, currentFiles)
169− if err != nil {
170− return NewTextErrorResponse(fmt.Sprintf("failed to parse patch: %s", err)), nil
171− }
172−
173− if fuzz > 3 {
174− return NewTextErrorResponse(fmt.Sprintf("patch contains fuzzy matches (fuzz level: %d). Please make your context lines more precise", fuzz)), nil
175− }
176−
177− // Convert patch to commit
178− commit, err := diff.PatchToCommit(patch, currentFiles)
179− if err != nil {
180− return NewTextErrorResponse(fmt.Sprintf("failed to create commit from patch: %s", err)), nil
181− }
182−
183− // Get session ID and message ID
184− sessionID, messageID := GetContextValues(ctx)
185− if sessionID == "" || messageID == "" {
186− return ToolResponse{}, fmt.Errorf("session ID and message ID are required for creating a patch")
187− }
188−
189− // Request permission for all changes
190− for path, change := range commit.Changes {
191− switch change.Type {
192− case diff.ActionAdd:
193− dir := filepath.Dir(path)
194− patchDiff, _, _ := diff.GenerateDiff("", *change.NewContent, path)
195− p := p.permissions.Request(
196− permission.CreatePermissionRequest{
197− SessionID: sessionID,
198− Path: dir,
199− ToolName: PatchToolName,
200− Action: "create",
201− Description: fmt.Sprintf("Create file %s", path),
202− Params: EditPermissionsParams{
203− FilePath: path,
204− Diff: patchDiff,
205− },
206− },
207− )
208− if !p {
209− return ToolResponse{}, permission.ErrorPermissionDenied
210− }
211− case diff.ActionUpdate:
212− currentContent := ""
213− if change.OldContent != nil {
214− currentContent = *change.OldContent
215− }
216− newContent := ""
217− if change.NewContent != nil {
218− newContent = *change.NewContent
219− }
220− patchDiff, _, _ := diff.GenerateDiff(currentContent, newContent, path)
221− dir := filepath.Dir(path)
222− p := p.permissions.Request(
223− permission.CreatePermissionRequest{
224− SessionID: sessionID,
225− Path: dir,
226− ToolName: PatchToolName,
227− Action: "update",
228− Description: fmt.Sprintf("Update file %s", path),
229− Params: EditPermissionsParams{
230− FilePath: path,
231− Diff: patchDiff,
232− },
233− },
234− )
235− if !p {
236− return ToolResponse{}, permission.ErrorPermissionDenied
237− }
238− case diff.ActionDelete:
239− dir := filepath.Dir(path)
240− patchDiff, _, _ := diff.GenerateDiff(*change.OldContent, "", path)
241− p := p.permissions.Request(
242− permission.CreatePermissionRequest{
243− SessionID: sessionID,
244− Path: dir,
245− ToolName: PatchToolName,
246− Action: "delete",
247− Description: fmt.Sprintf("Delete file %s", path),
248− Params: EditPermissionsParams{
249− FilePath: path,
250− Diff: patchDiff,
251− },
252− },
253− )
254− if !p {
255− return ToolResponse{}, permission.ErrorPermissionDenied
256− }
257− }
258− }
259−
260− // Apply the changes to the filesystem
261− err = diff.ApplyCommit(commit, func(path string, content string) error {
262− absPath := path
263− if !filepath.IsAbs(absPath) {
264− wd := config.WorkingDirectory()
265− absPath = filepath.Join(wd, absPath)
266− }
267−
268− // Create parent directories if needed
269− dir := filepath.Dir(absPath)
270− if err := os.MkdirAll(dir, 0o755); err != nil {
271− return fmt.Errorf("failed to create parent directories for %s: %w", absPath, err)
272− }
273−
274− return os.WriteFile(absPath, []byte(content), 0o644)
275− }, func(path string) error {
276− absPath := path
277− if !filepath.IsAbs(absPath) {
278− wd := config.WorkingDirectory()
279− absPath = filepath.Join(wd, absPath)
280− }
281− return os.Remove(absPath)
282− })
283− if err != nil {
284− return NewTextErrorResponse(fmt.Sprintf("failed to apply patch: %s", err)), nil
285− }
286−
287− // Update file history for all modified files
288− changedFiles := []string{}
289− totalAdditions := 0
290− totalRemovals := 0
291−
292− for path, change := range commit.Changes {
293− absPath := path
294− if !filepath.IsAbs(absPath) {
295− wd := config.WorkingDirectory()
296− absPath = filepath.Join(wd, absPath)
297− }
298− changedFiles = append(changedFiles, absPath)
299−
300− oldContent := ""
301− if change.OldContent != nil {
302− oldContent = *change.OldContent
303− }
304−
305− newContent := ""
306− if change.NewContent != nil {
307− newContent = *change.NewContent
308− }
309−
310− // Calculate diff statistics
311− _, additions, removals := diff.GenerateDiff(oldContent, newContent, path)
312− totalAdditions += additions
313− totalRemovals += removals
314−
315− // Update history
316− file, err := p.files.GetByPathAndSession(ctx, absPath, sessionID)
317− if err != nil && change.Type != diff.ActionAdd {
318− // If not adding a file, create history entry for existing file
319− _, err = p.files.Create(ctx, sessionID, absPath, oldContent)
320− if err != nil {
321− logging.Debug("Error creating file history", "error", err)
322− }
323− }
324−
325− if err == nil && change.Type != diff.ActionAdd && file.Content != oldContent {
326− // User manually changed content, store intermediate version
327− _, err = p.files.CreateVersion(ctx, sessionID, absPath, oldContent)
328− if err != nil {
329− logging.Debug("Error creating file history version", "error", err)
330− }
331− }
332−
333− // Store new version
334− if change.Type == diff.ActionDelete {
335− _, err = p.files.CreateVersion(ctx, sessionID, absPath, "")
336− } else {
337− _, err = p.files.CreateVersion(ctx, sessionID, absPath, newContent)
338− }
339− if err != nil {
340− logging.Debug("Error creating file history version", "error", err)
341− }
342−
343− // Record file operations
344− recordFileWrite(absPath)
345− recordFileRead(absPath)
346− }
347−
348− // Run LSP diagnostics on all changed files
349− for _, filePath := range changedFiles {
350− waitForLspDiagnostics(ctx, filePath, p.lspClients)
351− }
352−
353− result := fmt.Sprintf("Patch applied successfully. %d files changed, %d additions, %d removals",
354− len(changedFiles), totalAdditions, totalRemovals)
355−
356− diagnosticsText := ""
357− for _, filePath := range changedFiles {
358− diagnosticsText += getDiagnostics(filePath, p.lspClients)
359− }
360−
361− if diagnosticsText != "" {
362− result += "\n\nDiagnostics:\n" + diagnosticsText
363− }
364−
365− return WithResponseMetadata(
366− NewTextResponse(result),
367− PatchResponseMetadata{
368− FilesChanged: changedFiles,
369− Additions: totalAdditions,
370− Removals: totalRemovals,
371− }), nil
372−}
internal/llm/tools/shell/comparison_test.go−30
@@ -3,7 +3,6 @@ package shell
33import (
44 "context"
55 "os"
6− "runtime"
76 "testing"
87 "time"
98
@@ -32,35 +31,6 @@ func TestShellPerformanceComparison(t *testing.T) {
3231 t.Logf("Quick command took: %v", duration)
3332}
3433
35−func TestShellCPUUsageComparison(t *testing.T) {
36− tmpDir, err := os.MkdirTemp("", "shell-test")
37− require.NoError(t, err)
38− defer os.RemoveAll(tmpDir)
39−
40− shell := GetPersistentShell(tmpDir)
41− defer shell.Close()
42−
43− // Measure CPU and memory usage during a longer command
44− var m1, m2 runtime.MemStats
45− runtime.GC()
46− runtime.ReadMemStats(&m1)
47−
48− start := time.Now()
49− _, stderr, exitCode, _, err := shell.Exec(context.Background(), "sleep 0.1", 1000)
50− duration := time.Since(start)
51−
52− runtime.ReadMemStats(&m2)
53−
54− require.NoError(t, err)
55− assert.Equal(t, 0, exitCode)
56− assert.Empty(t, stderr)
57−
58− memGrowth := m2.Alloc - m1.Alloc
59− t.Logf("Sleep 0.1s command took: %v", duration)
60− t.Logf("Memory growth during polling: %d bytes", memGrowth)
61− t.Logf("GC cycles during test: %d", m2.NumGC-m1.NumGC)
62−}
63−
6434// Benchmark CPU usage during polling
6535func BenchmarkShellPolling(b *testing.B) {
6636 tmpDir, err := os.MkdirTemp("", "shell-bench")
internal/llm/tools/shell/shell_test.go−25
@@ -4,35 +4,10 @@ import (
44 "context"
55 "os"
66 "testing"
7− "time"
87
9− "github.com/stretchr/testify/assert"
108 "github.com/stretchr/testify/require"
119)
1210
13−func TestShellPerformanceImprovement(t *testing.T) {
14− // Create a temporary directory for the shell
15− tmpDir, err := os.MkdirTemp("", "shell-test")
16− require.NoError(t, err)
17− defer os.RemoveAll(tmpDir)
18−
19− shell := GetPersistentShell(tmpDir)
20− defer shell.Close()
21−
22− // Test that quick commands complete fast
23− start := time.Now()
24− stdout, stderr, exitCode, _, err := shell.Exec(context.Background(), "echo 'hello world'", 0)
25− duration := time.Since(start)
26−
27− require.NoError(t, err)
28− assert.Equal(t, 0, exitCode)
29− assert.Contains(t, stdout, "hello world")
30− assert.Empty(t, stderr)
31−
32− // Quick commands should complete very fast with our exponential backoff
33− assert.Less(t, duration, 50*time.Millisecond, "Quick command should complete fast with exponential backoff")
34−}
35−
3611// Benchmark to measure CPU efficiency
3712func BenchmarkShellQuickCommands(b *testing.B) {
3813 tmpDir, err := os.MkdirTemp("", "shell-bench")
internal/llm/tools/sourcegraph.go+15−48
@@ -8,7 +8,6 @@ import (
88 "io"
99 "net/http"
1010 "strings"
11− "sync"
1211 "time"
1312)
1413
@@ -25,9 +24,7 @@ type SourcegraphResponseMetadata struct {
2524}
2625
2726type sourcegraphTool struct {
28− client *http.Client
29− clientPool map[int]*http.Client
30− clientPoolMu sync.RWMutex
27+ client *http.Client
3128}
3229
3330const (
@@ -138,50 +135,9 @@ func NewSourcegraphTool() BaseTool {
138135 IdleConnTimeout: 90 * time.Second,
139136 },
140137 },
141− clientPool: make(map[int]*http.Client),
142138 }
143139}
144140
145−// getClientForTimeout returns a cached client for the given timeout or the default client
146−func (t *sourcegraphTool) getClientForTimeout(timeout int) *http.Client {
147− if timeout <= 0 {
148− return t.client
149− }
150−
151− maxTimeout := 120 // 2 minutes
152− if timeout > maxTimeout {
153− timeout = maxTimeout
154− }
155−
156− // Check if we have a cached client for this timeout
157− t.clientPoolMu.RLock()
158− if client, exists := t.clientPool[timeout]; exists {
159− t.clientPoolMu.RUnlock()
160− return client
161− }
162− t.clientPoolMu.RUnlock()
163−
164− // Create and cache a new client
165− t.clientPoolMu.Lock()
166− defer t.clientPoolMu.Unlock()
167−
168− // Double-check in case another goroutine created it
169− if client, exists := t.clientPool[timeout]; exists {
170− return client
171− }
172−
173− client := &http.Client{
174− Timeout: time.Duration(timeout) * time.Second,
175− Transport: &http.Transport{
176− MaxIdleConns: 100,
177− MaxIdleConnsPerHost: 10,
178− IdleConnTimeout: 90 * time.Second,
179− },
180− }
181− t.clientPool[timeout] = client
182− return client
183−}
184−
185141func (t *sourcegraphTool) Info() ToolInfo {
186142 return ToolInfo{
187143 Name: SourcegraphToolName,
@@ -227,7 +183,18 @@ func (t *sourcegraphTool) Run(ctx context.Context, call ToolCall) (ToolResponse,
227183 if params.ContextWindow <= 0 {
228184 params.ContextWindow = 10 // Default context window
229185 }
230− client := t.getClientForTimeout(params.Timeout)
186+
187+ // Handle timeout with context
188+ requestCtx := ctx
189+ if params.Timeout > 0 {
190+ maxTimeout := 120 // 2 minutes
191+ if params.Timeout > maxTimeout {
192+ params.Timeout = maxTimeout
193+ }
194+ var cancel context.CancelFunc
195+ requestCtx, cancel = context.WithTimeout(ctx, time.Duration(params.Timeout)*time.Second)
196+ defer cancel()
197+ }
231198
232199 type graphqlRequest struct {
233200 Query string `json:"query"`
@@ -248,7 +215,7 @@ func (t *sourcegraphTool) Run(ctx context.Context, call ToolCall) (ToolResponse,
248215 graphqlQuery := string(graphqlQueryBytes)
249216
250217 req, err := http.NewRequestWithContext(
251− ctx,
218+ requestCtx,
252219 "POST",
253220 "https://sourcegraph.com/.api/graphql",
254221 bytes.NewBuffer([]byte(graphqlQuery)),
@@ -260,7 +227,7 @@ func (t *sourcegraphTool) Run(ctx context.Context, call ToolCall) (ToolResponse,
260227 req.Header.Set("Content-Type", "application/json")
261228 req.Header.Set("User-Agent", "crush/1.0")
262229
263− resp, err := client.Do(req)
230+ resp, err := t.client.Do(req)
264231 if err != nil {
265232 return ToolResponse{}, fmt.Errorf("failed to fetch URL: %w", err)
266233 }
internal/lsp/watcher/watcher.go+25−9
@@ -257,7 +257,10 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
257257 }
258258 }
259259
260− // For each pattern, find and open matching files
260+ // Collect all files to open first
261+ var filesToOpen []string
262+
263+ // For each pattern, find matching files
261264 for _, pattern := range patterns {
262265 // Use doublestar.Glob to find files matching the pattern (supports ** patterns)
263266 matches, err := doublestar.Glob(os.DirFS(w.workspacePath), pattern)
@@ -278,7 +281,23 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
278281 continue
279282 }
280283
281− // Open the file
284+ filesToOpen = append(filesToOpen, fullPath)
285+
286+ // Limit the number of files per pattern
287+ if len(filesToOpen) >= 5 && (serverName != "java" && serverName != "jdtls") {
288+ break
289+ }
290+ }
291+ }
292+
293+ // Open files in batches to reduce overhead
294+ batchSize := 3
295+ for i := 0; i < len(filesToOpen); i += batchSize {
296+ end := min(i+batchSize, len(filesToOpen))
297+
298+ // Open batch of files
299+ for j := i; j < end; j++ {
300+ fullPath := filesToOpen[j]
282301 if err := w.client.OpenFile(ctx, fullPath); err != nil {
283302 if cnf.DebugLSP {
284303 logging.Debug("Error opening high-priority file", "path", fullPath, "error", err)
@@ -289,14 +308,11 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
289308 logging.Debug("Opened high-priority file", "path", fullPath)
290309 }
291310 }
311+ }
292312
293− // Add a small delay to prevent overwhelming the server
294− time.Sleep(20 * time.Millisecond)
295−
296− // Limit the number of files opened per pattern
297− if filesOpened >= 5 && (serverName != "java" && serverName != "jdtls") {
298− break
299− }
313+ // Only add delay between batches, not individual files
314+ if end < len(filesToOpen) {
315+ time.Sleep(50 * time.Millisecond)
300316 }
301317 }
302318
internal/tui/components/chat/editor/editor.go+2−2
@@ -12,7 +12,7 @@ import (
1212 "github.com/charmbracelet/bubbles/v2/textarea"
1313 tea "github.com/charmbracelet/bubbletea/v2"
1414 "github.com/charmbracelet/crush/internal/app"
15− "github.com/charmbracelet/crush/internal/fileutil"
15+ "github.com/charmbracelet/crush/internal/fsext"
1616 "github.com/charmbracelet/crush/internal/logging"
1717 "github.com/charmbracelet/crush/internal/message"
1818 "github.com/charmbracelet/crush/internal/session"
@@ -327,7 +327,7 @@ func (m *editorCmp) SetPosition(x, y int) tea.Cmd {
327327}
328328
329329func (m *editorCmp) startCompletions() tea.Msg {
330− files, _, _ := fileutil.ListDirectory(".", []string{}, 0)
330+ files, _, _ := fsext.ListDirectory(".", []string{}, 0)
331331 completionItems := make([]completions.Completion, 0, len(files))
332332 for _, file := range files {
333333 file = strings.TrimPrefix(file, "./")
internal/tui/components/chat/messages/messages.go+1−1
@@ -167,7 +167,7 @@ func (m *messageCmp) renderAssistantMessage() string {
167167 case message.FinishReasonPermissionDenied:
168168 infoMsg = "permission denied"
169169 }
170− assistant := t.S().Muted.Render(fmt.Sprintf("⬡ %s (%s)", models.SupportedModels[m.message.Model].Name, infoMsg))
170+ assistant := t.S().Muted.Render(fmt.Sprintf("%s %s (%s)", styles.ModelIcon, models.SupportedModels[m.message.Model].Name, infoMsg))
171171 parts = append(parts, core.Section(assistant, m.textWidth()))
172172 }
173173
internal/tui/components/chat/messages/renderer.go+12−44
@@ -6,11 +6,11 @@ import (
66 "strings"
77 "time"
88
9− "github.com/charmbracelet/crush/internal/diff"
10− "github.com/charmbracelet/crush/internal/fileutil"
9+ "github.com/charmbracelet/crush/internal/fsext"
1110 "github.com/charmbracelet/crush/internal/highlight"
1211 "github.com/charmbracelet/crush/internal/llm/agent"
1312 "github.com/charmbracelet/crush/internal/llm/tools"
13+ "github.com/charmbracelet/crush/internal/tui/components/core"
1414 "github.com/charmbracelet/crush/internal/tui/styles"
1515 "github.com/charmbracelet/lipgloss/v2"
1616 "github.com/charmbracelet/lipgloss/v2/tree"
@@ -148,7 +148,6 @@ func init() {
148148 registry.register(tools.GrepToolName, func() renderer { return grepRenderer{} })
149149 registry.register(tools.LSToolName, func() renderer { return lsRenderer{} })
150150 registry.register(tools.SourcegraphToolName, func() renderer { return sourcegraphRenderer{} })
151− registry.register(tools.PatchToolName, func() renderer { return patchRenderer{} })
152151 registry.register(tools.DiagnosticsToolName, func() renderer { return diagnosticsRenderer{} })
153152 registry.register(agent.AgentToolName, func() renderer { return agentRenderer{} })
154153}
@@ -209,7 +208,7 @@ func (vr viewRenderer) Render(v *toolCallCmp) string {
209208 return vr.renderError(v, "Invalid view parameters")
210209 }
211210
212− file := fileutil.PrettyPath(params.FilePath)
211+ file := fsext.PrettyPath(params.FilePath)
213212 args := newParamBuilder().
214213 addMain(file).
215214 addKeyValue("limit", formatNonZero(params.Limit)).
@@ -249,7 +248,7 @@ func (er editRenderer) Render(v *toolCallCmp) string {
249248 return er.renderError(v, "Invalid edit parameters")
250249 }
251250
252− file := fileutil.PrettyPath(params.FilePath)
251+ file := fsext.PrettyPath(params.FilePath)
253252 args := newParamBuilder().addMain(file).build()
254253
255254 return er.renderWithParams(v, "Edit", args, func() string {
@@ -258,9 +257,12 @@ func (er editRenderer) Render(v *toolCallCmp) string {
258257 return renderPlainContent(v, v.result.Content)
259258 }
260259
261− trunc := truncateHeight(meta.Diff, responseContextHeight)
262− diffView, _ := diff.FormatDiff(trunc, diff.WithTotalWidth(v.textWidth()-2))
263− return diffView
260+ formatter := core.DiffFormatter().
261+ Before(fsext.PrettyPath(params.FilePath), meta.OldContent).
262+ After(fsext.PrettyPath(params.FilePath), meta.NewContent).
263+ Split().
264+ Width(v.textWidth() - 2) // -2 for padding
265+ return formatter.String()
264266 })
265267}
266268
@@ -280,7 +282,7 @@ func (wr writeRenderer) Render(v *toolCallCmp) string {
280282 return wr.renderError(v, "Invalid write parameters")
281283 }
282284
283− file := fileutil.PrettyPath(params.FilePath)
285+ file := fsext.PrettyPath(params.FilePath)
284286 args := newParamBuilder().addMain(file).build()
285287
286288 return wr.renderWithParams(v, "Write", args, func() string {
@@ -410,7 +412,7 @@ func (lr lsRenderer) Render(v *toolCallCmp) string {
410412 if path == "" {
411413 path = "."
412414 }
413− path = fileutil.PrettyPath(path)
415+ path = fsext.PrettyPath(path)
414416
415417 args := newParamBuilder().addMain(path).build()
416418
@@ -446,38 +448,6 @@ func (sr sourcegraphRenderer) Render(v *toolCallCmp) string {
446448 })
447449}
448450
449−// -----------------------------------------------------------------------------
450−// Patch renderer
451−// -----------------------------------------------------------------------------
452−
453−// patchRenderer handles multi-file patches with change summaries
454−type patchRenderer struct {
455− baseRenderer
456−}
457−
458−// Render displays patch summary with file count and change statistics
459−func (pr patchRenderer) Render(v *toolCallCmp) string {
460− var params tools.PatchParams
461− if err := pr.unmarshalParams(v.call.Input, &params); err != nil {
462− return pr.renderError(v, "Invalid patch parameters")
463− }
464−
465− args := newParamBuilder().addMain("multiple files").build()
466−
467− return pr.renderWithParams(v, "Patch", args, func() string {
468− var meta tools.PatchResponseMetadata
469− if err := pr.unmarshalParams(v.result.Metadata, &meta); err != nil {
470− return renderPlainContent(v, v.result.Content)
471− }
472−
473− summary := fmt.Sprintf("Changed %d files (%d+ %d-)",
474− len(meta.FilesChanged), meta.Additions, meta.Removals)
475− filesList := strings.Join(meta.FilesChanged, "\n")
476−
477− return renderPlainContent(v, summary+"\n\n"+filesList)
478− })
479−}
480−
481451// -----------------------------------------------------------------------------
482452// Diagnostics renderer
483453// -----------------------------------------------------------------------------
@@ -711,8 +681,6 @@ func prettifyToolName(name string) string {
711681 return "View"
712682 case tools.WriteToolName:
713683 return "Write"
714− case tools.PatchToolName:
715− return "Patch"
716684 default:
717685 return name
718686 }
internal/tui/components/chat/sidebar/sidebar.go+306−7
@@ -1,11 +1,22 @@
11package sidebar
22
33import (
4+ "context"
5+ "fmt"
46 "os"
7+ "sort"
58 "strings"
9+ "sync"
610
711 tea "github.com/charmbracelet/bubbletea/v2"
812 "github.com/charmbracelet/crush/internal/config"
13+ "github.com/charmbracelet/crush/internal/diff"
14+ "github.com/charmbracelet/crush/internal/fsext"
15+ "github.com/charmbracelet/crush/internal/history"
16+ "github.com/charmbracelet/crush/internal/llm/models"
17+ "github.com/charmbracelet/crush/internal/logging"
18+ "github.com/charmbracelet/crush/internal/lsp"
19+ "github.com/charmbracelet/crush/internal/lsp/protocol"
920 "github.com/charmbracelet/crush/internal/pubsub"
1021 "github.com/charmbracelet/crush/internal/session"
1122 "github.com/charmbracelet/crush/internal/tui/components/chat"
@@ -16,12 +27,28 @@ import (
1627 "github.com/charmbracelet/crush/internal/tui/util"
1728 "github.com/charmbracelet/crush/internal/version"
1829 "github.com/charmbracelet/lipgloss/v2"
30+ "github.com/charmbracelet/x/ansi"
1931)
2032
2133const (
2234 logoBreakpoint = 65
2335)
2436
37+type FileHistory struct {
38+ initialVersion history.File
39+ latestVersion history.File
40+}
41+
42+type SessionFile struct {
43+ History FileHistory
44+ FilePath string
45+ Additions int
46+ Deletions int
47+}
48+type SessionFilesMsg struct {
49+ Files []SessionFile
50+}
51+
2552type Sidebar interface {
2653 util.Model
2754 layout.Sizeable
@@ -32,10 +59,17 @@ type sidebarCmp struct {
3259 session session.Session
3360 logo string
3461 cwd string
62+ lspClients map[string]*lsp.Client
63+ history history.Service
64+ // Using a sync map here because we might receive file history events concurrently
65+ files sync.Map
3566}
3667
37−func NewSidebarCmp() Sidebar {
38− return &sidebarCmp{}
68+func NewSidebarCmp(history history.Service, lspClients map[string]*lsp.Client) Sidebar {
69+ return &sidebarCmp{
70+ lspClients: lspClients,
71+ history: history,
72+ }
3973}
4074
4175func (m *sidebarCmp) Init() tea.Cmd {
@@ -50,8 +84,19 @@ func (m *sidebarCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
5084 if msg.ID != m.session.ID {
5185 m.session = msg
5286 }
87+ return m, m.loadSessionFiles
88+ case SessionFilesMsg:
89+ m.files = sync.Map{}
90+ for _, file := range msg.Files {
91+ m.files.Store(file.FilePath, file)
92+ }
93+ return m, nil
94+
5395 case chat.SessionClearedMsg:
5496 m.session = session.Session{}
97+ case pubsub.Event[history.File]:
98+ logging.Info("sidebar", "Received file history event", "file", msg.Payload.Path, "session", msg.Payload.SessionID)
99+ return m, m.handleFileHistoryEvent(msg)
55100 case pubsub.Event[session.Session]:
56101 if msg.Type == pubsub.UpdatedEvent {
57102 if m.session.ID == msg.Payload.ID {
@@ -74,6 +119,13 @@ func (m *sidebarCmp) View() tea.View {
74119
75120 parts = append(parts,
76121 m.cwd,
122+ "",
123+ m.currentModelBlock(),
124+ )
125+ if m.session.ID != "" {
126+ parts = append(parts, "", m.filesBlock())
127+ }
128+ parts = append(parts,
77129 "",
78130 m.lspBlock(),
79131 "",
@@ -85,6 +137,91 @@ func (m *sidebarCmp) View() tea.View {
85137 )
86138}
87139
140+func (m *sidebarCmp) handleFileHistoryEvent(event pubsub.Event[history.File]) tea.Cmd {
141+ return func() tea.Msg {
142+ file := event.Payload
143+ found := false
144+ m.files.Range(func(key, value any) bool {
145+ existing := value.(SessionFile)
146+ if existing.FilePath == file.Path {
147+ if existing.History.latestVersion.Version < file.Version {
148+ existing.History.latestVersion = file
149+ } else if file.Version == 0 {
150+ existing.History.initialVersion = file
151+ } else {
152+ // If the version is not greater than the latest, we ignore it
153+ return true
154+ }
155+ before := existing.History.initialVersion.Content
156+ after := existing.History.latestVersion.Content
157+ path := existing.History.initialVersion.Path
158+ _, additions, deletions := diff.GenerateDiff(before, after, path)
159+ existing.Additions = additions
160+ existing.Deletions = deletions
161+ m.files.Store(file.Path, existing)
162+ found = true
163+ return false
164+ }
165+ return true
166+ })
167+ if found {
168+ return nil
169+ }
170+ sf := SessionFile{
171+ History: FileHistory{
172+ initialVersion: file,
173+ latestVersion: file,
174+ },
175+ FilePath: file.Path,
176+ Additions: 0,
177+ Deletions: 0,
178+ }
179+ m.files.Store(file.Path, sf)
180+ return nil
181+ }
182+}
183+
184+func (m *sidebarCmp) loadSessionFiles() tea.Msg {
185+ files, err := m.history.ListBySession(context.Background(), m.session.ID)
186+ if err != nil {
187+ return util.InfoMsg{
188+ Type: util.InfoTypeError,
189+ Msg: err.Error(),
190+ }
191+ }
192+
193+ fileMap := make(map[string]FileHistory)
194+
195+ for _, file := range files {
196+ if existing, ok := fileMap[file.Path]; ok {
197+ // Update the latest version
198+ existing.latestVersion = file
199+ fileMap[file.Path] = existing
200+ } else {
201+ // Add the initial version
202+ fileMap[file.Path] = FileHistory{
203+ initialVersion: file,
204+ latestVersion: file,
205+ }
206+ }
207+ }
208+
209+ sessionFiles := make([]SessionFile, 0, len(fileMap))
210+ for path, fh := range fileMap {
211+ _, additions, deletions := diff.GenerateDiff(fh.initialVersion.Content, fh.latestVersion.Content, fh.initialVersion.Path)
212+ sessionFiles = append(sessionFiles, SessionFile{
213+ History: fh,
214+ FilePath: path,
215+ Additions: additions,
216+ Deletions: deletions,
217+ })
218+ }
219+
220+ return SessionFilesMsg{
221+ Files: sessionFiles,
222+ }
223+}
224+
88225func (m *sidebarCmp) SetSize(width, height int) tea.Cmd {
89226 if width < logoBreakpoint && m.width >= logoBreakpoint {
90227 m.logo = m.logoBlock(true)
@@ -112,11 +249,74 @@ func (m *sidebarCmp) logoBlock(compact bool) string {
112249 })
113250}
114251
252+func (m *sidebarCmp) filesBlock() string {
253+ maxWidth := min(m.width, 58)
254+ t := styles.CurrentTheme()
255+
256+ section := t.S().Subtle.Render(
257+ core.Section("Modified Files", maxWidth),
258+ )
259+
260+ files := make([]SessionFile, 0)
261+ m.files.Range(func(key, value any) bool {
262+ file := value.(SessionFile)
263+ files = append(files, file)
264+ return true // continue iterating
265+ })
266+ if len(files) == 0 {
267+ return lipgloss.JoinVertical(
268+ lipgloss.Left,
269+ section,
270+ "",
271+ t.S().Base.Foreground(t.Border).Render("None"),
272+ )
273+ }
274+
275+ fileList := []string{section, ""}
276+ // order files by the latest version's created time
277+ sort.Slice(files, func(i, j int) bool {
278+ return files[i].History.latestVersion.CreatedAt > files[j].History.latestVersion.CreatedAt
279+ })
280+
281+ for _, file := range files {
282+ // Extract just the filename from the path
283+
284+ // Create status indicators for additions/deletions
285+ var statusParts []string
286+ if file.Additions > 0 {
287+ statusParts = append(statusParts, t.S().Base.Foreground(t.Success).Render(fmt.Sprintf("+%d", file.Additions)))
288+ }
289+ if file.Deletions > 0 {
290+ statusParts = append(statusParts, t.S().Base.Foreground(t.Error).Render(fmt.Sprintf("-%d", file.Deletions)))
291+ }
292+
293+ extraContent := strings.Join(statusParts, " ")
294+ filePath := fsext.DirTrim(fsext.PrettyPath(file.FilePath), 2)
295+ filePath = ansi.Truncate(filePath, maxWidth-lipgloss.Width(extraContent)-2, "…")
296+ fileList = append(fileList,
297+ core.Status(
298+ core.StatusOpts{
299+ IconColor: t.FgMuted,
300+ NoIcon: true,
301+ Title: filePath,
302+ ExtraContent: extraContent,
303+ },
304+ m.width,
305+ ),
306+ )
307+ }
308+
309+ return lipgloss.JoinVertical(
310+ lipgloss.Left,
311+ fileList...,
312+ )
313+}
314+
115315func (m *sidebarCmp) lspBlock() string {
116316 maxWidth := min(m.width, 58)
117317 t := styles.CurrentTheme()
118318
119− section := t.S().Muted.Render(
319+ section := t.S().Subtle.Render(
120320 core.Section("LSPs", maxWidth),
121321 )
122322
@@ -137,12 +337,43 @@ func (m *sidebarCmp) lspBlock() string {
137337 if l.Disabled {
138338 iconColor = t.FgMuted
139339 }
340+ lspErrs := map[protocol.DiagnosticSeverity]int{
341+ protocol.SeverityError: 0,
342+ protocol.SeverityWarning: 0,
343+ protocol.SeverityHint: 0,
344+ protocol.SeverityInformation: 0,
345+ }
346+ if client, ok := m.lspClients[n]; ok {
347+ for _, diagnostics := range client.GetDiagnostics() {
348+ for _, diagnostic := range diagnostics {
349+ if severity, ok := lspErrs[diagnostic.Severity]; ok {
350+ lspErrs[diagnostic.Severity] = severity + 1
351+ }
352+ }
353+ }
354+ }
355+
356+ errs := []string{}
357+ if lspErrs[protocol.SeverityError] > 0 {
358+ errs = append(errs, t.S().Base.Foreground(t.Error).Render(fmt.Sprintf("%s%d", styles.ErrorIcon, lspErrs[protocol.SeverityError])))
359+ }
360+ if lspErrs[protocol.SeverityWarning] > 0 {
361+ errs = append(errs, t.S().Base.Foreground(t.Warning).Render(fmt.Sprintf("%s%d", styles.WarningIcon, lspErrs[protocol.SeverityWarning])))
362+ }
363+ if lspErrs[protocol.SeverityHint] > 0 {
364+ errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s%d", styles.HintIcon, lspErrs[protocol.SeverityHint])))
365+ }
366+ if lspErrs[protocol.SeverityInformation] > 0 {
367+ errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s%d", styles.InfoIcon, lspErrs[protocol.SeverityInformation])))
368+ }
369+
140370 lspList = append(lspList,
141371 core.Status(
142372 core.StatusOpts{
143− IconColor: iconColor,
144− Title: n,
145− Description: l.Command,
373+ IconColor: iconColor,
374+ Title: n,
375+ Description: l.Command,
376+ ExtraContent: strings.Join(errs, " "),
146377 },
147378 m.width,
148379 ),
@@ -159,7 +390,7 @@ func (m *sidebarCmp) mcpBlock() string {
159390 maxWidth := min(m.width, 58)
160391 t := styles.CurrentTheme()
161392
162− section := t.S().Muted.Render(
393+ section := t.S().Subtle.Render(
163394 core.Section("MCPs", maxWidth),
164395 )
165396
@@ -195,6 +426,74 @@ func (m *sidebarCmp) mcpBlock() string {
195426 )
196427}
197428
429+func formatTokensAndCost(tokens, contextWindow int64, cost float64) string {
430+ t := styles.CurrentTheme()
431+ // Format tokens in human-readable format (e.g., 110K, 1.2M)
432+ var formattedTokens string
433+ switch {
434+ case tokens >= 1_000_000:
435+ formattedTokens = fmt.Sprintf("%.1fM", float64(tokens)/1_000_000)
436+ case tokens >= 1_000:
437+ formattedTokens = fmt.Sprintf("%.1fK", float64(tokens)/1_000)
438+ default:
439+ formattedTokens = fmt.Sprintf("%d", tokens)
440+ }
441+
442+ // Remove .0 suffix if present
443+ if strings.HasSuffix(formattedTokens, ".0K") {
444+ formattedTokens = strings.Replace(formattedTokens, ".0K", "K", 1)
445+ }
446+ if strings.HasSuffix(formattedTokens, ".0M") {
447+ formattedTokens = strings.Replace(formattedTokens, ".0M", "M", 1)
448+ }
449+
450+ percentage := (float64(tokens) / float64(contextWindow)) * 100
451+
452+ baseStyle := t.S().Base
453+
454+ formattedCost := baseStyle.Foreground(t.FgMuted).Render(fmt.Sprintf("$%.2f", cost))
455+
456+ formattedTokens = baseStyle.Foreground(t.FgSubtle).Render(fmt.Sprintf("(%s)", formattedTokens))
457+ formattedPercentage := baseStyle.Foreground(t.FgMuted).Render(fmt.Sprintf("%d%%", int(percentage)))
458+ formattedTokens = fmt.Sprintf("%s %s", formattedPercentage, formattedTokens)
459+ if percentage > 80 {
460+ // add the warning icon
461+ formattedTokens = fmt.Sprintf("%s %s", styles.WarningIcon, formattedTokens)
462+ }
463+
464+ return fmt.Sprintf("%s %s", formattedTokens, formattedCost)
465+}
466+
467+func (s *sidebarCmp) currentModelBlock() string {
468+ cfg := config.Get()
469+ agentCfg := cfg.Agents[config.AgentCoder]
470+ selectedModelID := agentCfg.Model
471+ model := models.SupportedModels[selectedModelID]
472+
473+ t := styles.CurrentTheme()
474+
475+ modelIcon := t.S().Base.Foreground(t.FgSubtle).Render(styles.ModelIcon)
476+ modelName := t.S().Text.Render(model.Name)
477+ modelInfo := fmt.Sprintf("%s %s", modelIcon, modelName)
478+ parts := []string{
479+ modelInfo,
480+ }
481+ if s.session.ID != "" {
482+ parts = append(
483+ parts,
484+ " "+formatTokensAndCost(
485+ s.session.CompletionTokens+s.session.PromptTokens,
486+ model.ContextWindow,
487+ s.session.Cost,
488+ ),
489+ )
490+ }
491+ return lipgloss.JoinVertical(
492+ lipgloss.Left,
493+ parts...,
494+ )
495+}
496+
198497func cwd() string {
199498 cwd := config.WorkingDirectory()
200499 t := styles.CurrentTheme()
internal/tui/components/core/helpers.go+31−7
@@ -4,6 +4,8 @@ import (
44 "image/color"
55 "strings"
66
7+ "github.com/alecthomas/chroma/v2"
8+ "github.com/charmbracelet/crush/internal/exp/diffview"
79 "github.com/charmbracelet/crush/internal/tui/styles"
810 "github.com/charmbracelet/lipgloss/v2"
911 "github.com/charmbracelet/x/ansi"
@@ -38,10 +40,12 @@ func Title(title string, width int) string {
3840type StatusOpts struct {
3941 Icon string
4042 IconColor color.Color
43+ NoIcon bool // If true, no icon will be displayed
4144 Title string
4245 TitleColor color.Color
4346 Description string
4447 DescriptionColor color.Color
48+ ExtraContent string // Additional content to append after the description
4549}
4650
4751func Status(ops StatusOpts, width int) string {
@@ -50,6 +54,8 @@ func Status(ops StatusOpts, width int) string {
5054 iconColor := t.Success
5155 if ops.Icon != "" {
5256 icon = ops.Icon
57+ } else if ops.NoIcon {
58+ icon = ""
5359 }
5460 if ops.IconColor != nil {
5561 iconColor = ops.IconColor
@@ -64,17 +70,26 @@ func Status(ops StatusOpts, width int) string {
6470 if ops.DescriptionColor != nil {
6571 descriptionColor = ops.DescriptionColor
6672 }
67− icon = t.S().Base.Foreground(iconColor).Render(icon)
6873 title = t.S().Base.Foreground(titleColor).Render(title)
6974 if description != "" {
70− description = ansi.Truncate(description, width-lipgloss.Width(icon)-lipgloss.Width(title)-2, "…")
75+ extraContent := len(ops.ExtraContent)
76+ if extraContent > 0 {
77+ extraContent += 1
78+ }
79+ description = ansi.Truncate(description, width-lipgloss.Width(icon)-lipgloss.Width(title)-2-extraContent, "…")
7180 }
7281 description = t.S().Base.Foreground(descriptionColor).Render(description)
73− return strings.Join([]string{
74− icon,
75− title,
76− description,
77− }, " ")
82+
83+ content := []string{}
84+ if icon != "" {
85+ content = append(content, t.S().Base.Foreground(iconColor).Render(icon))
86+ }
87+ content = append(content, title, description)
88+ if ops.ExtraContent != "" {
89+ content = append(content, ops.ExtraContent)
90+ }
91+
92+ return strings.Join(content, " ")
7893}
7994
8095type ButtonOpts struct {
@@ -131,3 +146,12 @@ func SelectableButtons(buttons []ButtonOpts, spacing string) string {
131146
132147 return lipgloss.JoinHorizontal(lipgloss.Left, parts...)
133148}
149+
150+func DiffFormatter() *diffview.DiffView {
151+ formatDiff := diffview.New()
152+ style := chroma.MustNewStyle("crush", styles.GetChromaTheme())
153+ diff := formatDiff.
154+ SyntaxHightlight(true).
155+ ChromaStyle(style)
156+ return diff
157+}
internal/tui/components/dialogs/commands/commands.go+2−1
@@ -239,7 +239,8 @@ func (c *commandDialogCmp) defaultCommands() []Command {
239239
240240 The file you create will be given to agentic coding agents (such as yourself) that operate in this repository. Make it about 20 lines long.
241241 If there's already a CRUSH.md, improve it.
242− If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.`
242+ If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.
243+ Add the .crush directory to the .gitignore file if it's not already there.`
243244 return util.CmdHandler(chat.SendMsg{
244245 Text: prompt,
245246 })
internal/tui/components/dialogs/init/init.go+2−1
@@ -180,7 +180,8 @@ func (m *initDialogCmp) handleInitialization(initialize bool) tea.Cmd {
180180
181181The file you create will be given to agentic coding agents (such as yourself) that operate in this repository. Make it about 20 lines long.
182182If there's already a CRUSH.md, improve it.
183−If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.`
183+If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.
184+Add the .crush directory to the .gitignore file if it's not already there.`
184185
185186 // Mark the project as initialized
186187 if err := config.MarkProjectInitialized(); err != nil {
internal/tui/components/dialogs/permissions/permissions.go+10−23
@@ -8,7 +8,7 @@ import (
88 "github.com/charmbracelet/bubbles/v2/viewport"
99 tea "github.com/charmbracelet/bubbletea/v2"
1010 "github.com/charmbracelet/crush/internal/diff"
11− "github.com/charmbracelet/crush/internal/fileutil"
11+ "github.com/charmbracelet/crush/internal/fsext"
1212 "github.com/charmbracelet/crush/internal/llm/tools"
1313 "github.com/charmbracelet/crush/internal/permission"
1414 "github.com/charmbracelet/crush/internal/tui/components/core"
@@ -170,7 +170,7 @@ func (p *permissionDialogCmp) renderHeader() string {
170170 pathKey := t.S().Muted.Render("Path")
171171 pathValue := t.S().Text.
172172 Width(p.width - lipgloss.Width(pathKey)).
173− Render(fmt.Sprintf(" %s", fileutil.PrettyPath(p.permission.Path)))
173+ Render(fmt.Sprintf(" %s", fsext.PrettyPath(p.permission.Path)))
174174
175175 headerParts := []string{
176176 lipgloss.JoinHorizontal(
@@ -196,7 +196,7 @@ func (p *permissionDialogCmp) renderHeader() string {
196196 fileKey := t.S().Muted.Render("File")
197197 filePath := t.S().Text.
198198 Width(p.width - lipgloss.Width(fileKey)).
199− Render(fmt.Sprintf(" %s", fileutil.PrettyPath(params.FilePath)))
199+ Render(fmt.Sprintf(" %s", fsext.PrettyPath(params.FilePath)))
200200 headerParts = append(headerParts,
201201 lipgloss.JoinHorizontal(
202202 lipgloss.Left,
@@ -211,7 +211,7 @@ func (p *permissionDialogCmp) renderHeader() string {
211211 fileKey := t.S().Muted.Render("File")
212212 filePath := t.S().Text.
213213 Width(p.width - lipgloss.Width(fileKey)).
214− Render(fmt.Sprintf(" %s", fileutil.PrettyPath(params.FilePath)))
214+ Render(fmt.Sprintf(" %s", fsext.PrettyPath(params.FilePath)))
215215 headerParts = append(headerParts,
216216 lipgloss.JoinHorizontal(
217217 lipgloss.Left,
@@ -267,24 +267,13 @@ func (p *permissionDialogCmp) renderBashContent() string {
267267
268268func (p *permissionDialogCmp) renderEditContent() string {
269269 if pr, ok := p.permission.Params.(tools.EditPermissionsParams); ok {
270− diff := p.GetOrSetDiff(p.permission.ID, func() (string, error) {
271− return diff.FormatDiff(pr.Diff, diff.WithTotalWidth(p.contentViewPort.Width()))
272− })
273−
274− contentHeight := min(p.height-9, lipgloss.Height(diff))
275− p.contentViewPort.SetHeight(contentHeight)
276− p.contentViewPort.SetContent(diff)
277− return p.styleViewport()
278− }
279− return ""
280−}
281−
282−func (p *permissionDialogCmp) renderPatchContent() string {
283− if pr, ok := p.permission.Params.(tools.EditPermissionsParams); ok {
284− diff := p.GetOrSetDiff(p.permission.ID, func() (string, error) {
285− return diff.FormatDiff(pr.Diff, diff.WithTotalWidth(p.contentViewPort.Width()))
286− })
270+ formatter := core.DiffFormatter().
271+ Before(fsext.PrettyPath(pr.FilePath), pr.OldContent).
272+ After(fsext.PrettyPath(pr.FilePath), pr.NewContent).
273+ Width(p.contentViewPort.Width()).
274+ Split()
287275
276+ diff := formatter.String()
288277 contentHeight := min(p.height-9, lipgloss.Height(diff))
289278 p.contentViewPort.SetHeight(contentHeight)
290279 p.contentViewPort.SetContent(diff)
@@ -381,8 +370,6 @@ func (p *permissionDialogCmp) render() string {
381370 contentFinal = p.renderBashContent()
382371 case tools.EditToolName:
383372 contentFinal = p.renderEditContent()
384− case tools.PatchToolName:
385− contentFinal = p.renderPatchContent()
386373 case tools.WriteToolName:
387374 contentFinal = p.renderWriteContent()
388375 case tools.FetchToolName:
internal/tui/page/chat/chat.go+1−1
@@ -209,7 +209,7 @@ func (p *chatPage) Bindings() []key.Binding {
209209
210210func NewChatPage(app *app.App) ChatPage {
211211 sidebarContainer := layout.NewContainer(
212− sidebar.NewSidebarCmp(),
212+ sidebar.NewSidebarCmp(app.History, app.LSPClients),
213213 layout.WithPadding(1, 1, 1, 1),
214214 )
215215 editorContainer := layout.NewContainer(
internal/tui/styles/crush.go+1−1
@@ -32,7 +32,7 @@ func NewCrushTheme() *Theme {
3232 // Status
3333 Success: charmtone.Guac,
3434 Error: charmtone.Sriracha,
35− Warning: charmtone.Uni,
35+ Warning: charmtone.Zest,
3636 Info: charmtone.Malibu,
3737
3838 // Colors
internal/tui/styles/icons.go+3−2
@@ -4,11 +4,12 @@ const (
44 CheckIcon string = "✓"
55 ErrorIcon string = "×"
66 WarningIcon string = "⚠"
7− InfoIcon string = ""
8− HintIcon string = "i"
7+ InfoIcon string = "ⓘ"
8+ HintIcon string = "∵"
99 SpinnerIcon string = "..."
1010 LoadingIcon string = "⟳"
1111 DocumentIcon string = "🖼"
12+ ModelIcon string = "⬡"
1213
1314 // Tool call icons
1415 ToolPending string = "●"
internal/version/version.go+1−1
@@ -5,7 +5,7 @@ import "runtime/debug"
55// Build-time parameters set via -ldflags
66var Version = "unknown"
77
8−// A user may install pug using `go install github.com/charmbracelet/crush@latest`.
8+// A user may install crush using `go install github.com/charmbracelet/crush@latest`.
99// without -ldflags, in which case the version above is unset. As a workaround
1010// we use the embedded build version that *is* set when using `go install` (and
1111// is only set for `go install` and not for `go build`).
todos.md+8−2
@@ -3,8 +3,11 @@
33- [x] Implement help
44 - [x] Show full help
55 - [x] Make help dependent on the focused pane and page
6−- [ ] Implement current model in the sidebar
7−- [ ] Implement changed files
6+- [x] Implement current model in the sidebar
7+- [x] Implement LSP errors
8+- [x] Implement changed files
9+ - [x] Implement initial load
10+ - [x] Implement realtime file changes
811- [ ] Events when tool error
912- [ ] Support bash commands
1013- [ ] Editor attachments fixes
@@ -23,8 +26,10 @@
2326 - [ ] Address UX issues
2427 - [ ] Fix issue with numbers (padding) view tool
2528- [ ] Implement responsive mode
29+- [ ] Update interactive mode to use the spinner
2630- [ ] Revisit the core list component
2731 - [ ] This component has become super complex we might need to fix this.
32+- [ ] Handle correct LSP and MCP status icon
2833- [x] Investigate ways to make the spinner less CPU intensive
2934- [ ] General cleanup and documentation
3035- [ ] Update the readme
@@ -34,3 +39,4 @@
3439- [ ] Revisit the provider/model/configs
3540- [ ] Implement correct persistent shell
3641- [ ] Store file read/write time somewhere so that the we can make sure that even if we restart we do not need to re-read the same file
42+- [ ] Send updates to the UI when new LSP diagnostics are available