on a08e332 help sign in
7795 files changed+2916−5603248
since splitsnapshots
unifiedsplit
.github/cla-signatures.json−32 .github/workflows/security.yml+5−5 .github/workflows/snapshot.yml+1−1 .gitignore−1 .golangci.yml+4−12 README.md−5 flake.lock+4−3 flake.nix+1−32 go.mod+11−11 go.sum+22−22 internal/agent/agent.go+32−71 internal/agent/agent_test.go−143 internal/agent/coordinator.go+12−185 internal/agent/hyper/provider.go+1−1 internal/agent/hyper/provider.json+25−25 internal/agent/loop_detection.go+17−17 internal/agent/loop_detection_test.go+8−8 internal/agent/prompt/prompt.go+1−1 internal/agent/tools/bash.go+16−4 internal/agent/tools/download.go+3−3 internal/agent/tools/edit.go+1−4 internal/agent/tools/fetch.go+12−12 internal/agent/tools/grep.go+4−11 internal/agent/tools/ls.go+1−1 internal/agent/tools/mcp-tools.go+2−2 internal/agent/tools/mcp/init.go+3−22 internal/agent/tools/mcp/tools.go−14 internal/agent/tools/multiedit.go+1 internal/agent/tools/references.go+8 internal/agent/tools/safe.go+22−95 internal/agent/tools/safe_test.go+24−51 internal/agent/tools/sourcegraph.go+4−4 internal/agent/tools/view.go+24−25 internal/agent/tools/view_test.go+2−2 internal/agent/tools/web_fetch.go+4−4 internal/agent/tools/web_search.go+1−1 internal/agent/tools/write.go+14−15 internal/agent/usage_fallback.go+9 internal/app/app.go+6−77 internal/backend/agent.go+10−1 internal/backend/backend.go−18 internal/backend/config_test.go+9−7 internal/backend/shell_command_test.go−54 internal/client/proto.go+4−2 internal/cmd/logs.go+3−3 internal/cmd/root.go+26−1 internal/cmd/run.go−11 internal/cmd/session.go+1−1 internal/config/config.go+2−13 internal/config/hyper.go+1−1 internal/config/load.go+1−17 internal/config/resolve.go−11 internal/config/store.go+18−41 internal/csync/maps.go+5−20 internal/db/db.go+4−24 internal/db/files.sql.go+5−21 internal/db/messages.sql.go+2−50 internal/db/migrations/20260701000000_add_composite_indexes.sql−12 internal/db/migrations/20260702000000_add_hot_path_indexes.sql−14 internal/db/models.go+1−1 internal/db/querier.go+3−5 internal/db/read_files.sql.go+1−1 internal/db/sessions.sql.go+9−39 internal/db/sql/files.sql+4−12 internal/db/sql/messages.sql−7 internal/db/sql/sessions.sql+4−6 internal/db/stats.sql.go+1−1 internal/event/event.go+2−15 internal/filetracker/service.go+2−19 internal/fsext/fileutil.go+22−29 internal/fsext/fileutil_dirtrim_test.go−32 internal/fsext/lookup.go+7−2 internal/fsext/owner_others.go+5−8 internal/fsext/owner_windows.go−7 internal/herdr/client.go−405 internal/herdr/client_test.go−158 internal/herdr/translate.go−150 internal/herdr/translate_test.go−145 internal/history/file.go+15−20 internal/lsp/client.go−5 internal/lsp/handlers.go+6−6 internal/lsp/manager.go+8−28 internal/lsp/util/edit.go+34−41 internal/message/content.go+23−69 internal/message/message.go+56−102 internal/oauth/copilot/client.go+7−22 internal/oauth/copilot/client_test.go−70 internal/permission/permission.go+47−55 internal/pubsub/broker.go+27−69 internal/pubsub/broker_test.go−117 internal/server/proto.go+3−13 internal/session/session.go+18−13 internal/shell/background.go+1−1 internal/shell/persist_message.go−44 internal/shell/persist_message_test.go−69 internal/shell/run.go−30 internal/shell/run_test.go−43 internal/shell/shell.go−5 internal/skills/embed.go+1−1 internal/skills/skills.go+1−30 internal/ui/attachments/attachments.go+2−2 internal/ui/chat/agent.go+26−61 internal/ui/chat/assistant.go+85−313 internal/ui/chat/assistant_render_test.go−78 internal/ui/chat/assistant_section_cache_test.go+3−3 internal/ui/chat/assistant_thinking_bench_test.go−69 internal/ui/chat/assistant_thinking_bound_test.go−105 internal/ui/chat/bash.go+7−10 internal/ui/chat/diagnostics.go+2−2 internal/ui/chat/docker_mcp.go+6−6 internal/ui/chat/fetch.go+10−10 internal/ui/chat/file.go+22−22 internal/ui/chat/generic.go+4−4 internal/ui/chat/incremental_glamour_test.go+16−144 internal/ui/chat/lsp_restart.go+2−2 internal/ui/chat/mcp.go+5−5 internal/ui/chat/messages.go+1−2 internal/ui/chat/references.go+2−2 internal/ui/chat/search.go+12−12 internal/ui/chat/shell.go+32−85 internal/ui/chat/streaming_markdown.go+251−232 internal/ui/chat/todos.go+2−2 internal/ui/chat/tool_result_content.go+14−15 internal/ui/chat/tool_result_content_test.go+5−5 internal/ui/chat/tools.go+64−152 internal/ui/chat/unified_diff.go+14−13 internal/ui/chat/version_bump_test.go+10−10 internal/ui/common/capabilities.go+12 internal/ui/common/diff.go+1−20 internal/ui/common/elements.go−3 internal/ui/common/highlight.go+11−60 internal/ui/common/scrollbar.go+2−4 internal/ui/completions/completions.go+4−6 internal/ui/dialog/actions.go−3 internal/ui/dialog/api_key_input.go+7−54 internal/ui/dialog/arguments.go+11−20 internal/ui/dialog/commands.go+163−48 internal/ui/dialog/commands_item.go+6−6 internal/ui/dialog/dialog.go+1−1 internal/ui/dialog/filepicker.go+2−11 internal/ui/dialog/models.go−2 internal/ui/dialog/models_list.go+56−52 internal/ui/dialog/oauth.go+2 internal/ui/dialog/permissions.go+21−84 internal/ui/dialog/permissions_test.go+6−7 internal/ui/dialog/sessions.go+11−16 internal/ui/dialog/sessions_item.go+2−12 internal/ui/difftastic/difftastic.go−60 internal/ui/diffview/diffview.go+2−2 internal/ui/diffview/diffview_test.go+8 internal/ui/diffview/style.go+44−46 internal/ui/diffview/testdata/TestDiffView/Split/CustomContextLines/DarkMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Split/CustomContextLines/LightMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Split/Default/DarkMode.golden+2−2 internal/ui/diffview/testdata/TestDiffView/Split/Default/LightMode.golden+2−2 internal/ui/diffview/testdata/TestDiffView/Split/LargeWidth/DarkMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Split/LargeWidth/LightMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Split/MultipleHunks/DarkMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Split/MultipleHunks/LightMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Split/Narrow/DarkMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Split/Narrow/LightMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Split/NoLineNumbers/DarkMode.golden+2−2 internal/ui/diffview/testdata/TestDiffView/Split/NoLineNumbers/LightMode.golden+2−2 internal/ui/diffview/testdata/TestDiffView/Split/NoSyntaxHighlight/DarkMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Split/NoSyntaxHighlight/LightMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Split/SmallWidth/DarkMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Split/SmallWidth/LightMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Unified/CustomContextLines/DarkMode.golden+4−4 internal/ui/diffview/testdata/TestDiffView/Unified/CustomContextLines/LightMode.golden+4−4 internal/ui/diffview/testdata/TestDiffView/Unified/Default/DarkMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Unified/Default/LightMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Unified/LargeWidth/DarkMode.golden+4−4 internal/ui/diffview/testdata/TestDiffView/Unified/LargeWidth/LightMode.golden+4−4 internal/ui/diffview/testdata/TestDiffView/Unified/MultipleHunks/DarkMode.golden+4−4 internal/ui/diffview/testdata/TestDiffView/Unified/MultipleHunks/LightMode.golden+4−4 internal/ui/diffview/testdata/TestDiffView/Unified/Narrow/DarkMode.golden+6−6 internal/ui/diffview/testdata/TestDiffView/Unified/Narrow/LightMode.golden+6−6 internal/ui/diffview/testdata/TestDiffView/Unified/NoLineNumbers/DarkMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Unified/NoLineNumbers/LightMode.golden+3−3 internal/ui/diffview/testdata/TestDiffView/Unified/NoSyntaxHighlight/DarkMode.golden+4−4 internal/ui/diffview/testdata/TestDiffView/Unified/NoSyntaxHighlight/LightMode.golden+4−4 internal/ui/diffview/testdata/TestDiffView/Unified/SmallWidth/DarkMode.golden+4−4 internal/ui/diffview/testdata/TestDiffView/Unified/SmallWidth/LightMode.golden+4−4 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf005.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf006.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf007.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf008.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf009.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf010.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf011.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf012.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf013.golden+2−2 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf014.golden+3−3 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf015.golden+3−3 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf016.golden+3−3 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf017.golden+3−3 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf018.golden+3−3 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf019.golden+3−3 internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf020.golden+3−3 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf005.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf006.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf007.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf008.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf009.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf010.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf011.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf012.golden+1−1 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf013.golden+2−2 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf014.golden+3−3 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf015.golden+4−4 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf016.golden+4−4 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf017.golden+4−4 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf018.golden+4−4 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf019.golden+4−4 internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf020.golden+4−4 internal/ui/diffview/testdata/TestDiffViewLineBreakIssue/Split.golden+5−5 internal/ui/diffview/testdata/TestDiffViewLineBreakIssue/Unified.golden+8−8 internal/ui/diffview/testdata/TestDiffViewTabs/Split.golden+3−3 internal/ui/diffview/testdata/TestDiffViewTabs/Unified.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf001.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf002.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf003.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf004.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf005.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf006.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf007.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf008.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf009.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf010.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf011.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf012.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf013.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf014.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf015.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf016.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf017.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf018.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf019.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf020.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf021.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf022.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf023.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf024.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf025.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf026.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf027.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf028.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf029.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf030.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf031.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf032.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf033.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf034.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf035.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf036.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf037.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf038.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf039.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf040.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf041.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf042.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf043.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf044.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf045.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf046.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf047.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf048.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf049.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf050.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf051.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf052.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf053.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf054.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf055.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf056.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf057.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf058.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf059.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf060.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf061.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf062.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf063.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf064.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf065.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf066.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf067.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf068.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf069.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf070.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf071.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf072.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf073.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf074.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf075.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf076.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf077.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf078.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf079.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf080.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf081.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf082.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf083.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf084.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf085.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf086.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf087.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf088.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf089.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf090.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf091.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf092.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf093.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf094.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf095.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf096.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf097.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf098.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf099.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf100.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf101.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf102.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf103.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf104.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf105.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf106.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf107.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf108.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf109.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf110.golden+3−3 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf001.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf002.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf003.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf004.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf005.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf006.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf007.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf008.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf009.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf010.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf011.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf012.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf013.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf014.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf015.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf016.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf017.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf018.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf019.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf020.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf021.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf022.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf023.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf024.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf025.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf026.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf027.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf028.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf029.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf030.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf031.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf032.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf033.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf034.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf035.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf036.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf037.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf038.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf039.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf040.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf041.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf042.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf043.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf044.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf045.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf046.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf047.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf048.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf049.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf050.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf051.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf052.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf053.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf054.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf055.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf056.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf057.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf058.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf059.golden+4−4 internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf060.golden+4−4 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf00.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf01.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf02.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf03.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf04.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf05.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf06.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf07.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf08.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf09.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf10.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf11.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf12.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf13.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf14.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf15.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf16.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf17.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf18.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf19.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf20.golden+2−2 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf00.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf01.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf02.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf03.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf04.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf05.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf06.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf07.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf08.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf09.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf10.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf11.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf12.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf13.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf14.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf15.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf16.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf17.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf18.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf19.golden+3−3 internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf20.golden+3−3 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf00.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf01.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf02.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf03.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf04.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf08.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf09.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf10.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf11.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf12.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf13.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf14.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf15.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf16.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf00.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf01.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf02.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf03.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf04.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf08.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf09.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf10.golden+3−3 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf11.golden+3−3 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf12.golden+3−3 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf13.golden+3−3 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf14.golden+3−3 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf15.golden+3−3 internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf16.golden+3−3 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf00.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf01.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf02.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf03.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf04.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf08.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf09.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf10.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf11.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf12.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf13.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf00.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf01.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf02.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf03.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf04.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf08.golden+1−1 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf09.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf10.golden+3−3 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf11.golden+3−3 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf12.golden+3−3 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf13.golden+2−2 internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf14.golden+1−1 internal/ui/diffview/util.go+1−8 internal/ui/list/filterable.go+1−33 internal/ui/list/highlight.go+1−2 internal/ui/list/list.go+4−23 internal/ui/model/chat.go+33−150 internal/ui/model/header.go+10−93 internal/ui/model/history.go+2−1 internal/ui/model/keys.go+21−25 internal/ui/model/landing.go+9−78 internal/ui/model/layout_test.go+1−2 internal/ui/model/lsp.go+2−23 internal/ui/model/mcp.go+2−9 internal/ui/model/onboarding.go+2−1 internal/ui/model/pills.go+4−49 internal/ui/model/session.go+6−5 internal/ui/model/sidebar.go+21−175 internal/ui/model/skills.go+5−15 internal/ui/model/ui.go+89−316 internal/ui/styles/grad.go+2−51 internal/ui/styles/quickstyle.go+21−7 internal/ui/styles/styles.go+3−4 internal/ui/styles/themes.go+12−131 internal/ui/xchroma/chroma.go+19−37 internal/workspace/app_workspace.go+12−6 internal/workspace/client_workspace.go+3−16 schema.json−20 vendor/charm.land/bubbles/v2/LICENSE−21 vendor/charm.land/bubbles/v2/cursor/cursor.go−241 vendor/charm.land/bubbles/v2/filepicker/filepicker.go−529 vendor/charm.land/bubbles/v2/filepicker/hidden_unix.go−11 vendor/charm.land/bubbles/v2/filepicker/hidden_windows.go−21 vendor/charm.land/bubbles/v2/help/help.go−253 vendor/charm.land/bubbles/v2/internal/memoization/memoization.go−125 vendor/charm.land/bubbles/v2/internal/runeutil/runeutil.go−102 vendor/charm.land/bubbles/v2/key/key.go−140 vendor/charm.land/bubbles/v2/spinner/spinner.go−211 vendor/charm.land/bubbles/v2/textarea/textarea.go−1897 vendor/charm.land/bubbles/v2/textinput/styles.go−96 vendor/charm.land/bubbles/v2/textinput/textinput.go−968 vendor/charm.land/bubbles/v2/viewport/highlight.go−141 vendor/charm.land/bubbles/v2/viewport/keymap.go−58 vendor/charm.land/bubbles/v2/viewport/viewport.go−765 vendor/charm.land/bubbletea/v2/.gitattributes−1 vendor/charm.land/bubbletea/v2/.gitignore−23 vendor/charm.land/bubbletea/v2/.golangci.yml−47 vendor/charm.land/bubbletea/v2/.goreleaser.yml−5 vendor/charm.land/bubbletea/v2/LICENSE−21 vendor/charm.land/bubbletea/v2/README.md−402 vendor/charm.land/bubbletea/v2/Taskfile.yaml−38 vendor/charm.land/bubbletea/v2/UPGRADE_GUIDE_V2.md−573 vendor/charm.land/bubbletea/v2/clipboard.go−70 vendor/charm.land/bubbletea/v2/color.go−91 vendor/charm.land/bubbletea/v2/commands.go−175 vendor/charm.land/bubbletea/v2/cursed_renderer.go−860 vendor/charm.land/bubbletea/v2/cursor.go−28 vendor/charm.land/bubbletea/v2/environ.go−34 vendor/charm.land/bubbletea/v2/exec.go−129 vendor/charm.land/bubbletea/v2/focus.go−9 vendor/charm.land/bubbletea/v2/input.go−54 vendor/charm.land/bubbletea/v2/key.go−371 vendor/charm.land/bubbletea/v2/keyboard.go−59 vendor/charm.land/bubbletea/v2/logging.go−53 vendor/charm.land/bubbletea/v2/mod.go−27 vendor/charm.land/bubbletea/v2/mouse.go−144 vendor/charm.land/bubbletea/v2/nil_renderer.go−53 vendor/charm.land/bubbletea/v2/options.go−168 vendor/charm.land/bubbletea/v2/paste.go−20 vendor/charm.land/bubbletea/v2/profile.go−15 vendor/charm.land/bubbletea/v2/raw.go−37 vendor/charm.land/bubbletea/v2/renderer.go−104 vendor/charm.land/bubbletea/v2/screen.go−68 vendor/charm.land/bubbletea/v2/signals_unix.go−33 vendor/charm.land/bubbletea/v2/signals_windows.go−10 vendor/charm.land/bubbletea/v2/tea.go−1439 vendor/charm.land/bubbletea/v2/termcap.go−48 vendor/charm.land/bubbletea/v2/termios_bsd.go−13 vendor/charm.land/bubbletea/v2/termios_other.go−8 vendor/charm.land/bubbletea/v2/termios_unix.go−14 vendor/charm.land/bubbletea/v2/termios_windows.go−11 vendor/charm.land/bubbletea/v2/tty.go−136 vendor/charm.land/bubbletea/v2/tty_unix.go−47 vendor/charm.land/bubbletea/v2/tty_windows.go−64 vendor/charm.land/bubbletea/v2/xterm.go−22 vendor/charm.land/catwalk/LICENSE−21 vendor/charm.land/catwalk/internal/providers/configs/aihubmix.json−3746 vendor/charm.land/catwalk/internal/providers/configs/alibaba-singapore.json−141 vendor/charm.land/catwalk/internal/providers/configs/alibaba-united-states.json−45 vendor/charm.land/catwalk/internal/providers/configs/anthropic.json−183 vendor/charm.land/catwalk/internal/providers/configs/avian.json−125 vendor/charm.land/catwalk/internal/providers/configs/azure.json−225 vendor/charm.land/catwalk/internal/providers/configs/baseten.json−187 vendor/charm.land/catwalk/internal/providers/configs/bedrock-europe.json−115 vendor/charm.land/catwalk/internal/providers/configs/bedrock-united-states.json−143 vendor/charm.land/catwalk/internal/providers/configs/cerebras.json−42 vendor/charm.land/catwalk/internal/providers/configs/chutes.json−227 vendor/charm.land/catwalk/internal/providers/configs/copilot.json−310 vendor/charm.land/catwalk/internal/providers/configs/cortecs.json−1697 vendor/charm.land/catwalk/internal/providers/configs/deepseek.json−45 vendor/charm.land/catwalk/internal/providers/configs/fireworks.json−171 vendor/charm.land/catwalk/internal/providers/configs/gemini.json−91 vendor/charm.land/catwalk/internal/providers/configs/groq.json−36 vendor/charm.land/catwalk/internal/providers/configs/huggingface.json−267 vendor/charm.land/catwalk/internal/providers/configs/ionet.json−335 vendor/charm.land/catwalk/internal/providers/configs/kimi.json−23 vendor/charm.land/catwalk/internal/providers/configs/minimax-china.json−91 vendor/charm.land/catwalk/internal/providers/configs/minimax.json−91 vendor/charm.land/catwalk/internal/providers/configs/moonshot.json−131 vendor/charm.land/catwalk/internal/providers/configs/nebius.json−419 vendor/charm.land/catwalk/internal/providers/configs/neuralwatt.json−155 vendor/charm.land/catwalk/internal/providers/configs/openai.json−352 vendor/charm.land/catwalk/internal/providers/configs/opencode-go.json−351 vendor/charm.land/catwalk/internal/providers/configs/opencode-zen.json−908 vendor/charm.land/catwalk/internal/providers/configs/openrouter.json−3831 vendor/charm.land/catwalk/internal/providers/configs/qiniucloud.json−179 vendor/charm.land/catwalk/internal/providers/configs/scaleway.json−141 vendor/charm.land/catwalk/internal/providers/configs/synthetic.json−197 vendor/charm.land/catwalk/internal/providers/configs/venice.json−1173 vendor/charm.land/catwalk/internal/providers/configs/vercel.json−3072 vendor/charm.land/catwalk/internal/providers/configs/vertexai.json−170 vendor/charm.land/catwalk/internal/providers/configs/xai.json−59 vendor/charm.land/catwalk/internal/providers/configs/zai.json−136 vendor/charm.land/catwalk/internal/providers/configs/zhipu-coding.json−136 vendor/charm.land/catwalk/internal/providers/configs/zhipu.json−120 vendor/charm.land/catwalk/internal/providers/providers.go−349 vendor/charm.land/catwalk/pkg/catwalk/client.go−79 vendor/charm.land/catwalk/pkg/catwalk/pkg.go−2 vendor/charm.land/catwalk/pkg/catwalk/provider.go−157 vendor/charm.land/catwalk/pkg/embedded/embedded.go−13 vendor/charm.land/fang/v2/.gitignore−3 vendor/charm.land/fang/v2/.golangci.yml−47 vendor/charm.land/fang/v2/.goreleaser.yml−5 vendor/charm.land/fang/v2/LICENSE.md−21 vendor/charm.land/fang/v2/README.md−92 vendor/charm.land/fang/v2/Taskfile.yml−7 vendor/charm.land/fang/v2/UPGRADE_GUIDE_V2.md−127 vendor/charm.land/fang/v2/fang.go−208 vendor/charm.land/fang/v2/fang_other.go−10 vendor/charm.land/fang/v2/fang_windows.go−30 vendor/charm.land/fang/v2/help.go−482 vendor/charm.land/fang/v2/theme.go−222 vendor/charm.land/fantasy/.editorconfig−18 vendor/charm.land/fantasy/.gitattributes−1 vendor/charm.land/fantasy/.gitignore−42 vendor/charm.land/fantasy/.golangci.yml−44 vendor/charm.land/fantasy/.goreleaser.yml−6 vendor/charm.land/fantasy/AGENTS.md−29 vendor/charm.land/fantasy/LICENSE−177 vendor/charm.land/fantasy/NOTICE−13 vendor/charm.land/fantasy/README.md−95 vendor/charm.land/fantasy/Taskfile.yaml−45 vendor/charm.land/fantasy/agent.go−1702 vendor/charm.land/fantasy/content.go−625 vendor/charm.land/fantasy/content_json.go−1046 vendor/charm.land/fantasy/crush.json−6 vendor/charm.land/fantasy/cspell.json−1 vendor/charm.land/fantasy/doc.go−2 vendor/charm.land/fantasy/errors.go−155 vendor/charm.land/fantasy/jsonrepair/jsonrepair.go−1648 vendor/charm.land/fantasy/model.go−261 vendor/charm.land/fantasy/model_json.go−156 vendor/charm.land/fantasy/object.go−236 vendor/charm.land/fantasy/object/object.go−620 vendor/charm.land/fantasy/provider.go−11 vendor/charm.land/fantasy/provider_registry.go−106 vendor/charm.land/fantasy/providers/anthropic/anthropic.go−1707 vendor/charm.land/fantasy/providers/anthropic/bedrock.go−15 vendor/charm.land/fantasy/providers/anthropic/call_useragent.go−14 vendor/charm.land/fantasy/providers/anthropic/computer_use.go−427 vendor/charm.land/fantasy/providers/anthropic/error.go−67 vendor/charm.land/fantasy/providers/anthropic/google.go−11 vendor/charm.land/fantasy/providers/anthropic/provider_options.go−283 vendor/charm.land/fantasy/providers/anthropic/sanitize.go−33 vendor/charm.land/fantasy/providers/azure/README.md−6 vendor/charm.land/fantasy/providers/azure/azure.go−125 vendor/charm.land/fantasy/providers/bedrock/README.md−10 vendor/charm.land/fantasy/providers/bedrock/bedrock.go−87 vendor/charm.land/fantasy/providers/google/README.md−63 vendor/charm.land/fantasy/providers/google/auth.go−15 vendor/charm.land/fantasy/providers/google/call_useragent.go−53 vendor/charm.land/fantasy/providers/google/error.go−51 vendor/charm.land/fantasy/providers/google/google.go−1488 vendor/charm.land/fantasy/providers/google/provider_options.go−146 vendor/charm.land/fantasy/providers/google/slice.go−11 vendor/charm.land/fantasy/providers/internal/httpheaders/httpheaders.go−60 vendor/charm.land/fantasy/providers/openai/call_useragent.go−31 vendor/charm.land/fantasy/providers/openai/error.go−92 vendor/charm.land/fantasy/providers/openai/language_model.go−990 vendor/charm.land/fantasy/providers/openai/language_model_hooks.go−668 vendor/charm.land/fantasy/providers/openai/openai.go−220 vendor/charm.land/fantasy/providers/openai/provider_options.go−217 vendor/charm.land/fantasy/providers/openai/responses_language_model.go−1678 vendor/charm.land/fantasy/providers/openai/responses_options.go−395 vendor/charm.land/fantasy/providers/openaicompat/language_model_hooks.go−516 vendor/charm.land/fantasy/providers/openaicompat/openaicompat.go−140 vendor/charm.land/fantasy/providers/openaicompat/provider_options.go−161 vendor/charm.land/fantasy/providers/openrouter/language_model_hooks.go−1063 vendor/charm.land/fantasy/providers/openrouter/openrouter.go−121 vendor/charm.land/fantasy/providers/openrouter/provider_options.go−218 vendor/charm.land/fantasy/providers/vercel/language_model_hooks.go−1074 vendor/charm.land/fantasy/providers/vercel/provider_options.go−195 vendor/charm.land/fantasy/providers/vercel/vercel.go−122 vendor/charm.land/fantasy/retry.go−155 vendor/charm.land/fantasy/schema/schema.go−442 vendor/charm.land/fantasy/tool.go−176 vendor/charm.land/fantasy/util.go−22 vendor/charm.land/fantasy/version.go−12 vendor/charm.land/fantasy/version.txt−1 vendor/charm.land/glamour/v2/.editorconfig−18 vendor/charm.land/glamour/v2/.gitattributes−2 vendor/charm.land/glamour/v2/.gitignore−2 vendor/charm.land/glamour/v2/.golangci.yml−44 vendor/charm.land/glamour/v2/.goreleaser.yml−6 vendor/charm.land/glamour/v2/CONTRIBUTING.md−23 vendor/charm.land/glamour/v2/LICENSE−21 vendor/charm.land/glamour/v2/README.md−129 vendor/charm.land/glamour/v2/Taskfile.yml−25 vendor/charm.land/glamour/v2/UPGRADE_GUIDE_V2.md−279 vendor/charm.land/glamour/v2/ansi/ansi.go−3 vendor/charm.land/glamour/v2/ansi/baseelement.go−157 vendor/charm.land/glamour/v2/ansi/blockelement.go−66 vendor/charm.land/glamour/v2/ansi/blockstack.go−95 vendor/charm.land/glamour/v2/ansi/codeblock.go−151 vendor/charm.land/glamour/v2/ansi/codespan.go−15 vendor/charm.land/glamour/v2/ansi/context.go−38 vendor/charm.land/glamour/v2/ansi/elements.go−481 vendor/charm.land/glamour/v2/ansi/emphasis.go−47 vendor/charm.land/glamour/v2/ansi/heading.go−80 vendor/charm.land/glamour/v2/ansi/image.go−57 vendor/charm.land/glamour/v2/ansi/link.go−112 vendor/charm.land/glamour/v2/ansi/listitem.go−29 vendor/charm.land/glamour/v2/ansi/margin.go−231 vendor/charm.land/glamour/v2/ansi/paragraph.go−63 vendor/charm.land/glamour/v2/ansi/renderer.go−166 vendor/charm.land/glamour/v2/ansi/style.go−252 vendor/charm.land/glamour/v2/ansi/table.go−206 vendor/charm.land/glamour/v2/ansi/table_links.go−234 vendor/charm.land/glamour/v2/ansi/task.go−27 vendor/charm.land/glamour/v2/ansi/templatehelper.go−86 vendor/charm.land/glamour/v2/examples.sh−19 vendor/charm.land/glamour/v2/gallery.sh−23 vendor/charm.land/glamour/v2/glamour.go−294 vendor/charm.land/glamour/v2/internal/autolink/autolink.go−55 vendor/charm.land/glamour/v2/styles/README.md−565 vendor/charm.land/glamour/v2/styles/ascii.json−87 vendor/charm.land/glamour/v2/styles/dark.json−191 vendor/charm.land/glamour/v2/styles/dracula.go−216 vendor/charm.land/glamour/v2/styles/dracula.json−188 vendor/charm.land/glamour/v2/styles/light.json−190 vendor/charm.land/glamour/v2/styles/notty.json−87 vendor/charm.land/glamour/v2/styles/pink.json−90 vendor/charm.land/glamour/v2/styles/styles.go−691 vendor/charm.land/glamour/v2/styles/tokyo-night.go−209 vendor/charm.land/glamour/v2/styles/tokyo-night.json−182 vendor/charm.land/glamour/v2/styles/tokyo_night.json−182 vendor/charm.land/lipgloss/v2/.editorconfig−18 vendor/charm.land/lipgloss/v2/.gitattributes−1 vendor/charm.land/lipgloss/v2/.gitignore−3 vendor/charm.land/lipgloss/v2/.golangci.yml−50 vendor/charm.land/lipgloss/v2/.goreleaser.yml−5 vendor/charm.land/lipgloss/v2/LICENSE−21 vendor/charm.land/lipgloss/v2/README.md−996 vendor/charm.land/lipgloss/v2/Taskfile.yaml−24 vendor/charm.land/lipgloss/v2/UPGRADE_GUIDE_V2.md−504 vendor/charm.land/lipgloss/v2/align.go−82 vendor/charm.land/lipgloss/v2/ansi_unix.go−8 vendor/charm.land/lipgloss/v2/ansi_windows.go−30 vendor/charm.land/lipgloss/v2/blending.go−196 vendor/charm.land/lipgloss/v2/borders.go−587 vendor/charm.land/lipgloss/v2/canvas.go−88 vendor/charm.land/lipgloss/v2/color.go−359 vendor/charm.land/lipgloss/v2/get.go−657 vendor/charm.land/lipgloss/v2/join.go−175 vendor/charm.land/lipgloss/v2/layer.go−327 vendor/charm.land/lipgloss/v2/lipgloss.go−3 vendor/charm.land/lipgloss/v2/position.go−134 vendor/charm.land/lipgloss/v2/query.go−92 vendor/charm.land/lipgloss/v2/ranges.go−48 vendor/charm.land/lipgloss/v2/runes.go−43 vendor/charm.land/lipgloss/v2/set.go−932 vendor/charm.land/lipgloss/v2/size.go−40 vendor/charm.land/lipgloss/v2/style.go−637 vendor/charm.land/lipgloss/v2/table/resizing.go−489 vendor/charm.land/lipgloss/v2/table/rows.go−130 vendor/charm.land/lipgloss/v2/table/table.go−568 vendor/charm.land/lipgloss/v2/table/util.go−44 vendor/charm.land/lipgloss/v2/terminal.go−124 vendor/charm.land/lipgloss/v2/tree/children.go−100 vendor/charm.land/lipgloss/v2/tree/enumerator.go−74 vendor/charm.land/lipgloss/v2/tree/renderer.go−165 vendor/charm.land/lipgloss/v2/tree/tree.go−407 vendor/charm.land/lipgloss/v2/unset.go−359 vendor/charm.land/lipgloss/v2/whitespace.go−76 vendor/charm.land/lipgloss/v2/wrap.go−113 vendor/charm.land/lipgloss/v2/writer.go−160 vendor/charm.land/log/v2/.gitignore−17 vendor/charm.land/log/v2/.golangci.yml−50 vendor/charm.land/log/v2/.goreleaser.yml−6 vendor/charm.land/log/v2/LICENSE−21 vendor/charm.land/log/v2/README.md−367 vendor/charm.land/log/v2/UPGRADE_GUIDE_V2.md−320 vendor/charm.land/log/v2/context.go−23 vendor/charm.land/log/v2/doc.go−3 vendor/charm.land/log/v2/formatter.go−27 vendor/charm.land/log/v2/json.go−157 vendor/charm.land/log/v2/level.go−65 vendor/charm.land/log/v2/logfmt.go−32 vendor/charm.land/log/v2/logger.go−413 vendor/charm.land/log/v2/logger_121.go−72 vendor/charm.land/log/v2/logger_no121.go−69 vendor/charm.land/log/v2/options.go−61 vendor/charm.land/log/v2/pkg.go−248 vendor/charm.land/log/v2/stdlog.go−67 vendor/charm.land/log/v2/styles.go−83 vendor/charm.land/log/v2/text.go−272 vendor/charm.land/x/vcr/LICENSE−21 vendor/charm.land/x/vcr/doc.go−3 vendor/charm.land/x/vcr/hooks.go−33 vendor/charm.land/x/vcr/marshaler.go−19 vendor/charm.land/x/vcr/matcher.go−70 vendor/charm.land/x/vcr/recorder.go−70 vendor/cloud.google.com/go/LICENSE−202 vendor/cloud.google.com/go/auth/CHANGES.md−483 vendor/cloud.google.com/go/auth/LICENSE−202 vendor/cloud.google.com/go/auth/README.md−40 vendor/cloud.google.com/go/auth/auth.go−618 vendor/cloud.google.com/go/auth/credentials/compute.go−102 vendor/cloud.google.com/go/auth/credentials/detect.go−471 vendor/cloud.google.com/go/auth/credentials/doc.go−45 vendor/cloud.google.com/go/auth/credentials/filetypes.go−329 vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.go−531 vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.go−284 vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.go−431 vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.go−78 vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.go−74 vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.go−30 vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.go−93 vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.go−220 vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.go−115 vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.go−198 vendor/cloud.google.com/go/auth/credentials/internal/impersonate/idtoken.go−105 vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.go−168 vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.go−167 vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.go−89 vendor/cloud.google.com/go/auth/grpctransport/dial_socketopt.go−62 vendor/cloud.google.com/go/auth/grpctransport/directpath.go−177 vendor/cloud.google.com/go/auth/grpctransport/grpctransport.go−675 vendor/cloud.google.com/go/auth/grpctransport/pool.go−119 vendor/cloud.google.com/go/auth/httptransport/httptransport.go−254 vendor/cloud.google.com/go/auth/httptransport/transport.go−548 vendor/cloud.google.com/go/auth/internal/compute/compute.go−65 vendor/cloud.google.com/go/auth/internal/compute/manufacturer.go−22 vendor/cloud.google.com/go/auth/internal/compute/manufacturer_linux.go−23 vendor/cloud.google.com/go/auth/internal/compute/manufacturer_windows.go−46 vendor/cloud.google.com/go/auth/internal/credsfile/credsfile.go−63 vendor/cloud.google.com/go/auth/internal/credsfile/filetype.go−159 vendor/cloud.google.com/go/auth/internal/credsfile/parse.go−99 vendor/cloud.google.com/go/auth/internal/internal.go−285 vendor/cloud.google.com/go/auth/internal/jwt/jwt.go−208 vendor/cloud.google.com/go/auth/internal/retry/retry.go−117 vendor/cloud.google.com/go/auth/internal/transport/cba.go−361 vendor/cloud.google.com/go/auth/internal/transport/cert/default_cert.go−65 vendor/cloud.google.com/go/auth/internal/transport/cert/enterprise_cert.go−54 vendor/cloud.google.com/go/auth/internal/transport/cert/secureconnect_cert.go−124 vendor/cloud.google.com/go/auth/internal/transport/cert/workload_cert.go−138 vendor/cloud.google.com/go/auth/internal/transport/headers/headers.go−61 vendor/cloud.google.com/go/auth/internal/transport/s2a.go−138 vendor/cloud.google.com/go/auth/internal/transport/transport.go−137 vendor/cloud.google.com/go/auth/internal/trustboundary/external_accounts_config_providers.go−100 vendor/cloud.google.com/go/auth/internal/trustboundary/trust_boundary.go−392 vendor/cloud.google.com/go/auth/internal/version.go−20 vendor/cloud.google.com/go/auth/oauth2adapt/CHANGES.md−82 vendor/cloud.google.com/go/auth/oauth2adapt/LICENSE−202 vendor/cloud.google.com/go/auth/oauth2adapt/oauth2adapt.go−200 vendor/cloud.google.com/go/auth/threelegged.go−382 vendor/cloud.google.com/go/civil/civil.go−500 vendor/cloud.google.com/go/compute/metadata/CHANGES.md−115 vendor/cloud.google.com/go/compute/metadata/LICENSE−202 vendor/cloud.google.com/go/compute/metadata/README.md−27 vendor/cloud.google.com/go/compute/metadata/log.go−149 vendor/cloud.google.com/go/compute/metadata/metadata.go−937 vendor/cloud.google.com/go/compute/metadata/retry.go−117 vendor/cloud.google.com/go/compute/metadata/retry_linux.go−31 vendor/cloud.google.com/go/compute/metadata/syscheck.go−28 vendor/cloud.google.com/go/compute/metadata/syscheck_linux.go−30 vendor/cloud.google.com/go/compute/metadata/syscheck_windows.go−39 vendor/git.sr.ht/~jackmordaunt/go-toast/.gitignore−1 vendor/git.sr.ht/~jackmordaunt/go-toast/ARCHITECTURE.md−244 vendor/git.sr.ht/~jackmordaunt/go-toast/LICENSE−61 vendor/git.sr.ht/~jackmordaunt/go-toast/README.md−86 vendor/git.sr.ht/~jackmordaunt/go-toast/constants.go−65 vendor/git.sr.ht/~jackmordaunt/go-toast/internal/winrt/data/xml/dom/xmldocument.go−73 vendor/git.sr.ht/~jackmordaunt/go-toast/internal/winrt/ui/notifications/toastnotification.go−88 vendor/git.sr.ht/~jackmordaunt/go-toast/internal/winrt/ui/notifications/toastnotificationmanager.go−53 vendor/git.sr.ht/~jackmordaunt/go-toast/internal/winrt/ui/notifications/toastnotificationmanagerforuser.go−68 vendor/git.sr.ht/~jackmordaunt/go-toast/internal/winrt/ui/notifications/toastnotifier.go−64 vendor/git.sr.ht/~jackmordaunt/go-toast/tmpl/powershell.go.tmpl−14 vendor/git.sr.ht/~jackmordaunt/go-toast/tmpl/tmpl.go−28 vendor/git.sr.ht/~jackmordaunt/go-toast/tmpl/xml.go.tmpl−34 vendor/git.sr.ht/~jackmordaunt/go-toast/toast.go−225 vendor/git.sr.ht/~jackmordaunt/go-toast/wintoast/bind.go−86 vendor/git.sr.ht/~jackmordaunt/go-toast/wintoast/bind_noop.go−19 vendor/git.sr.ht/~jackmordaunt/go-toast/wintoast/bind_windows.go−174 vendor/git.sr.ht/~jackmordaunt/go-toast/wintoast/impl.go−179 vendor/git.sr.ht/~jackmordaunt/go-toast/wintoast/procs.go−37 vendor/git.sr.ht/~jackmordaunt/go-toast/wintoast/registry.go−110 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/CHANGELOG.md−849 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/LICENSE.txt−21 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/README.md−39 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/ci.yml−29 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud/cloud.go−44 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud/doc.go−53 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/core.go−173 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/doc.go−264 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/errors.go−17 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/etag.go−57 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/exported.go−175 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/pipeline.go−77 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/request.go−260 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/response_error.go−201 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/log/log.go−50 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/async/async.go−159 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/body/body.go−135 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/fake/fake.go−133 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/loc/loc.go−123 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/op/op.go−148 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/poller.go−24 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/util.go−212 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/constants.go−44 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/shared.go−149 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/log/doc.go−10 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/log/log.go−55 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/policy/doc.go−10 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/policy/policy.go−198 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/doc.go−10 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/errors.go−27 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pager.go−138 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pipeline.go−94 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_api_version.go−75 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_bearer_token.go−236 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_body_download.go−72 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_http_header.go−40 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_http_trace.go−154 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_include_response.go−35 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_key_credential.go−64 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_logging.go−264 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_request_id.go−34 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_retry.go−276 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_sas_credential.go−55 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_telemetry.go−83 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/poller.go−396 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/request.go−281 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/response.go−109 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_other.go−15 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_wasm.go−15 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_http_client.go−48 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming/doc.go−9 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming/progress.go−89 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing/constants.go−41 vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing/tracing.go−191 vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/LICENSE.txt−21 vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/diag/diag.go−51 vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/diag/doc.go−7 vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo/doc.go−7 vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo/errorinfo.go−46 vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/exported/exported.go−129 vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/log/doc.go−7 vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/log/log.go−104 vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/poller/util.go−155 vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/temporal/resource.go−123 vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/uuid/doc.go−7 vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/uuid/uuid.go−76 vendor/github.com/JohannesKaufmann/html-to-markdown/.gitignore−14 vendor/github.com/JohannesKaufmann/html-to-markdown/CONTRIBUTING.md vendor/github.com/JohannesKaufmann/html-to-markdown/LICENSE−21 vendor/github.com/JohannesKaufmann/html-to-markdown/README.md−242 vendor/github.com/JohannesKaufmann/html-to-markdown/SECURITY.md−6 vendor/github.com/JohannesKaufmann/html-to-markdown/commonmark.go−393 vendor/github.com/JohannesKaufmann/html-to-markdown/escape/escape.go−65 vendor/github.com/JohannesKaufmann/html-to-markdown/from.go−464 vendor/github.com/JohannesKaufmann/html-to-markdown/logo_five_years.png vendor/github.com/JohannesKaufmann/html-to-markdown/markdown.go−212 vendor/github.com/JohannesKaufmann/html-to-markdown/utils.go−533 vendor/github.com/KyleBanks/depth/.gitignore−16 vendor/github.com/KyleBanks/depth/.travis.yml−9 vendor/github.com/KyleBanks/depth/LICENSE−21 vendor/github.com/KyleBanks/depth/Makefile−32 vendor/github.com/KyleBanks/depth/README.md−232 vendor/github.com/KyleBanks/depth/depth.go−129 vendor/github.com/KyleBanks/depth/pkg.go−184 vendor/github.com/MakeNowJust/heredoc/LICENSE−21 vendor/github.com/MakeNowJust/heredoc/README.md−52 vendor/github.com/MakeNowJust/heredoc/heredoc.go−105 vendor/github.com/Microsoft/go-winio/.gitattributes−1 vendor/github.com/Microsoft/go-winio/.gitignore−10 vendor/github.com/Microsoft/go-winio/.golangci.yml−147 vendor/github.com/Microsoft/go-winio/CODEOWNERS−1 vendor/github.com/Microsoft/go-winio/LICENSE−22 vendor/github.com/Microsoft/go-winio/README.md−89 vendor/github.com/Microsoft/go-winio/SECURITY.md−41 vendor/github.com/Microsoft/go-winio/backup.go−287 vendor/github.com/Microsoft/go-winio/doc.go−22 vendor/github.com/Microsoft/go-winio/ea.go−137 vendor/github.com/Microsoft/go-winio/file.go−320 vendor/github.com/Microsoft/go-winio/fileinfo.go−106 vendor/github.com/Microsoft/go-winio/hvsock.go−582 vendor/github.com/Microsoft/go-winio/internal/fs/doc.go−2 vendor/github.com/Microsoft/go-winio/internal/fs/fs.go−262 vendor/github.com/Microsoft/go-winio/internal/fs/security.go−12 vendor/github.com/Microsoft/go-winio/internal/fs/zsyscall_windows.go−61 vendor/github.com/Microsoft/go-winio/internal/socket/rawaddr.go−20 vendor/github.com/Microsoft/go-winio/internal/socket/socket.go−177 vendor/github.com/Microsoft/go-winio/internal/socket/zsyscall_windows.go−69 vendor/github.com/Microsoft/go-winio/internal/stringbuffer/wstring.go−132 vendor/github.com/Microsoft/go-winio/pipe.go−586 vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go−232 vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go−16 vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go−13 vendor/github.com/Microsoft/go-winio/pkg/guid/variant_string.go−27 vendor/github.com/Microsoft/go-winio/privilege.go−196 vendor/github.com/Microsoft/go-winio/reparse.go−131 vendor/github.com/Microsoft/go-winio/sd.go−133 vendor/github.com/Microsoft/go-winio/syscall.go−5 vendor/github.com/Microsoft/go-winio/zsyscall_windows.go−378 vendor/github.com/PuerkitoBio/goquery/.gitattributes−1 vendor/github.com/PuerkitoBio/goquery/.gitignore−16 vendor/github.com/PuerkitoBio/goquery/LICENSE−12 vendor/github.com/PuerkitoBio/goquery/README.md−218 vendor/github.com/PuerkitoBio/goquery/array.go−124 vendor/github.com/PuerkitoBio/goquery/doc.go−123 vendor/github.com/PuerkitoBio/goquery/expand.go−70 vendor/github.com/PuerkitoBio/goquery/filter.go−163 vendor/github.com/PuerkitoBio/goquery/iteration.go−61 vendor/github.com/PuerkitoBio/goquery/manipulation.go−679 vendor/github.com/PuerkitoBio/goquery/property.go−275 vendor/github.com/PuerkitoBio/goquery/query.go−49 vendor/github.com/PuerkitoBio/goquery/traversal.go−704 vendor/github.com/PuerkitoBio/goquery/type.go−203 vendor/github.com/PuerkitoBio/goquery/utilities.go−177 vendor/github.com/alecthomas/chroma/v2/.editorconfig−17 vendor/github.com/alecthomas/chroma/v2/.gitignore−28 vendor/github.com/alecthomas/chroma/v2/.golangci.yml−122 vendor/github.com/alecthomas/chroma/v2/.goreleaser.yml−34 vendor/github.com/alecthomas/chroma/v2/AGENTS.md−11 vendor/github.com/alecthomas/chroma/v2/BUILD.bit−93 vendor/github.com/alecthomas/chroma/v2/COPYING−118 vendor/github.com/alecthomas/chroma/v2/Dockerfile−64 vendor/github.com/alecthomas/chroma/v2/Justfile−59 vendor/github.com/alecthomas/chroma/v2/README.md−309 vendor/github.com/alecthomas/chroma/v2/biome.json−6 vendor/github.com/alecthomas/chroma/v2/chroma.jpg vendor/github.com/alecthomas/chroma/v2/coalesce.go−35 vendor/github.com/alecthomas/chroma/v2/colour.go−192 vendor/github.com/alecthomas/chroma/v2/delegate.go−161 vendor/github.com/alecthomas/chroma/v2/doc.go−7 vendor/github.com/alecthomas/chroma/v2/emitters.go−233 vendor/github.com/alecthomas/chroma/v2/formatter.go−43 vendor/github.com/alecthomas/chroma/v2/formatters/api.go−57 vendor/github.com/alecthomas/chroma/v2/formatters/html/html.go−692 vendor/github.com/alecthomas/chroma/v2/formatters/json.go−39 vendor/github.com/alecthomas/chroma/v2/formatters/svg/font_liberation_mono.go−51 vendor/github.com/alecthomas/chroma/v2/formatters/svg/svg.go−222 vendor/github.com/alecthomas/chroma/v2/formatters/tokens.go−18 vendor/github.com/alecthomas/chroma/v2/formatters/tty_indexed.go−284 vendor/github.com/alecthomas/chroma/v2/formatters/tty_truecolour.go−76 vendor/github.com/alecthomas/chroma/v2/iterator.go−93 vendor/github.com/alecthomas/chroma/v2/lexer.go−179 vendor/github.com/alecthomas/chroma/v2/lexers/README.md−46 vendor/github.com/alecthomas/chroma/v2/lexers/caddyfile.go−275 vendor/github.com/alecthomas/chroma/v2/lexers/cl.go−243 vendor/github.com/alecthomas/chroma/v2/lexers/dns.go−17 vendor/github.com/alecthomas/chroma/v2/lexers/emacs.go−533 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abap.xml−154 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abnf.xml−66 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript.xml−68 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript_3.xml−163 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ada.xml−321 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/agda.xml−66 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/al.xml−75 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/alloy.xml−58 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ampl.xml−98 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/angular2.xml−109 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/antlr.xml−317 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apacheconf.xml−74 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apl.xml−59 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/applescript.xml−151 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arangodb_aql.xml−174 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arduino.xml−322 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/armasm.xml−126 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arturo.xml−119 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/atl.xml−165 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/autohotkey.xml−78 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/autoit.xml−70 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/awk.xml−95 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ballerina.xml−97 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bash.xml−222 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bash_session.xml−25 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/batchfile.xml−660 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/beef.xml−120 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bibtex.xml−152 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bicep.xml−84 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/blitzbasic.xml−141 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bnf.xml−28 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bqn.xml−83 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/brainfuck.xml−51 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c#.xml−121 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c++.xml−331 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c.xml−260 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c3.xml−374 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cap_n_proto.xml−122 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cassandra_cql.xml−137 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ceylon.xml−151 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cfengine3.xml−197 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cfstatement.xml−92 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/chaiscript.xml−134 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/chapel.xml−143 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cheetah.xml−55 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/clojure.xml−71 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cmake.xml−90 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cobol.xml−90 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/coffeescript.xml−210 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/common_lisp.xml−184 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/coq.xml−136 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/core.xml−79 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/crystal.xml−762 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/css.xml−323 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/csv.xml−53 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cue.xml−85 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cython.xml−372 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/d.xml−133 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dart.xml−213 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dax.xml−39 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/desktop_entry.xml−17 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/devicetree.xml−251 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/diff.xml−52 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/django_jinja.xml−153 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dns.xml−44 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/docker.xml−68 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dtd.xml−168 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dylan.xml−176 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ebnf.xml−90 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/elixir.xml−744 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/elm.xml−119 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/emacslisp.xml−132 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/erb.xml−37 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/erlang.xml−166 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/factor.xml−412 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fennel.xml−68 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fish.xml−159 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/forth.xml−78 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fortran.xml−102 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fortranfixed.xml−71 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fsharp.xml−245 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gas.xml−150 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gdscript.xml−259 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gdscript3.xml−270 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gemfile_lock.xml−81 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gettext.xml−24 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gherkin.xml−263 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gleam.xml−100 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/glsl.xml−65 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gnuplot.xml−289 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/go_template.xml−114 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/graphql.xml−92 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/groff.xml−90 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/groovy.xml−135 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/handlebars.xml−147 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hare.xml−98 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/haskell.xml−275 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hcl.xml−143 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hexdump.xml−189 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hlb.xml−131 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hlsl.xml−110 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/holyc.xml−252 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/html.xml−159 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hy.xml−104 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/idris.xml−216 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/igor.xml−47 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ini.xml−52 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/io.xml−71 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/iscdhcpd.xml−96 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/j.xml−157 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/janet.xml−48 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/java.xml−206 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/javascript.xml−160 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/json.xml−117 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jsonata.xml−83 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jsonnet.xml−138 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/julia.xml−400 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jungle.xml−98 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kakoune.xml−96 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kdl.xml−75 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kotlin.xml−234 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lateralus.xml−184 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lean.xml−56 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lighttpd_configuration_file.xml−42 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lilypond.xml−133 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/llvm.xml−73 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lox.xml−78 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lua.xml−158 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/luau.xml−173 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/makefile.xml−131 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mako.xml−120 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mason.xml−89 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/materialize_sql_dialect.xml−155 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mathematica.xml−60 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/matlab.xml−114 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mcfunction.xml−138 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/meson.xml−86 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/metal.xml−270 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/microcad.xml−139 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/minizinc.xml−82 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mlir.xml−73 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/modelica.xml−106 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/modula-2.xml−245 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mojo.xml−228 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/monkeyc.xml−153 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/moonbit.xml−75 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/moonscript.xml−83 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/morrowindscript.xml−90 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/myghty.xml−77 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mysql.xml−121 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nasm.xml−126 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/natural.xml−143 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ndisasm.xml−123 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/newspeak.xml−121 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nginx_configuration_file.xml−101 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nim.xml−211 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nix.xml−258 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nsis.xml−59 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nu.xml−126 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/objective-c.xml−510 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/objectpascal.xml−142 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ocaml.xml−153 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/octave.xml−101 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/odin.xml−127 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/onesenterprise.xml−92 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/openedge_abl.xml−101 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/openscad.xml−96 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/org_mode.xml−329 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pacmanconf.xml−37 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/perl.xml−400 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/php.xml−258 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pig.xml−105 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pkgconfig.xml−73 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pl_pgsql.xml−119 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/plaintext.xml−21 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/plutus_core.xml−105 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pony.xml−135 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/postgresql_sql_dialect.xml−155 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/postscript.xml−93 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/povray.xml−58 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/powerquery.xml−51 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/powershell.xml−230 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/prolog.xml−115 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/promela.xml−119 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/promql.xml−123 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/properties.xml−45 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/protocol_buffer.xml−124 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/prql.xml−161 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/psl.xml−213 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/puppet.xml−100 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/python.xml−595 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/python_2.xml−356 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/qbasic.xml−173 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/qml.xml−113 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/r.xml−128 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/racket.xml−260 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ragel.xml−149 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/react.xml−236 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/reasonml.xml−147 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/reg.xml−68 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rego.xml−94 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rexx.xml−127 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rgbasm.xml−239 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ring.xml−74 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rpgle.xml−176 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rpm_spec.xml−58 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ruby.xml−728 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rust.xml−375 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sas.xml−191 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sass.xml−362 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scala.xml−274 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scdoc.xml−115 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scheme.xml−106 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scilab.xml−98 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scss.xml−373 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sed.xml−92 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sieve.xml−61 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smali.xml−73 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smalltalk.xml−294 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smarty.xml−79 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/snbt.xml−58 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/snobol.xml−95 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/solidity.xml−291 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sourcepawn.xml−59 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/spade.xml−292 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sparql.xml−160 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sql.xml−90 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/squidconf.xml−63 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/standard_ml.xml−548 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/stas.xml−85 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/stylus.xml−132 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/swift.xml−207 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/systemd.xml−63 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/systemverilog.xml−181 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tablegen.xml−69 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tal.xml−43 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tasm.xml−135 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tcl.xml−272 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tcsh.xml−121 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/templ.xml−315 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/termcap.xml−75 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/terminfo.xml−84 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/terraform.xml−149 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tex.xml−113 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/thrift.xml−154 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/toml.xml−45 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tradingview.xml−81 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/transact-sql.xml−137 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/turing.xml−82 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/turtle.xml−170 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/twig.xml−155 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/txtpb.xml−162 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typescript.xml−302 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscript.xml−178 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscriptcssdata.xml−52 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscripthtmldata.xml−52 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typst.xml−108 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ucode.xml−147 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/v.xml−355 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/v_shell.xml−365 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vala.xml−72 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vb_net.xml−162 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/verilog.xml−158 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vhdl.xml−171 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vhs.xml−48 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/viml.xml−85 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vue.xml−295 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/wat.xml−149 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/wdte.xml−43 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/webgpu_shading_language.xml−142 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/webvtt.xml−283 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/whiley.xml−57 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/xml.xml−96 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/xorg.xml−35 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/yaml.xml−132 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/yang.xml−99 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/z80_assembly.xml−74 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/zed.xml−51 vendor/github.com/alecthomas/chroma/v2/lexers/embedded/zig.xml−187 vendor/github.com/alecthomas/chroma/v2/lexers/erb.go−29 vendor/github.com/alecthomas/chroma/v2/lexers/gemtext.go−37 vendor/github.com/alecthomas/chroma/v2/lexers/genshi.go−118 vendor/github.com/alecthomas/chroma/v2/lexers/go.go−81 vendor/github.com/alecthomas/chroma/v2/lexers/haxe.go−647 vendor/github.com/alecthomas/chroma/v2/lexers/html.go−8 vendor/github.com/alecthomas/chroma/v2/lexers/http.go−131 vendor/github.com/alecthomas/chroma/v2/lexers/lexers.go−85 vendor/github.com/alecthomas/chroma/v2/lexers/markdown.go−107 vendor/github.com/alecthomas/chroma/v2/lexers/markless.go−168 vendor/github.com/alecthomas/chroma/v2/lexers/mysql.go−33 vendor/github.com/alecthomas/chroma/v2/lexers/php.go−37 vendor/github.com/alecthomas/chroma/v2/lexers/raku.go−1710 vendor/github.com/alecthomas/chroma/v2/lexers/rst.go−89 vendor/github.com/alecthomas/chroma/v2/lexers/svelte.go−70 vendor/github.com/alecthomas/chroma/v2/lexers/typoscript.go−85 vendor/github.com/alecthomas/chroma/v2/lexers/yaml_jinja.go−20 vendor/github.com/alecthomas/chroma/v2/lexers/zed.go−24 vendor/github.com/alecthomas/chroma/v2/mutators.go−201 vendor/github.com/alecthomas/chroma/v2/pygments-lexers.txt−605 vendor/github.com/alecthomas/chroma/v2/quick/quick.go−44 vendor/github.com/alecthomas/chroma/v2/regexp.go−533 vendor/github.com/alecthomas/chroma/v2/registry.go−228 vendor/github.com/alecthomas/chroma/v2/remap.go−94 vendor/github.com/alecthomas/chroma/v2/renovate.json5−25 vendor/github.com/alecthomas/chroma/v2/serialise.go−483 vendor/github.com/alecthomas/chroma/v2/style.go−530 vendor/github.com/alecthomas/chroma/v2/styles/abap.xml−11 vendor/github.com/alecthomas/chroma/v2/styles/algol.xml−18 vendor/github.com/alecthomas/chroma/v2/styles/algol_nu.xml−18 vendor/github.com/alecthomas/chroma/v2/styles/api.go−95 vendor/github.com/alecthomas/chroma/v2/styles/arduino.xml−18 vendor/github.com/alecthomas/chroma/v2/styles/ashen.xml−69 vendor/github.com/alecthomas/chroma/v2/styles/aura-theme-dark-soft.xml−107 vendor/github.com/alecthomas/chroma/v2/styles/aura-theme-dark.xml−107 vendor/github.com/alecthomas/chroma/v2/styles/autumn.xml−36 vendor/github.com/alecthomas/chroma/v2/styles/average.xml−74 vendor/github.com/alecthomas/chroma/v2/styles/base16-snazzy.xml−74 vendor/github.com/alecthomas/chroma/v2/styles/borland.xml−26 vendor/github.com/alecthomas/chroma/v2/styles/bw.xml−23 vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-frappe.xml−83 vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-latte.xml−83 vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-macchiato.xml−83 vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-mocha.xml−83 vendor/github.com/alecthomas/chroma/v2/styles/colorful.xml−52 vendor/github.com/alecthomas/chroma/v2/styles/compat.go−69 vendor/github.com/alecthomas/chroma/v2/styles/darcula.xml−83 vendor/github.com/alecthomas/chroma/v2/styles/doom-one.xml−51 vendor/github.com/alecthomas/chroma/v2/styles/doom-one2.xml−64 vendor/github.com/alecthomas/chroma/v2/styles/dracula.xml−74 vendor/github.com/alecthomas/chroma/v2/styles/emacs.xml−44 vendor/github.com/alecthomas/chroma/v2/styles/evergarden.xml−33 vendor/github.com/alecthomas/chroma/v2/styles/friendly.xml−44 vendor/github.com/alecthomas/chroma/v2/styles/fruity.xml−19 vendor/github.com/alecthomas/chroma/v2/styles/github-dark.xml−46 vendor/github.com/alecthomas/chroma/v2/styles/github.xml−39 vendor/github.com/alecthomas/chroma/v2/styles/gruvbox-light.xml−34 vendor/github.com/alecthomas/chroma/v2/styles/gruvbox.xml−34 vendor/github.com/alecthomas/chroma/v2/styles/hr_high_contrast.xml−12 vendor/github.com/alecthomas/chroma/v2/styles/hrdark.xml−10 vendor/github.com/alecthomas/chroma/v2/styles/igor.xml−9 vendor/github.com/alecthomas/chroma/v2/styles/kanagawa-dragon.xml−83 vendor/github.com/alecthomas/chroma/v2/styles/kanagawa-lotus.xml−83 vendor/github.com/alecthomas/chroma/v2/styles/kanagawa-wave.xml−83 vendor/github.com/alecthomas/chroma/v2/styles/lovelace.xml−53 vendor/github.com/alecthomas/chroma/v2/styles/manni.xml−44 vendor/github.com/alecthomas/chroma/v2/styles/modus-operandi.xml−13 vendor/github.com/alecthomas/chroma/v2/styles/modus-vivendi.xml−13 vendor/github.com/alecthomas/chroma/v2/styles/monokai.xml−29 vendor/github.com/alecthomas/chroma/v2/styles/monokailight.xml−26 vendor/github.com/alecthomas/chroma/v2/styles/murphy.xml−52 vendor/github.com/alecthomas/chroma/v2/styles/native.xml−35 vendor/github.com/alecthomas/chroma/v2/styles/nord.xml−46 vendor/github.com/alecthomas/chroma/v2/styles/nordic.xml−46 vendor/github.com/alecthomas/chroma/v2/styles/onedark.xml−25 vendor/github.com/alecthomas/chroma/v2/styles/onesenterprise.xml−10 vendor/github.com/alecthomas/chroma/v2/styles/paraiso-dark.xml−37 vendor/github.com/alecthomas/chroma/v2/styles/paraiso-light.xml−37 vendor/github.com/alecthomas/chroma/v2/styles/pastie.xml−45 vendor/github.com/alecthomas/chroma/v2/styles/perldoc.xml−37 vendor/github.com/alecthomas/chroma/v2/styles/pygments.xml−42 vendor/github.com/alecthomas/chroma/v2/styles/rainbow_dash.xml−40 vendor/github.com/alecthomas/chroma/v2/styles/rose-pine-dawn.xml−29 vendor/github.com/alecthomas/chroma/v2/styles/rose-pine-moon.xml−29 vendor/github.com/alecthomas/chroma/v2/styles/rose-pine.xml−29 vendor/github.com/alecthomas/chroma/v2/styles/rpgle.xml−30 vendor/github.com/alecthomas/chroma/v2/styles/rrt.xml−19 vendor/github.com/alecthomas/chroma/v2/styles/solarized-dark.xml−39 vendor/github.com/alecthomas/chroma/v2/styles/solarized-dark256.xml−41 vendor/github.com/alecthomas/chroma/v2/styles/solarized-light.xml−17 vendor/github.com/alecthomas/chroma/v2/styles/swapoff.xml−18 vendor/github.com/alecthomas/chroma/v2/styles/tango.xml−72 vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-day.xml−83 vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-moon.xml−83 vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-night.xml−83 vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-storm.xml−83 vendor/github.com/alecthomas/chroma/v2/styles/trac.xml−35 vendor/github.com/alecthomas/chroma/v2/styles/vim.xml−29 vendor/github.com/alecthomas/chroma/v2/styles/vs.xml−16 vendor/github.com/alecthomas/chroma/v2/styles/vulcan.xml−74 vendor/github.com/alecthomas/chroma/v2/styles/witchhazel.xml−31 vendor/github.com/alecthomas/chroma/v2/styles/xcode-dark.xml−31 vendor/github.com/alecthomas/chroma/v2/styles/xcode.xml−22 vendor/github.com/alecthomas/chroma/v2/table.py−31 vendor/github.com/alecthomas/chroma/v2/test.jsonl−2 vendor/github.com/alecthomas/chroma/v2/tokentype_enumer.go−588 vendor/github.com/alecthomas/chroma/v2/types.go−357 vendor/github.com/andybalholm/brotli/.gitignore−2 vendor/github.com/andybalholm/brotli/LICENSE−19 vendor/github.com/andybalholm/brotli/README.md−19 vendor/github.com/andybalholm/brotli/backward_references.go−185 vendor/github.com/andybalholm/brotli/backward_references_hq.go−796 vendor/github.com/andybalholm/brotli/bit_cost.go−436 vendor/github.com/andybalholm/brotli/bit_reader.go−266 vendor/github.com/andybalholm/brotli/bitwriter.go−56 vendor/github.com/andybalholm/brotli/block_splitter.go−144 vendor/github.com/andybalholm/brotli/block_splitter_command.go−434 vendor/github.com/andybalholm/brotli/block_splitter_distance.go−433 vendor/github.com/andybalholm/brotli/block_splitter_literal.go−433 vendor/github.com/andybalholm/brotli/brotli_bit_stream.go−1435 vendor/github.com/andybalholm/brotli/cluster.go−30 vendor/github.com/andybalholm/brotli/cluster_command.go−164 vendor/github.com/andybalholm/brotli/cluster_distance.go−326 vendor/github.com/andybalholm/brotli/cluster_literal.go−326 vendor/github.com/andybalholm/brotli/command.go−254 vendor/github.com/andybalholm/brotli/compress_fragment.go−834 vendor/github.com/andybalholm/brotli/compress_fragment_two_pass.go−775 vendor/github.com/andybalholm/brotli/constants.go−77 vendor/github.com/andybalholm/brotli/context.go−2176 vendor/github.com/andybalholm/brotli/decode.go−2581 vendor/github.com/andybalholm/brotli/dictionary.go−122890 vendor/github.com/andybalholm/brotli/dictionary_hash.go−32779 vendor/github.com/andybalholm/brotli/encode.go−1220 vendor/github.com/andybalholm/brotli/encoder.go−177 vendor/github.com/andybalholm/brotli/encoder_dict.go−22 vendor/github.com/andybalholm/brotli/encoder_fast.go−197 vendor/github.com/andybalholm/brotli/entropy_encode.go−592 vendor/github.com/andybalholm/brotli/entropy_encode_static.go−4399 vendor/github.com/andybalholm/brotli/fast_log.go−290 vendor/github.com/andybalholm/brotli/find_match_length.go−45 vendor/github.com/andybalholm/brotli/flate/LICENSE−27 vendor/github.com/andybalholm/brotli/flate/README.md−2 vendor/github.com/andybalholm/brotli/flate/gzip.go−65 vendor/github.com/andybalholm/brotli/flate/huffman_bit_writer.go−517 vendor/github.com/andybalholm/brotli/flate/huffman_code.go−346 vendor/github.com/andybalholm/brotli/flate/token.go−80 vendor/github.com/andybalholm/brotli/flate/writer.go−66 vendor/github.com/andybalholm/brotli/h10.go−287 vendor/github.com/andybalholm/brotli/h5.go−214 vendor/github.com/andybalholm/brotli/h6.go−216 vendor/github.com/andybalholm/brotli/hash.go−342 vendor/github.com/andybalholm/brotli/hash_composite.go−93 vendor/github.com/andybalholm/brotli/hash_forgetful_chain.go−252 vendor/github.com/andybalholm/brotli/hash_longest_match_quickly.go−214 vendor/github.com/andybalholm/brotli/hash_rolling.go−168 vendor/github.com/andybalholm/brotli/histogram.go−226 vendor/github.com/andybalholm/brotli/http.go−189 vendor/github.com/andybalholm/brotli/huffman.go−653 vendor/github.com/andybalholm/brotli/literal_cost.go−182 vendor/github.com/andybalholm/brotli/matchfinder/bargain1.go−212 vendor/github.com/andybalholm/brotli/matchfinder/bargain2.go−248 vendor/github.com/andybalholm/brotli/matchfinder/bargain3.go−278 vendor/github.com/andybalholm/brotli/matchfinder/emitter.go−34 vendor/github.com/andybalholm/brotli/matchfinder/m0.go−175 vendor/github.com/andybalholm/brotli/matchfinder/m4.go−339 vendor/github.com/andybalholm/brotli/matchfinder/matchfinder.go−103 vendor/github.com/andybalholm/brotli/matchfinder/pathfinder.go−328 vendor/github.com/andybalholm/brotli/matchfinder/textencoder.go−53 vendor/github.com/andybalholm/brotli/matchfinder/trio.go−326 vendor/github.com/andybalholm/brotli/matchfinder/zdfast.go−258 vendor/github.com/andybalholm/brotli/matchfinder/zfast.go−206 vendor/github.com/andybalholm/brotli/matchfinder/zm.go−301 vendor/github.com/andybalholm/brotli/memory.go−66 vendor/github.com/andybalholm/brotli/metablock.go−574 vendor/github.com/andybalholm/brotli/metablock_command.go−165 vendor/github.com/andybalholm/brotli/metablock_distance.go−165 vendor/github.com/andybalholm/brotli/metablock_literal.go−165 vendor/github.com/andybalholm/brotli/params.go−37 vendor/github.com/andybalholm/brotli/platform.go−103 vendor/github.com/andybalholm/brotli/prefix.go−30 vendor/github.com/andybalholm/brotli/prefix_dec.go−723 vendor/github.com/andybalholm/brotli/quality.go−196 vendor/github.com/andybalholm/brotli/reader.go−111 vendor/github.com/andybalholm/brotli/ringbuffer.go−134 vendor/github.com/andybalholm/brotli/state.go−294 vendor/github.com/andybalholm/brotli/static_dict.go−662 vendor/github.com/andybalholm/brotli/static_dict_lut.go−75094 vendor/github.com/andybalholm/brotli/symbol_list.go−22 vendor/github.com/andybalholm/brotli/transform.go−641 vendor/github.com/andybalholm/brotli/utf8_util.go−70 vendor/github.com/andybalholm/brotli/util.go−7 vendor/github.com/andybalholm/brotli/write_bits.go−52 vendor/github.com/andybalholm/brotli/writer.go−162 vendor/github.com/andybalholm/cascadia/.travis.yml−14 vendor/github.com/andybalholm/cascadia/LICENSE−24 vendor/github.com/andybalholm/cascadia/README.md−144 vendor/github.com/andybalholm/cascadia/parser.go−889 vendor/github.com/andybalholm/cascadia/pseudo_classes.go−458 vendor/github.com/andybalholm/cascadia/selector.go−586 vendor/github.com/andybalholm/cascadia/serialize.go−176 vendor/github.com/andybalholm/cascadia/specificity.go−26 vendor/github.com/atotto/clipboard/.travis.yml−22 vendor/github.com/atotto/clipboard/LICENSE−27 vendor/github.com/atotto/clipboard/README.md−48 vendor/github.com/atotto/clipboard/clipboard.go−20 vendor/github.com/atotto/clipboard/clipboard_darwin.go−52 vendor/github.com/atotto/clipboard/clipboard_plan9.go−42 vendor/github.com/atotto/clipboard/clipboard_unix.go−149 vendor/github.com/atotto/clipboard/clipboard_windows.go−157 vendor/github.com/aws/aws-sdk-go-v2/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/NOTICE.txt−3 vendor/github.com/aws/aws-sdk-go-v2/aws/accountid_endpoint_mode.go−18 vendor/github.com/aws/aws-sdk-go-v2/aws/checksum.go−33 vendor/github.com/aws/aws-sdk-go-v2/aws/config.go−254 vendor/github.com/aws/aws-sdk-go-v2/aws/context.go−22 vendor/github.com/aws/aws-sdk-go-v2/aws/credential_cache.go−235 vendor/github.com/aws/aws-sdk-go-v2/aws/credentials.go−234 vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/auto.go−38 vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/configuration.go−43 vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/defaults.go−50 vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/doc.go−2 vendor/github.com/aws/aws-sdk-go-v2/aws/defaultsmode.go−95 vendor/github.com/aws/aws-sdk-go-v2/aws/doc.go−62 vendor/github.com/aws/aws-sdk-go-v2/aws/endpoints.go−247 vendor/github.com/aws/aws-sdk-go-v2/aws/errors.go−9 vendor/github.com/aws/aws-sdk-go-v2/aws/from_ptr.go−365 vendor/github.com/aws/aws-sdk-go-v2/aws/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/aws/logging.go−119 vendor/github.com/aws/aws-sdk-go-v2/aws/logging_generate.go−95 vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/metadata.go−213 vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/middleware.go−168 vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/osname.go−24 vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/osname_go115.go−24 vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/recursion_detection.go−94 vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/request_id.go−27 vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/request_id_retriever.go−57 vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/user_agent.go−398 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/CHANGELOG.md−203 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/debug.go−144 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/decode.go−218 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/encode.go−167 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/error.go−23 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi/headers.go−24 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi/middleware.go−71 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi/transport.go−13 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi/transport_go117.go−12 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/header.go−175 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/header_value.go−521 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/message.go−99 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/array.go−61 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/encoder.go−80 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/map.go−78 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/middleware.go−62 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/object.go−68 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/value.go−117 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/restjson/decoder_util.go−85 vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/xml/error_utils.go−48 vendor/github.com/aws/aws-sdk-go-v2/aws/ratelimit/none.go−20 vendor/github.com/aws/aws-sdk-go-v2/aws/ratelimit/token_bucket.go−96 vendor/github.com/aws/aws-sdk-go-v2/aws/ratelimit/token_rate_limit.go−83 vendor/github.com/aws/aws-sdk-go-v2/aws/request.go−25 vendor/github.com/aws/aws-sdk-go-v2/aws/restrict_file_permissions.go−21 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/adaptive.go−156 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/adaptive_ratelimit.go−158 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/adaptive_token_bucket.go−83 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/attempt_metrics.go−51 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/doc.go−80 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/errors.go−20 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/jitter_backoff.go−124 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/metadata.go−52 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/middleware.go−487 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/retry.go−103 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/retryable_error.go−228 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/standard.go−343 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/throttle_error.go−60 vendor/github.com/aws/aws-sdk-go-v2/aws/retry/timeout_error.go−52 vendor/github.com/aws/aws-sdk-go-v2/aws/retryer.go−127 vendor/github.com/aws/aws-sdk-go-v2/aws/runtime.go−14 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/cache.go−115 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/const.go−40 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/header_rules.go−82 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/headers.go−70 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/hmac.go−13 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/host.go−75 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/scope.go−13 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/time.go−36 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/util.go−80 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/middleware.go−420 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/presign_middleware.go−127 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/stream.go−86 vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/v4.go−564 vendor/github.com/aws/aws-sdk-go-v2/aws/to_ptr.go−297 vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/client.go−355 vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/content_type.go−42 vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/response_error.go−33 vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/response_error_middleware.go−56 vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/timeout_read_closer.go−109 vendor/github.com/aws/aws-sdk-go-v2/aws/types.go−42 vendor/github.com/aws/aws-sdk-go-v2/aws/version.go−8 vendor/github.com/aws/aws-sdk-go-v2/config/CHANGELOG.md−1097 vendor/github.com/aws/aws-sdk-go-v2/config/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/config/auth_scheme_preference.go−19 vendor/github.com/aws/aws-sdk-go-v2/config/config.go−237 vendor/github.com/aws/aws-sdk-go-v2/config/defaultsmode.go−47 vendor/github.com/aws/aws-sdk-go-v2/config/doc.go−20 vendor/github.com/aws/aws-sdk-go-v2/config/env_config.go−966 vendor/github.com/aws/aws-sdk-go-v2/config/generate.go−4 vendor/github.com/aws/aws-sdk-go-v2/config/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/errors.go−22 vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/ini.go−56 vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/parse.go−109 vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/sections.go−157 vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/strings.go−89 vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/token.go−32 vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/tokenize.go−92 vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/value.go−93 vendor/github.com/aws/aws-sdk-go-v2/config/load_options.go−1371 vendor/github.com/aws/aws-sdk-go-v2/config/local.go−51 vendor/github.com/aws/aws-sdk-go-v2/config/provider.go−802 vendor/github.com/aws/aws-sdk-go-v2/config/resolve.go−458 vendor/github.com/aws/aws-sdk-go-v2/config/resolve_bearer_token.go−122 vendor/github.com/aws/aws-sdk-go-v2/config/resolve_credentials.go−650 vendor/github.com/aws/aws-sdk-go-v2/config/shared_config.go−1704 vendor/github.com/aws/aws-sdk-go-v2/credentials/CHANGELOG.md−990 vendor/github.com/aws/aws-sdk-go-v2/credentials/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/credentials/doc.go−4 vendor/github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds/doc.go−58 vendor/github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds/provider.go−241 vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client/auth.go−48 vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client/client.go−165 vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client/endpoints.go−20 vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client/middleware.go−164 vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/provider.go−207 vendor/github.com/aws/aws-sdk-go-v2/credentials/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/credentials/logincreds/dpop.go−150 vendor/github.com/aws/aws-sdk-go-v2/credentials/logincreds/file.go−14 vendor/github.com/aws/aws-sdk-go-v2/credentials/logincreds/provider.go−184 vendor/github.com/aws/aws-sdk-go-v2/credentials/logincreds/token.go−110 vendor/github.com/aws/aws-sdk-go-v2/credentials/processcreds/doc.go−92 vendor/github.com/aws/aws-sdk-go-v2/credentials/processcreds/provider.go−296 vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/doc.go−81 vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/sso_cached_token.go−232 vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/sso_credentials_provider.go−165 vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/sso_token_provider.go−147 vendor/github.com/aws/aws-sdk-go-v2/credentials/static_provider.go−63 vendor/github.com/aws/aws-sdk-go-v2/credentials/stscreds/assume_role_provider.go−338 vendor/github.com/aws/aws-sdk-go-v2/credentials/stscreds/web_identity_provider.go−181 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/CHANGELOG.md−584 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_client.go−358 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetDynamicData.go−77 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetIAMInfo.go−103 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetInstanceIdentityDocument.go−110 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetMetadata.go−77 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetRegion.go−73 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetToken.go−119 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetUserData.go−61 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/auth.go−48 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/doc.go−12 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/endpoints.go−20 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/internal/config/resolvers.go−114 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/request_middleware.go−313 vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/token_provider.go−261 vendor/github.com/aws/aws-sdk-go-v2/internal/auth/auth.go−45 vendor/github.com/aws/aws-sdk-go-v2/internal/auth/scheme.go−191 vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/bearer_token_adapter.go−43 vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/bearer_token_signer_adapter.go−35 vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/credentials_adapter.go−46 vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/smithy.go−2 vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/v4signer_adapter.go−57 vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/v4signer_adapter_eventstream.go−51 vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/CHANGELOG.md−544 vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/config.go−65 vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/endpoints.go−57 vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/internal/context/context.go−65 vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/arn.go−94 vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/doc.go−3 vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/generate.go−7 vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/host.go−51 vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/partition.go−76 vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/partitions.go−496 vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/partitions.json−267 vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/endpoints.go−201 vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/CHANGELOG.md−519 vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/endpoints.go−302 vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/internal/rand/rand.go−33 vendor/github.com/aws/aws-sdk-go-v2/internal/sdk/interfaces.go−9 vendor/github.com/aws/aws-sdk-go-v2/internal/sdk/time.go−74 vendor/github.com/aws/aws-sdk-go-v2/internal/sdkio/byte.go−12 vendor/github.com/aws/aws-sdk-go-v2/internal/shareddefaults/shared_config.go−47 vendor/github.com/aws/aws-sdk-go-v2/internal/strings/strings.go−11 vendor/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight/LICENSE−28 vendor/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight/docs.go−7 vendor/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight/singleflight.go−210 vendor/github.com/aws/aws-sdk-go-v2/internal/timeconv/duration.go−13 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/CHANGELOG.md−486 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/credentials.go−141 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/error.go−17 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/crypto/compare.go−30 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/crypto/ecc.go−113 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/const.go−36 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/header_rules.go−82 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/headers.go−68 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/hmac.go−13 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/host.go−75 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/time.go−36 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/util.go−64 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/middleware.go−118 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/presign_middleware.go−117 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/smithy.go−92 vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/v4a.go−520 vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/CHANGELOG.md−220 vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/accept_encoding_gzip.go−176 vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/doc.go−22 vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/CHANGELOG.md−571 vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/context.go−56 vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/doc.go−3 vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/middleware.go−110 vendor/github.com/aws/aws-sdk-go-v2/service/signin/CHANGELOG.md−85 vendor/github.com/aws/aws-sdk-go-v2/service/signin/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_client.go−945 vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_CreateOAuth2Token.go−212 vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_DeleteConsoleAuthorizationConfiguration.go−168 vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_DeleteResourcePermissionStatement.go−196 vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_GetConsoleAuthorizationConfiguration.go−168 vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_GetResourcePolicy.go−155 vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_ListResourcePermissionStatements.go−261 vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_PutConsoleAuthorizationConfiguration.go−168 vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_PutResourcePermissionStatement.go−216 vendor/github.com/aws/aws-sdk-go-v2/service/signin/auth.go−361 vendor/github.com/aws/aws-sdk-go-v2/service/signin/deserializers.go−2474 vendor/github.com/aws/aws-sdk-go-v2/service/signin/doc.go−9 vendor/github.com/aws/aws-sdk-go-v2/service/signin/endpoints.go−854 vendor/github.com/aws/aws-sdk-go-v2/service/signin/generated.json−40 vendor/github.com/aws/aws-sdk-go-v2/service/signin/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/service/signin/internal/endpoints/endpoints.go−333 vendor/github.com/aws/aws-sdk-go-v2/service/signin/options.go−242 vendor/github.com/aws/aws-sdk-go-v2/service/signin/serializers.go−724 vendor/github.com/aws/aws-sdk-go-v2/service/signin/types/enums.go−46 vendor/github.com/aws/aws-sdk-go-v2/service/signin/types/errors.go−247 vendor/github.com/aws/aws-sdk-go-v2/service/signin/types/types.go−162 vendor/github.com/aws/aws-sdk-go-v2/service/signin/validators.go−111 vendor/github.com/aws/aws-sdk-go-v2/service/sso/CHANGELOG.md−792 vendor/github.com/aws/aws-sdk-go-v2/service/sso/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_client.go−929 vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_op_GetRoleCredentials.go−165 vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_op_ListAccountRoles.go−263 vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_op_ListAccounts.go−261 vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_op_Logout.go−164 vendor/github.com/aws/aws-sdk-go-v2/service/sso/auth.go−379 vendor/github.com/aws/aws-sdk-go-v2/service/sso/deserializers.go−1171 vendor/github.com/aws/aws-sdk-go-v2/service/sso/doc.go−27 vendor/github.com/aws/aws-sdk-go-v2/service/sso/endpoints.go−570 vendor/github.com/aws/aws-sdk-go-v2/service/sso/generated.json−35 vendor/github.com/aws/aws-sdk-go-v2/service/sso/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/service/sso/internal/endpoints/endpoints.go−628 vendor/github.com/aws/aws-sdk-go-v2/service/sso/options.go−238 vendor/github.com/aws/aws-sdk-go-v2/service/sso/serializers.go−309 vendor/github.com/aws/aws-sdk-go-v2/service/sso/types/errors.go−115 vendor/github.com/aws/aws-sdk-go-v2/service/sso/types/types.go−63 vendor/github.com/aws/aws-sdk-go-v2/service/sso/validators.go−175 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/CHANGELOG.md−790 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_client.go−929 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_op_CreateToken.go−235 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_op_CreateTokenWithIAM.go−282 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_op_RegisterClient.go−206 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_op_StartDeviceAuthorization.go−188 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/auth.go−373 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/deserializers.go−2244 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/doc.go−49 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/endpoints.go−570 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/generated.json−36 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/internal/endpoints/endpoints.go−628 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/options.go−238 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/serializers.go−512 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/types/enums.go−44 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/types/errors.go−430 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/types/types.go−25 vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/validators.go−184 vendor/github.com/aws/aws-sdk-go-v2/service/sts/CHANGELOG.md−832 vendor/github.com/aws/aws-sdk-go-v2/service/sts/LICENSE.txt−202 vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_client.go−1115 vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_AssumeRole.go−544 vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_AssumeRoleWithSAML.go−455 vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_AssumeRoleWithWebIdentity.go−472 vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_AssumeRoot.go−229 vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_DecodeAuthorizationMessage.go−189 vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetAccessKeyInfo.go−180 vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetCallerIdentity.go−192 vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetDelegatedAccessToken.go−169 vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetFederationToken.go−393 vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetSessionToken.go−239 vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetWebIdentityToken.go−192 vendor/github.com/aws/aws-sdk-go-v2/service/sts/auth.go−377 vendor/github.com/aws/aws-sdk-go-v2/service/sts/deserializers.go−3456 vendor/github.com/aws/aws-sdk-go-v2/service/sts/doc.go−13 vendor/github.com/aws/aws-sdk-go-v2/service/sts/endpoints.go−683 vendor/github.com/aws/aws-sdk-go-v2/service/sts/generated.json−45 vendor/github.com/aws/aws-sdk-go-v2/service/sts/go_module_metadata.go−6 vendor/github.com/aws/aws-sdk-go-v2/service/sts/internal/endpoints/endpoints.go−585 vendor/github.com/aws/aws-sdk-go-v2/service/sts/options.go−286 vendor/github.com/aws/aws-sdk-go-v2/service/sts/serializers.go−1198 vendor/github.com/aws/aws-sdk-go-v2/service/sts/types/errors.go−363 vendor/github.com/aws/aws-sdk-go-v2/service/sts/types/types.go−144 vendor/github.com/aws/aws-sdk-go-v2/service/sts/validators.go−433 vendor/github.com/aws/smithy-go/.gitignore−29 vendor/github.com/aws/smithy-go/.travis.yml−28 vendor/github.com/aws/smithy-go/AGENTS.md−172 vendor/github.com/aws/smithy-go/CHANGELOG.md−454 vendor/github.com/aws/smithy-go/CODE_OF_CONDUCT.md−4 vendor/github.com/aws/smithy-go/CONTRIBUTING.md−90 vendor/github.com/aws/smithy-go/LICENSE−175 vendor/github.com/aws/smithy-go/Makefile−130 vendor/github.com/aws/smithy-go/NOTICE−1 vendor/github.com/aws/smithy-go/README.md−107 vendor/github.com/aws/smithy-go/auth/auth.go−3 vendor/github.com/aws/smithy-go/auth/bearer/docs.go−3 vendor/github.com/aws/smithy-go/auth/bearer/middleware.go−104 vendor/github.com/aws/smithy-go/auth/bearer/token.go−50 vendor/github.com/aws/smithy-go/auth/bearer/token_cache.go−208 vendor/github.com/aws/smithy-go/auth/identity.go−47 vendor/github.com/aws/smithy-go/auth/option.go−25 vendor/github.com/aws/smithy-go/auth/scheme_id.go−20 vendor/github.com/aws/smithy-go/changelog-template.json−9 vendor/github.com/aws/smithy-go/context/suppress_expired.go−81 vendor/github.com/aws/smithy-go/doc.go−2 vendor/github.com/aws/smithy-go/document.go−10 vendor/github.com/aws/smithy-go/document/doc.go−12 vendor/github.com/aws/smithy-go/document/document.go−239 vendor/github.com/aws/smithy-go/document/errors.go−75 vendor/github.com/aws/smithy-go/encoding/doc.go−4 vendor/github.com/aws/smithy-go/encoding/encoding.go−40 vendor/github.com/aws/smithy-go/encoding/httpbinding/encode.go−123 vendor/github.com/aws/smithy-go/encoding/httpbinding/header.go−122 vendor/github.com/aws/smithy-go/encoding/httpbinding/path_replace.go−108 vendor/github.com/aws/smithy-go/encoding/httpbinding/query.go−107 vendor/github.com/aws/smithy-go/encoding/httpbinding/uri.go−111 vendor/github.com/aws/smithy-go/encoding/json/array.go−35 vendor/github.com/aws/smithy-go/encoding/json/constants.go−15 vendor/github.com/aws/smithy-go/encoding/json/decoder_util.go−139 vendor/github.com/aws/smithy-go/encoding/json/encoder.go−30 vendor/github.com/aws/smithy-go/encoding/json/escape.go−198 vendor/github.com/aws/smithy-go/encoding/json/object.go−40 vendor/github.com/aws/smithy-go/encoding/json/value.go−154 vendor/github.com/aws/smithy-go/encoding/xml/array.go−49 vendor/github.com/aws/smithy-go/encoding/xml/constants.go−10 vendor/github.com/aws/smithy-go/encoding/xml/doc.go−49 vendor/github.com/aws/smithy-go/encoding/xml/element.go−91 vendor/github.com/aws/smithy-go/encoding/xml/encoder.go−51 vendor/github.com/aws/smithy-go/encoding/xml/error_utils.go−51 vendor/github.com/aws/smithy-go/encoding/xml/escape.go−137 vendor/github.com/aws/smithy-go/encoding/xml/map.go−53 vendor/github.com/aws/smithy-go/encoding/xml/value.go−302 vendor/github.com/aws/smithy-go/encoding/xml/xml_decoder.go−154 vendor/github.com/aws/smithy-go/endpoints/endpoint.go−23 vendor/github.com/aws/smithy-go/endpoints/private/bdd/evaluate.go−35 vendor/github.com/aws/smithy-go/endpoints/private/rulesfn/doc.go−4 vendor/github.com/aws/smithy-go/endpoints/private/rulesfn/split.go−16 vendor/github.com/aws/smithy-go/endpoints/private/rulesfn/string_slice.go−18 vendor/github.com/aws/smithy-go/endpoints/private/rulesfn/strings.go−25 vendor/github.com/aws/smithy-go/endpoints/private/rulesfn/uri.go−133 vendor/github.com/aws/smithy-go/errors.go−137 vendor/github.com/aws/smithy-go/eventstream/const.go−24 vendor/github.com/aws/smithy-go/eventstream/debug.go−144 vendor/github.com/aws/smithy-go/eventstream/decode.go−218 vendor/github.com/aws/smithy-go/eventstream/deserializer.go−294 vendor/github.com/aws/smithy-go/eventstream/encode.go−167 vendor/github.com/aws/smithy-go/eventstream/error.go−23 vendor/github.com/aws/smithy-go/eventstream/header.go−175 vendor/github.com/aws/smithy-go/eventstream/header_value.go−521 vendor/github.com/aws/smithy-go/eventstream/message.go−99 vendor/github.com/aws/smithy-go/eventstream/serializer.go−228 vendor/github.com/aws/smithy-go/eventstream/signer.go−82 vendor/github.com/aws/smithy-go/eventstream/types.go−26 vendor/github.com/aws/smithy-go/go_module_metadata.go−6 vendor/github.com/aws/smithy-go/internal/sync/singleflight/LICENSE−28 vendor/github.com/aws/smithy-go/internal/sync/singleflight/docs.go−8 vendor/github.com/aws/smithy-go/internal/sync/singleflight/singleflight.go−210 vendor/github.com/aws/smithy-go/io/byte.go−12 vendor/github.com/aws/smithy-go/io/doc.go−2 vendor/github.com/aws/smithy-go/io/reader.go−16 vendor/github.com/aws/smithy-go/io/ringbuffer.go−94 vendor/github.com/aws/smithy-go/local-mod-replace.sh−39 vendor/github.com/aws/smithy-go/logging/logger.go−82 vendor/github.com/aws/smithy-go/metrics/metrics.go−136 vendor/github.com/aws/smithy-go/metrics/nop.go−98 vendor/github.com/aws/smithy-go/middleware/context.go−41 vendor/github.com/aws/smithy-go/middleware/doc.go−67 vendor/github.com/aws/smithy-go/middleware/eventstream_middleware.go−21 vendor/github.com/aws/smithy-go/middleware/logging.go−46 vendor/github.com/aws/smithy-go/middleware/metadata.go−65 vendor/github.com/aws/smithy-go/middleware/middleware.go−71 vendor/github.com/aws/smithy-go/middleware/ordered_group.go−270 vendor/github.com/aws/smithy-go/middleware/stack.go−209 vendor/github.com/aws/smithy-go/middleware/stack_values.go−100 vendor/github.com/aws/smithy-go/middleware/step_build.go−298 vendor/github.com/aws/smithy-go/middleware/step_deserialize.go−300 vendor/github.com/aws/smithy-go/middleware/step_finalize.go−298 vendor/github.com/aws/smithy-go/middleware/step_initialize.go−299 vendor/github.com/aws/smithy-go/middleware/step_serialize.go−305 vendor/github.com/aws/smithy-go/modman.toml−9 vendor/github.com/aws/smithy-go/private/requestcompression/gzip.go−30 vendor/github.com/aws/smithy-go/private/requestcompression/middleware_capture_request_compression.go−52 vendor/github.com/aws/smithy-go/private/requestcompression/request_compression.go−103 vendor/github.com/aws/smithy-go/properties.go−69 vendor/github.com/aws/smithy-go/ptr/doc.go−5 vendor/github.com/aws/smithy-go/ptr/from_ptr.go−601 vendor/github.com/aws/smithy-go/ptr/gen_scalars.go−83 vendor/github.com/aws/smithy-go/ptr/to_ptr.go−499 vendor/github.com/aws/smithy-go/rand/doc.go−3 vendor/github.com/aws/smithy-go/rand/rand.go−31 vendor/github.com/aws/smithy-go/rand/uuid.go−87 vendor/github.com/aws/smithy-go/schema.go−328 vendor/github.com/aws/smithy-go/schema_ext.go−37 vendor/github.com/aws/smithy-go/serde.go−229 vendor/github.com/aws/smithy-go/sync/error.go−53 vendor/github.com/aws/smithy-go/time/time.go−134 vendor/github.com/aws/smithy-go/tracing/context.go−96 vendor/github.com/aws/smithy-go/tracing/nop.go−32 vendor/github.com/aws/smithy-go/tracing/tracing.go−95 vendor/github.com/aws/smithy-go/trait.go−21 vendor/github.com/aws/smithy-go/traits/http.go−69 vendor/github.com/aws/smithy-go/traits/index.go−107 vendor/github.com/aws/smithy-go/traits/serde.go−56 vendor/github.com/aws/smithy-go/traits/traits.go−72 vendor/github.com/aws/smithy-go/transport/http/auth.go−30 vendor/github.com/aws/smithy-go/transport/http/auth_schemes.go−45 vendor/github.com/aws/smithy-go/transport/http/checksum_middleware.go−70 vendor/github.com/aws/smithy-go/transport/http/client.go−161 vendor/github.com/aws/smithy-go/transport/http/doc.go−5 vendor/github.com/aws/smithy-go/transport/http/eventstream.go−209 vendor/github.com/aws/smithy-go/transport/http/eventstream_middleware.go−69 vendor/github.com/aws/smithy-go/transport/http/headerlist.go−163 vendor/github.com/aws/smithy-go/transport/http/host.go−89 vendor/github.com/aws/smithy-go/transport/http/interceptor.go−321 vendor/github.com/aws/smithy-go/transport/http/interceptor_middleware.go−325 vendor/github.com/aws/smithy-go/transport/http/internal/io/safe.go−75 vendor/github.com/aws/smithy-go/transport/http/md5_checksum.go−25 vendor/github.com/aws/smithy-go/transport/http/metrics.go−204 vendor/github.com/aws/smithy-go/transport/http/middleware_close_response_body.go−79 vendor/github.com/aws/smithy-go/transport/http/middleware_content_length.go−84 vendor/github.com/aws/smithy-go/transport/http/middleware_header_comment.go−81 vendor/github.com/aws/smithy-go/transport/http/middleware_headers.go−167 vendor/github.com/aws/smithy-go/transport/http/middleware_http_logging.go−75 vendor/github.com/aws/smithy-go/transport/http/middleware_metadata.go−51 vendor/github.com/aws/smithy-go/transport/http/middleware_min_proto.go−79 vendor/github.com/aws/smithy-go/transport/http/properties.go−80 vendor/github.com/aws/smithy-go/transport/http/protocol.go−27 vendor/github.com/aws/smithy-go/transport/http/request.go−188 vendor/github.com/aws/smithy-go/transport/http/response.go−34 vendor/github.com/aws/smithy-go/transport/http/time.go−13 vendor/github.com/aws/smithy-go/transport/http/url.go−44 vendor/github.com/aws/smithy-go/transport/http/user_agent.go−37 vendor/github.com/aws/smithy-go/type_registry.go−70 vendor/github.com/aws/smithy-go/validation.go−140 vendor/github.com/aymanbagabas/go-udiff/LICENSE-BSD−27 vendor/github.com/aymanbagabas/go-udiff/LICENSE-MIT−21 vendor/github.com/aymanbagabas/go-udiff/Makefile−4 vendor/github.com/aymanbagabas/go-udiff/README.md−142 vendor/github.com/aymanbagabas/go-udiff/diff.go−177 vendor/github.com/aymanbagabas/go-udiff/export.go−16 vendor/github.com/aymanbagabas/go-udiff/lcs/common.go−179 vendor/github.com/aymanbagabas/go-udiff/lcs/doc.go−156 vendor/github.com/aymanbagabas/go-udiff/lcs/export.go−11 vendor/github.com/aymanbagabas/go-udiff/lcs/git.sh−33 vendor/github.com/aymanbagabas/go-udiff/lcs/labels.go−55 vendor/github.com/aymanbagabas/go-udiff/lcs/old.go−475 vendor/github.com/aymanbagabas/go-udiff/lcs/sequence.go−70 vendor/github.com/aymanbagabas/go-udiff/merge.go−81 vendor/github.com/aymanbagabas/go-udiff/ndiff.go−118 vendor/github.com/aymanbagabas/go-udiff/unified.go−314 vendor/github.com/aymerick/douceur/LICENSE−22 vendor/github.com/aymerick/douceur/css/declaration.go−60 vendor/github.com/aymerick/douceur/css/rule.go−230 vendor/github.com/aymerick/douceur/css/stylesheet.go−25 vendor/github.com/aymerick/douceur/parser/parser.go−409 vendor/github.com/bahlo/generic-list-go/LICENSE−27 vendor/github.com/bahlo/generic-list-go/README.md−5 vendor/github.com/bahlo/generic-list-go/list.go−235 vendor/github.com/bmatcuk/doublestar/v4/.codecov.yml−10 vendor/github.com/bmatcuk/doublestar/v4/.gitignore−32 vendor/github.com/bmatcuk/doublestar/v4/LICENSE−22 vendor/github.com/bmatcuk/doublestar/v4/README.md−495 vendor/github.com/bmatcuk/doublestar/v4/UPGRADING.md−63 vendor/github.com/bmatcuk/doublestar/v4/doublestar.go−13 vendor/github.com/bmatcuk/doublestar/v4/glob.go−497 vendor/github.com/bmatcuk/doublestar/v4/globoptions.go−188 vendor/github.com/bmatcuk/doublestar/v4/globwalk.go−438 vendor/github.com/bmatcuk/doublestar/v4/match.go−419 vendor/github.com/bmatcuk/doublestar/v4/utils.go−165 vendor/github.com/bmatcuk/doublestar/v4/utils_unix.go−19 vendor/github.com/bmatcuk/doublestar/v4/utils_windows.go−43 vendor/github.com/bmatcuk/doublestar/v4/validate.go−80 vendor/github.com/buger/jsonparser/.gitignore−12 vendor/github.com/buger/jsonparser/.travis.yml−12 vendor/github.com/buger/jsonparser/Dockerfile−12 vendor/github.com/buger/jsonparser/LICENSE−21 vendor/github.com/buger/jsonparser/Makefile−36 vendor/github.com/buger/jsonparser/README.md−361 vendor/github.com/buger/jsonparser/bytes.go−47 vendor/github.com/buger/jsonparser/bytes_safe.go−25 vendor/github.com/buger/jsonparser/bytes_unsafe.go−44 vendor/github.com/buger/jsonparser/escape.go−173 vendor/github.com/buger/jsonparser/fuzz.go−117 vendor/github.com/buger/jsonparser/oss-fuzz-build.sh−47 vendor/github.com/buger/jsonparser/parser.go−1318 vendor/github.com/cespare/xxhash/v2/LICENSE.txt−22 vendor/github.com/cespare/xxhash/v2/README.md−74 vendor/github.com/cespare/xxhash/v2/testall.sh−10 vendor/github.com/cespare/xxhash/v2/xxhash.go−243 vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s−209 vendor/github.com/cespare/xxhash/v2/xxhash_arm64.s−183 vendor/github.com/cespare/xxhash/v2/xxhash_asm.go−15 vendor/github.com/cespare/xxhash/v2/xxhash_other.go−76 vendor/github.com/cespare/xxhash/v2/xxhash_safe.go−16 vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go−58 vendor/github.com/charlievieth/fastwalk/.gitignore−2 vendor/github.com/charlievieth/fastwalk/LICENSE−21 vendor/github.com/charlievieth/fastwalk/Makefile−101 vendor/github.com/charlievieth/fastwalk/README.md−193 vendor/github.com/charlievieth/fastwalk/adapters.go−102 vendor/github.com/charlievieth/fastwalk/dirent.go−73 vendor/github.com/charlievieth/fastwalk/dirent_portable.go−134 vendor/github.com/charlievieth/fastwalk/dirent_unix.go−146 vendor/github.com/charlievieth/fastwalk/entry_filter_portable.go−37 vendor/github.com/charlievieth/fastwalk/entry_filter_unix.go−59 vendor/github.com/charlievieth/fastwalk/entry_filter_windows.go−156 vendor/github.com/charlievieth/fastwalk/fastwalk.go−697 vendor/github.com/charlievieth/fastwalk/fastwalk_darwin.go−122 vendor/github.com/charlievieth/fastwalk/fastwalk_portable.go−65 vendor/github.com/charlievieth/fastwalk/fastwalk_unix.go−131 vendor/github.com/charlievieth/fastwalk/go.LICENSE−27 vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent.go−85 vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_aix.go−29 vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_dragonfly.go−54 vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_freebsd.go−46 vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_js.go−27 vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_linux.go−50 vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_netbsd.go−46 vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_openbsd.go−46 vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_solaris.go−29 vendor/github.com/charlievieth/fastwalk/internal/dirent/doc.go−2 vendor/github.com/charlievieth/fastwalk/internal/fmtdirent/fmtdirent_go120.go−31 vendor/github.com/charlievieth/fastwalk/internal/fmtdirent/fmtdirent_go121.go−15 vendor/github.com/charlievieth/fastwalk/zsyscall_darwin.go−93 vendor/github.com/charlievieth/fastwalk/zsyscall_darwin_amd64.1_13.s−22 vendor/github.com/charlievieth/fastwalk/zsyscall_darwin_arm64.1_13.s−22 vendor/github.com/charmbracelet/anthropic-sdk-go/.gitignore−4 vendor/github.com/charmbracelet/anthropic-sdk-go/.release-please-manifest.json−3 vendor/github.com/charmbracelet/anthropic-sdk-go/.stats.yml−4 vendor/github.com/charmbracelet/anthropic-sdk-go/Brewfile−1 vendor/github.com/charmbracelet/anthropic-sdk-go/CHANGELOG.md−641 vendor/github.com/charmbracelet/anthropic-sdk-go/CONTRIBUTING.md−65 vendor/github.com/charmbracelet/anthropic-sdk-go/LICENSE−8 vendor/github.com/charmbracelet/anthropic-sdk-go/README.md−999 vendor/github.com/charmbracelet/anthropic-sdk-go/SECURITY.md−27 vendor/github.com/charmbracelet/anthropic-sdk-go/aliases.go−72 vendor/github.com/charmbracelet/anthropic-sdk-go/api.md−545 vendor/github.com/charmbracelet/anthropic-sdk-go/bedrock/bedrock.go−295 vendor/github.com/charmbracelet/anthropic-sdk-go/beta.go−374 vendor/github.com/charmbracelet/anthropic-sdk-go/betafile.go−273 vendor/github.com/charmbracelet/anthropic-sdk-go/betamessage.go−11734 vendor/github.com/charmbracelet/anthropic-sdk-go/betamessagebatch.go−925 vendor/github.com/charmbracelet/anthropic-sdk-go/betamessageutil.go−459 vendor/github.com/charmbracelet/anthropic-sdk-go/betamodel.go−151 vendor/github.com/charmbracelet/anthropic-sdk-go/betaskill.go−362 vendor/github.com/charmbracelet/anthropic-sdk-go/betaskillversion.go−373 vendor/github.com/charmbracelet/anthropic-sdk-go/betatoolrunner.go−435 vendor/github.com/charmbracelet/anthropic-sdk-go/client.go−157 vendor/github.com/charmbracelet/anthropic-sdk-go/completion.go−196 vendor/github.com/charmbracelet/anthropic-sdk-go/field.go−46 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apierror/apierror.go−58 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiform/encoder.go−473 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiform/form.go−5 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiform/richparam.go−21 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiform/tag.go−51 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/decoder.go−709 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/encoder.go−392 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/enum.go−145 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/field.go−23 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/port.go−120 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/registry.go−59 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/subfield.go−68 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/tag.go−47 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/union.go−209 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiquery/encoder.go−414 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiquery/query.go−55 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiquery/richparam.go−20 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiquery/tag.go−44 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/decode.go−1325 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/encode.go−1396 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/fold.go−48 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/indent.go−182 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/scanner.go−610 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/sentinel/null.go−47 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/shims/shims.go−113 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/stream.go−512 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/tables.go−218 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/tags.go−38 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/time.go−62 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/paramutil/field.go−30 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/paramutil/union.go−49 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/requestconfig/requestconfig.go−638 vendor/github.com/charmbracelet/anthropic-sdk-go/internal/version.go−5 vendor/github.com/charmbracelet/anthropic-sdk-go/message.go−9436 vendor/github.com/charmbracelet/anthropic-sdk-go/messagebatch.go−827 vendor/github.com/charmbracelet/anthropic-sdk-go/messageutil.go−413 vendor/github.com/charmbracelet/anthropic-sdk-go/model.go−151 vendor/github.com/charmbracelet/anthropic-sdk-go/option/middleware.go−38 vendor/github.com/charmbracelet/anthropic-sdk-go/option/requestoption.go−285 vendor/github.com/charmbracelet/anthropic-sdk-go/packages/jsonl/jsonl.go−62 vendor/github.com/charmbracelet/anthropic-sdk-go/packages/pagination/pagination.go−351 vendor/github.com/charmbracelet/anthropic-sdk-go/packages/param/encoder.go−109 vendor/github.com/charmbracelet/anthropic-sdk-go/packages/param/null.go−19 vendor/github.com/charmbracelet/anthropic-sdk-go/packages/param/option.go−122 vendor/github.com/charmbracelet/anthropic-sdk-go/packages/param/param.go−189 vendor/github.com/charmbracelet/anthropic-sdk-go/packages/respjson/respjson.go−88 vendor/github.com/charmbracelet/anthropic-sdk-go/packages/ssestream/ssestream.go−198 vendor/github.com/charmbracelet/anthropic-sdk-go/release-please-config.json−68 vendor/github.com/charmbracelet/anthropic-sdk-go/schemautil.go−244 vendor/github.com/charmbracelet/anthropic-sdk-go/shared/constant/constants.go−487 vendor/github.com/charmbracelet/anthropic-sdk-go/shared/shared.go−346 vendor/github.com/charmbracelet/anthropic-sdk-go/tools.md−292 vendor/github.com/charmbracelet/anthropic-sdk-go/vertex/vertex.go−134 vendor/github.com/charmbracelet/colorprofile/.golangci.yml−47 vendor/github.com/charmbracelet/colorprofile/.goreleaser.yml−6 vendor/github.com/charmbracelet/colorprofile/LICENSE−21 vendor/github.com/charmbracelet/colorprofile/README.md−109 vendor/github.com/charmbracelet/colorprofile/doc.go−4 vendor/github.com/charmbracelet/colorprofile/env.go−294 vendor/github.com/charmbracelet/colorprofile/env_other.go−8 vendor/github.com/charmbracelet/colorprofile/env_windows.go−40 vendor/github.com/charmbracelet/colorprofile/profile.go−108 vendor/github.com/charmbracelet/colorprofile/writer.go−171 vendor/github.com/charmbracelet/openai-go/.gitignore−4 vendor/github.com/charmbracelet/openai-go/.release-please-manifest.json−3 vendor/github.com/charmbracelet/openai-go/.stats.yml−4 vendor/github.com/charmbracelet/openai-go/Brewfile−1 vendor/github.com/charmbracelet/openai-go/CHANGELOG.md−1171 vendor/github.com/charmbracelet/openai-go/CONTRIBUTING.md−65 vendor/github.com/charmbracelet/openai-go/LICENSE−201 vendor/github.com/charmbracelet/openai-go/MIGRATION.md−284 vendor/github.com/charmbracelet/openai-go/README.md−1007 vendor/github.com/charmbracelet/openai-go/SECURITY.md−29 vendor/github.com/charmbracelet/openai-go/aliases.go−585 vendor/github.com/charmbracelet/openai-go/api.md−774 vendor/github.com/charmbracelet/openai-go/audio.go−61 vendor/github.com/charmbracelet/openai-go/audiospeech.go−179 vendor/github.com/charmbracelet/openai-go/audiotranscription.go−950 vendor/github.com/charmbracelet/openai-go/audiotranslation.go−120 vendor/github.com/charmbracelet/openai-go/azure/azure.go−269 vendor/github.com/charmbracelet/openai-go/batch.go−464 vendor/github.com/charmbracelet/openai-go/beta.go−36 vendor/github.com/charmbracelet/openai-go/betaassistant.go−2284 vendor/github.com/charmbracelet/openai-go/betachatkit.go−125 vendor/github.com/charmbracelet/openai-go/betachatkitsession.go−83 vendor/github.com/charmbracelet/openai-go/betachatkitthread.go−1564 vendor/github.com/charmbracelet/openai-go/betathread.go−1574 vendor/github.com/charmbracelet/openai-go/betathreadmessage.go−1717 vendor/github.com/charmbracelet/openai-go/betathreadrun.go−974 vendor/github.com/charmbracelet/openai-go/betathreadrunstep.go−1396 vendor/github.com/charmbracelet/openai-go/chat.go−30 vendor/github.com/charmbracelet/openai-go/chatcompletion.go−3521 vendor/github.com/charmbracelet/openai-go/chatcompletionmessage.go−100 vendor/github.com/charmbracelet/openai-go/client.go−185 vendor/github.com/charmbracelet/openai-go/completion.go−436 vendor/github.com/charmbracelet/openai-go/container.go−648 vendor/github.com/charmbracelet/openai-go/containerfile.go−287 vendor/github.com/charmbracelet/openai-go/containerfilecontent.go−50 vendor/github.com/charmbracelet/openai-go/conversations/aliases.go−585 vendor/github.com/charmbracelet/openai-go/conversations/api.md−31 vendor/github.com/charmbracelet/openai-go/conversations/conversation.go−493 vendor/github.com/charmbracelet/openai-go/conversations/item.go−1082 vendor/github.com/charmbracelet/openai-go/embedding.go−207 vendor/github.com/charmbracelet/openai-go/field.go−45 vendor/github.com/charmbracelet/openai-go/file.go−352 vendor/github.com/charmbracelet/openai-go/finetuning.go−35 vendor/github.com/charmbracelet/openai-go/finetuningalpha.go−29 vendor/github.com/charmbracelet/openai-go/finetuningalphagrader.go−675 vendor/github.com/charmbracelet/openai-go/finetuningcheckpoint.go−29 vendor/github.com/charmbracelet/openai-go/finetuningcheckpointpermission.go−352 vendor/github.com/charmbracelet/openai-go/finetuningjob.go−884 vendor/github.com/charmbracelet/openai-go/finetuningjobcheckpoint.go−152 vendor/github.com/charmbracelet/openai-go/finetuningmethod.go−1487 vendor/github.com/charmbracelet/openai-go/grader.go−28 vendor/github.com/charmbracelet/openai-go/gradergradermodel.go−1753 vendor/github.com/charmbracelet/openai-go/image.go−1337 vendor/github.com/charmbracelet/openai-go/internal/apierror/apierror.go−58 vendor/github.com/charmbracelet/openai-go/internal/apiform/encoder.go−473 vendor/github.com/charmbracelet/openai-go/internal/apiform/form.go−5 vendor/github.com/charmbracelet/openai-go/internal/apiform/richparam.go−20 vendor/github.com/charmbracelet/openai-go/internal/apiform/tag.go−72 vendor/github.com/charmbracelet/openai-go/internal/apijson/decoder.go−688 vendor/github.com/charmbracelet/openai-go/internal/apijson/encoder.go−371 vendor/github.com/charmbracelet/openai-go/internal/apijson/enum.go−140 vendor/github.com/charmbracelet/openai-go/internal/apijson/field.go−23 vendor/github.com/charmbracelet/openai-go/internal/apijson/port.go−120 vendor/github.com/charmbracelet/openai-go/internal/apijson/registry.go−51 vendor/github.com/charmbracelet/openai-go/internal/apijson/subfield.go−67 vendor/github.com/charmbracelet/openai-go/internal/apijson/tag.go−69 vendor/github.com/charmbracelet/openai-go/internal/apijson/union.go−208 vendor/github.com/charmbracelet/openai-go/internal/apiquery/encoder.go−394 vendor/github.com/charmbracelet/openai-go/internal/apiquery/query.go−55 vendor/github.com/charmbracelet/openai-go/internal/apiquery/richparam.go−20 vendor/github.com/charmbracelet/openai-go/internal/apiquery/tag.go−44 vendor/github.com/charmbracelet/openai-go/internal/encoding/json/decode.go−1324 vendor/github.com/charmbracelet/openai-go/internal/encoding/json/encode.go−1396 vendor/github.com/charmbracelet/openai-go/internal/encoding/json/fold.go−48 vendor/github.com/charmbracelet/openai-go/internal/encoding/json/indent.go−182 vendor/github.com/charmbracelet/openai-go/internal/encoding/json/scanner.go−610 vendor/github.com/charmbracelet/openai-go/internal/encoding/json/sentinel/null.go−46 vendor/github.com/charmbracelet/openai-go/internal/encoding/json/shims/shims.go−113 vendor/github.com/charmbracelet/openai-go/internal/encoding/json/stream.go−512 vendor/github.com/charmbracelet/openai-go/internal/encoding/json/tables.go−218 vendor/github.com/charmbracelet/openai-go/internal/encoding/json/tags.go−38 vendor/github.com/charmbracelet/openai-go/internal/encoding/json/time.go−61 vendor/github.com/charmbracelet/openai-go/internal/paramutil/field.go−30 vendor/github.com/charmbracelet/openai-go/internal/paramutil/union.go−48 vendor/github.com/charmbracelet/openai-go/internal/requestconfig/requestconfig.go−644 vendor/github.com/charmbracelet/openai-go/internal/version.go−5 vendor/github.com/charmbracelet/openai-go/model.go−137 vendor/github.com/charmbracelet/openai-go/moderation.go−489 vendor/github.com/charmbracelet/openai-go/option/middleware.go−38 vendor/github.com/charmbracelet/openai-go/option/requestoption.go−299 vendor/github.com/charmbracelet/openai-go/packages/pagination/pagination.go−329 vendor/github.com/charmbracelet/openai-go/packages/param/encoder.go−109 vendor/github.com/charmbracelet/openai-go/packages/param/null.go−19 vendor/github.com/charmbracelet/openai-go/packages/param/option.go−122 vendor/github.com/charmbracelet/openai-go/packages/param/param.go−186 vendor/github.com/charmbracelet/openai-go/packages/respjson/respjson.go−88 vendor/github.com/charmbracelet/openai-go/packages/ssestream/ssestream.go−247 vendor/github.com/charmbracelet/openai-go/polling.go−129 vendor/github.com/charmbracelet/openai-go/realtime/aliases.go−585 vendor/github.com/charmbracelet/openai-go/realtime/api.md−55 vendor/github.com/charmbracelet/openai-go/realtime/call.go−133 vendor/github.com/charmbracelet/openai-go/realtime/clientsecret.go−1826 vendor/github.com/charmbracelet/openai-go/realtime/realtime.go−1979 vendor/github.com/charmbracelet/openai-go/release-please-config.json−68 vendor/github.com/charmbracelet/openai-go/responses/aliases.go−585 vendor/github.com/charmbracelet/openai-go/responses/api.md−240 vendor/github.com/charmbracelet/openai-go/responses/inputitem.go−135 vendor/github.com/charmbracelet/openai-go/responses/inputtoken.go−336 vendor/github.com/charmbracelet/openai-go/responses/response.go−21786 vendor/github.com/charmbracelet/openai-go/shared/constant/constants.go−1143 vendor/github.com/charmbracelet/openai-go/shared/shared.go−1183 vendor/github.com/charmbracelet/openai-go/skill.go−292 vendor/github.com/charmbracelet/openai-go/skillcontent.go−46 vendor/github.com/charmbracelet/openai-go/skillversion.go−283 vendor/github.com/charmbracelet/openai-go/skillversioncontent.go−50 vendor/github.com/charmbracelet/openai-go/streamaccumulator.go−215 vendor/github.com/charmbracelet/openai-go/upload.go−238 vendor/github.com/charmbracelet/openai-go/uploadpart.go−115 vendor/github.com/charmbracelet/openai-go/vectorstore.go−911 vendor/github.com/charmbracelet/openai-go/vectorstorefile.go−535 vendor/github.com/charmbracelet/openai-go/vectorstorefilebatch.go−427 vendor/github.com/charmbracelet/openai-go/video.go−677 vendor/github.com/charmbracelet/openai-go/webhooks/aliases.go−585 vendor/github.com/charmbracelet/openai-go/webhooks/api.md−20 vendor/github.com/charmbracelet/openai-go/webhooks/webhook.go−1306 vendor/github.com/charmbracelet/ultraviolet/.gitattributes−1 vendor/github.com/charmbracelet/ultraviolet/.golangci.yml−48 vendor/github.com/charmbracelet/ultraviolet/.goreleaser.yml−5 vendor/github.com/charmbracelet/ultraviolet/LICENSE−21 vendor/github.com/charmbracelet/ultraviolet/README.md−161 vendor/github.com/charmbracelet/ultraviolet/TUTORIAL.md−175 vendor/github.com/charmbracelet/ultraviolet/border.go−231 vendor/github.com/charmbracelet/ultraviolet/buffer.go−831 vendor/github.com/charmbracelet/ultraviolet/cancelreader_other.go−16 vendor/github.com/charmbracelet/ultraviolet/cancelreader_windows.go−139 vendor/github.com/charmbracelet/ultraviolet/cell.go−465 vendor/github.com/charmbracelet/ultraviolet/console.go−219 vendor/github.com/charmbracelet/ultraviolet/console_unix.go−13 vendor/github.com/charmbracelet/ultraviolet/console_windows.go−13 vendor/github.com/charmbracelet/ultraviolet/cursor.go−22 vendor/github.com/charmbracelet/ultraviolet/decoder.go−2102 vendor/github.com/charmbracelet/ultraviolet/doc.go−46 vendor/github.com/charmbracelet/ultraviolet/environ.go−32 vendor/github.com/charmbracelet/ultraviolet/event.go−567 vendor/github.com/charmbracelet/ultraviolet/internal/casso/math.go−172 vendor/github.com/charmbracelet/ultraviolet/internal/casso/solver.go−275 vendor/github.com/charmbracelet/ultraviolet/internal/lru/lru.go−69 vendor/github.com/charmbracelet/ultraviolet/key.go−762 vendor/github.com/charmbracelet/ultraviolet/key_table.go−664 vendor/github.com/charmbracelet/ultraviolet/layout/cache.go−29 vendor/github.com/charmbracelet/ultraviolet/layout/constraint.go−174 vendor/github.com/charmbracelet/ultraviolet/layout/flex.go−210 vendor/github.com/charmbracelet/ultraviolet/layout/layout.go−973 vendor/github.com/charmbracelet/ultraviolet/layout/padding.go−66 vendor/github.com/charmbracelet/ultraviolet/logger.go−6 vendor/github.com/charmbracelet/ultraviolet/mouse.go−150 vendor/github.com/charmbracelet/ultraviolet/poll.go−38 vendor/github.com/charmbracelet/ultraviolet/poll_bsd.go−165 vendor/github.com/charmbracelet/ultraviolet/poll_default.go−12 vendor/github.com/charmbracelet/ultraviolet/poll_fallback.go−158 vendor/github.com/charmbracelet/ultraviolet/poll_linux.go−175 vendor/github.com/charmbracelet/ultraviolet/poll_select.go−156 vendor/github.com/charmbracelet/ultraviolet/poll_solaris.go−12 vendor/github.com/charmbracelet/ultraviolet/poll_windows.go−217 vendor/github.com/charmbracelet/ultraviolet/screen/context.go−379 vendor/github.com/charmbracelet/ultraviolet/screen/screen.go−117 vendor/github.com/charmbracelet/ultraviolet/styled.go−337 vendor/github.com/charmbracelet/ultraviolet/tabstop.go−142 vendor/github.com/charmbracelet/ultraviolet/terminal.go−394 vendor/github.com/charmbracelet/ultraviolet/terminal_bsdly.go−10 vendor/github.com/charmbracelet/ultraviolet/terminal_bsdly_other.go−8 vendor/github.com/charmbracelet/ultraviolet/terminal_other.go−22 vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go−503 vendor/github.com/charmbracelet/ultraviolet/terminal_reader_other.go−13 vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go−336 vendor/github.com/charmbracelet/ultraviolet/terminal_renderer.go−1639 vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hardscroll.go−243 vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hashmap.go−298 vendor/github.com/charmbracelet/ultraviolet/terminal_screen.go−837 vendor/github.com/charmbracelet/ultraviolet/terminal_tabdly.go−10 vendor/github.com/charmbracelet/ultraviolet/terminal_tabdly_other.go−8 vendor/github.com/charmbracelet/ultraviolet/terminal_unix.go−56 vendor/github.com/charmbracelet/ultraviolet/terminal_windows.go−77 vendor/github.com/charmbracelet/ultraviolet/tty.go−42 vendor/github.com/charmbracelet/ultraviolet/tty_other.go−26 vendor/github.com/charmbracelet/ultraviolet/tty_unix.go−39 vendor/github.com/charmbracelet/ultraviolet/tty_windows.go−39 vendor/github.com/charmbracelet/ultraviolet/utils.go−15 vendor/github.com/charmbracelet/ultraviolet/uv.go−406 vendor/github.com/charmbracelet/ultraviolet/winch.go−62 vendor/github.com/charmbracelet/ultraviolet/winch_other.go−17 vendor/github.com/charmbracelet/ultraviolet/winch_unix.go−50 vendor/github.com/charmbracelet/ultraviolet/window.go−147 vendor/github.com/charmbracelet/x/ansi/LICENSE−21 vendor/github.com/charmbracelet/x/ansi/ansi.go−11 vendor/github.com/charmbracelet/x/ansi/ascii.go−8 vendor/github.com/charmbracelet/x/ansi/background.go−178 vendor/github.com/charmbracelet/x/ansi/c0.go−79 vendor/github.com/charmbracelet/x/ansi/c1.go−72 vendor/github.com/charmbracelet/x/ansi/charset.go−55 vendor/github.com/charmbracelet/x/ansi/clipboard.go−75 vendor/github.com/charmbracelet/x/ansi/color.go−784 vendor/github.com/charmbracelet/x/ansi/ctrl.go−156 vendor/github.com/charmbracelet/x/ansi/cursor.go−637 vendor/github.com/charmbracelet/x/ansi/cwd.go−26 vendor/github.com/charmbracelet/x/ansi/doc.go−7 vendor/github.com/charmbracelet/x/ansi/finalterm.go−67 vendor/github.com/charmbracelet/x/ansi/focus.go−9 vendor/github.com/charmbracelet/x/ansi/graphics.go−62 vendor/github.com/charmbracelet/x/ansi/hyperlink.go−28 vendor/github.com/charmbracelet/x/ansi/inband.go−24 vendor/github.com/charmbracelet/x/ansi/iterm2.go−18 vendor/github.com/charmbracelet/x/ansi/keypad.go−28 vendor/github.com/charmbracelet/x/ansi/kitty.go−90 vendor/github.com/charmbracelet/x/ansi/kitty/decoder.go−86 vendor/github.com/charmbracelet/x/ansi/kitty/encoder.go−64 vendor/github.com/charmbracelet/x/ansi/kitty/graphics.go−414 vendor/github.com/charmbracelet/x/ansi/kitty/options.go−373 vendor/github.com/charmbracelet/x/ansi/kitty/writer.go−187 vendor/github.com/charmbracelet/x/ansi/method.go−196 vendor/github.com/charmbracelet/x/ansi/mode.go−690 vendor/github.com/charmbracelet/x/ansi/mode_deprecated.go−495 vendor/github.com/charmbracelet/x/ansi/modes.go−65 vendor/github.com/charmbracelet/x/ansi/mouse.go−172 vendor/github.com/charmbracelet/x/ansi/notification.go−32 vendor/github.com/charmbracelet/x/ansi/palette.go−34 vendor/github.com/charmbracelet/x/ansi/parser.go−417 vendor/github.com/charmbracelet/x/ansi/parser/const.go−79 vendor/github.com/charmbracelet/x/ansi/parser/seq.go−136 vendor/github.com/charmbracelet/x/ansi/parser/transition_table.go−273 vendor/github.com/charmbracelet/x/ansi/parser_decode.go−525 vendor/github.com/charmbracelet/x/ansi/parser_handler.go−60 vendor/github.com/charmbracelet/x/ansi/parser_sync.go−29 vendor/github.com/charmbracelet/x/ansi/passthrough.go−60 vendor/github.com/charmbracelet/x/ansi/paste.go−7 vendor/github.com/charmbracelet/x/ansi/progress.go−49 vendor/github.com/charmbracelet/x/ansi/reset.go−11 vendor/github.com/charmbracelet/x/ansi/screen.go−410 vendor/github.com/charmbracelet/x/ansi/sgr.go−79 vendor/github.com/charmbracelet/x/ansi/status.go−168 vendor/github.com/charmbracelet/x/ansi/style.go−819 vendor/github.com/charmbracelet/x/ansi/termcap.go−41 vendor/github.com/charmbracelet/x/ansi/title.go−48 vendor/github.com/charmbracelet/x/ansi/truncate.go−290 vendor/github.com/charmbracelet/x/ansi/urxvt.go−17 vendor/github.com/charmbracelet/x/ansi/util.go−92 vendor/github.com/charmbracelet/x/ansi/width.go−107 vendor/github.com/charmbracelet/x/ansi/winop.go−59 vendor/github.com/charmbracelet/x/ansi/wrap.go−472 vendor/github.com/charmbracelet/x/ansi/xterm.go−138 vendor/github.com/charmbracelet/x/editor/LICENSE−21 vendor/github.com/charmbracelet/x/editor/editor.go−140 vendor/github.com/charmbracelet/x/etag/LICENSE−21 vendor/github.com/charmbracelet/x/etag/etag.go−48 vendor/github.com/charmbracelet/x/exp/charmtone/LICENSE−21 vendor/github.com/charmbracelet/x/exp/charmtone/charmtone.go−443 vendor/github.com/charmbracelet/x/exp/golden/LICENSE−21 vendor/github.com/charmbracelet/x/exp/golden/golden.go−83 vendor/github.com/charmbracelet/x/exp/ordered/LICENSE−21 vendor/github.com/charmbracelet/x/exp/ordered/ordered.go−42 vendor/github.com/charmbracelet/x/exp/slice/LICENSE−21 vendor/github.com/charmbracelet/x/exp/slice/slice.go−148 vendor/github.com/charmbracelet/x/exp/strings/LICENSE−21 vendor/github.com/charmbracelet/x/exp/strings/contains.go−16 vendor/github.com/charmbracelet/x/exp/strings/join.go−134 vendor/github.com/charmbracelet/x/powernap/LICENSE−21 vendor/github.com/charmbracelet/x/powernap/pkg/config/config.go−118 vendor/github.com/charmbracelet/x/powernap/pkg/config/lsps.json−367 vendor/github.com/charmbracelet/x/powernap/pkg/config/overrides.go−85 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/client.go−875 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/language.go−158 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol.go−50 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/LICENSE−27 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/interface.go−132 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/pattern_interfaces.go−79 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/tables.go−31 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/tsdocument-changes.go−83 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/tsjson.go−3072 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/tsprotocol.go−6963 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/uri.go−192 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/sync.go−240 vendor/github.com/charmbracelet/x/powernap/pkg/lsp/types.go−93 vendor/github.com/charmbracelet/x/powernap/pkg/transport/connection.go−130 vendor/github.com/charmbracelet/x/powernap/pkg/transport/router.go−107 vendor/github.com/charmbracelet/x/powernap/pkg/transport/transport.go−214 vendor/github.com/charmbracelet/x/term/LICENSE−21 vendor/github.com/charmbracelet/x/term/term.go−51 vendor/github.com/charmbracelet/x/term/term_other.go−39 vendor/github.com/charmbracelet/x/term/term_plan9.go−118 vendor/github.com/charmbracelet/x/term/term_unix.go−96 vendor/github.com/charmbracelet/x/term/term_unix_bsd.go−11 vendor/github.com/charmbracelet/x/term/term_unix_other.go−11 vendor/github.com/charmbracelet/x/term/term_windows.go−87 vendor/github.com/charmbracelet/x/term/terminal.go−12 vendor/github.com/charmbracelet/x/term/util.go−47 vendor/github.com/charmbracelet/x/termios/LICENSE−21 vendor/github.com/charmbracelet/x/termios/bit_bsd.go−7 vendor/github.com/charmbracelet/x/termios/bit_darwin.go−7 vendor/github.com/charmbracelet/x/termios/bit_other.go−7 vendor/github.com/charmbracelet/x/termios/syscalls_bsd.go−11 vendor/github.com/charmbracelet/x/termios/syscalls_darwin.go−10 vendor/github.com/charmbracelet/x/termios/syscalls_linux.go−14 vendor/github.com/charmbracelet/x/termios/termios.go−249 vendor/github.com/charmbracelet/x/termios/termios_bsd.go−13 vendor/github.com/charmbracelet/x/termios/termios_linux.go−13 vendor/github.com/charmbracelet/x/termios/termios_other.go−15 vendor/github.com/charmbracelet/x/termios/termios_solaris.go−26 vendor/github.com/charmbracelet/x/windows/LICENSE−21 vendor/github.com/charmbracelet/x/windows/doc.go−4 vendor/github.com/charmbracelet/x/windows/syscall_windows.go−14 vendor/github.com/charmbracelet/x/windows/types.go−221 vendor/github.com/charmbracelet/x/windows/types_windows.go−142 vendor/github.com/charmbracelet/x/windows/zsyscall_windows.go−77 vendor/github.com/clipperhouse/displaywidth/.gitignore−3 vendor/github.com/clipperhouse/displaywidth/AGENTS.md−51 vendor/github.com/clipperhouse/displaywidth/CHANGELOG.md−129 vendor/github.com/clipperhouse/displaywidth/LICENSE−21 vendor/github.com/clipperhouse/displaywidth/README.md−190 vendor/github.com/clipperhouse/displaywidth/gen.go−3 vendor/github.com/clipperhouse/displaywidth/graphemes.go−73 vendor/github.com/clipperhouse/displaywidth/options.go−30 vendor/github.com/clipperhouse/displaywidth/trie.go−1699 vendor/github.com/clipperhouse/displaywidth/truncate.go−149 vendor/github.com/clipperhouse/displaywidth/width.go−239 vendor/github.com/clipperhouse/uax29/v2/LICENSE−21 vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md−120 vendor/github.com/clipperhouse/uax29/v2/graphemes/ansi.go−138 vendor/github.com/clipperhouse/uax29/v2/graphemes/ansi8.go−79 vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go−144 vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go−25 vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go−205 vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go−1717 vendor/github.com/clipperhouse/uax29/v2/words/README.md−121 vendor/github.com/clipperhouse/uax29/v2/words/bleve_compat.go−104 vendor/github.com/clipperhouse/uax29/v2/words/iterator.go−126 vendor/github.com/clipperhouse/uax29/v2/words/joiners.go−41 vendor/github.com/clipperhouse/uax29/v2/words/reader.go−34 vendor/github.com/clipperhouse/uax29/v2/words/seek.go−43 vendor/github.com/clipperhouse/uax29/v2/words/splitfunc.go−269 vendor/github.com/clipperhouse/uax29/v2/words/trie.go−3774 vendor/github.com/cyphar/filepath-securejoin/.golangci.yml−60 vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md−453 vendor/github.com/cyphar/filepath-securejoin/COPYING.md−447 vendor/github.com/cyphar/filepath-securejoin/LICENSE.BSD−28 vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0−373 vendor/github.com/cyphar/filepath-securejoin/README.md−184 vendor/github.com/cyphar/filepath-securejoin/VERSION−1 vendor/github.com/cyphar/filepath-securejoin/codecov.yml−29 vendor/github.com/cyphar/filepath-securejoin/doc.go−47 vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go−15 vendor/github.com/cyphar/filepath-securejoin/join.go−169 vendor/github.com/cyphar/filepath-securejoin/vfs.go−37 vendor/github.com/davecgh/go-spew/LICENSE−15 vendor/github.com/davecgh/go-spew/spew/bypass.go−145 vendor/github.com/davecgh/go-spew/spew/bypasssafe.go−38 vendor/github.com/davecgh/go-spew/spew/common.go−341 vendor/github.com/davecgh/go-spew/spew/config.go−306 vendor/github.com/davecgh/go-spew/spew/doc.go−211 vendor/github.com/davecgh/go-spew/spew/dump.go−509 vendor/github.com/davecgh/go-spew/spew/format.go−419 vendor/github.com/davecgh/go-spew/spew/spew.go−148 vendor/github.com/denisbrodbeck/machineid/.gitignore−29 vendor/github.com/denisbrodbeck/machineid/LICENSE.md−22 vendor/github.com/denisbrodbeck/machineid/README.md−182 vendor/github.com/denisbrodbeck/machineid/helper.go−36 vendor/github.com/denisbrodbeck/machineid/id.go−45 vendor/github.com/denisbrodbeck/machineid/id_bsd.go−42 vendor/github.com/denisbrodbeck/machineid/id_darwin.go−37 vendor/github.com/denisbrodbeck/machineid/id_linux.go−27 vendor/github.com/denisbrodbeck/machineid/id_windows.go−23 vendor/github.com/denisbrodbeck/machineid/logo.png vendor/github.com/denisbrodbeck/machineid/makefile−12 vendor/github.com/disintegration/imaging/.travis.yml−12 vendor/github.com/disintegration/imaging/LICENSE−21 vendor/github.com/disintegration/imaging/README.md−226 vendor/github.com/disintegration/imaging/adjust.go−253 vendor/github.com/disintegration/imaging/convolution.go−148 vendor/github.com/disintegration/imaging/doc.go−7 vendor/github.com/disintegration/imaging/effects.go−169 vendor/github.com/disintegration/imaging/histogram.go−52 vendor/github.com/disintegration/imaging/io.go−444 vendor/github.com/disintegration/imaging/resize.go−595 vendor/github.com/disintegration/imaging/scanner.go−285 vendor/github.com/disintegration/imaging/tools.go−249 vendor/github.com/disintegration/imaging/transform.go−268 vendor/github.com/disintegration/imaging/utils.go−167 vendor/github.com/dlclark/regexp2/v2/.gitignore−29 vendor/github.com/dlclark/regexp2/v2/ATTRIB−133 vendor/github.com/dlclark/regexp2/v2/LICENSE−21 vendor/github.com/dlclark/regexp2/v2/README.md−283 vendor/github.com/dlclark/regexp2/v2/Taskfile.yml−64 vendor/github.com/dlclark/regexp2/v2/bufferpool.go−76 vendor/github.com/dlclark/regexp2/v2/fastclock.go−141 vendor/github.com/dlclark/regexp2/v2/helpers/indexof.go−394 vendor/github.com/dlclark/regexp2/v2/helpers/math.go−15 vendor/github.com/dlclark/regexp2/v2/helpers/runes.go−58 vendor/github.com/dlclark/regexp2/v2/helpers/searchvalues.go−180 vendor/github.com/dlclark/regexp2/v2/match.go−455 vendor/github.com/dlclark/regexp2/v2/options.go−157 vendor/github.com/dlclark/regexp2/v2/regexp.go−717 vendor/github.com/dlclark/regexp2/v2/regexp_codegen.go−70 vendor/github.com/dlclark/regexp2/v2/replace.go−357 vendor/github.com/dlclark/regexp2/v2/runner.go−2197 vendor/github.com/dlclark/regexp2/v2/split.go−67 vendor/github.com/dlclark/regexp2/v2/stringprefixfilter.go−375 vendor/github.com/dlclark/regexp2/v2/syntax/charclass.go−1507 vendor/github.com/dlclark/regexp2/v2/syntax/code.go−316 vendor/github.com/dlclark/regexp2/v2/syntax/escape.go−94 vendor/github.com/dlclark/regexp2/v2/syntax/fuzz.go−21 vendor/github.com/dlclark/regexp2/v2/syntax/optimizations.go−672 vendor/github.com/dlclark/regexp2/v2/syntax/parser.go−2529 vendor/github.com/dlclark/regexp2/v2/syntax/prefix.go−943 vendor/github.com/dlclark/regexp2/v2/syntax/prefixanalyzer.go−1491 vendor/github.com/dlclark/regexp2/v2/syntax/replacerdata.go−87 vendor/github.com/dlclark/regexp2/v2/syntax/tree.go−2362 vendor/github.com/dlclark/regexp2/v2/syntax/unicode_alias_tables.go−6058 vendor/github.com/dlclark/regexp2/v2/syntax/writer.go−500 vendor/github.com/dustin/go-humanize/.travis.yml−21 vendor/github.com/dustin/go-humanize/LICENSE−21 vendor/github.com/dustin/go-humanize/README.markdown−124 vendor/github.com/dustin/go-humanize/big.go−31 vendor/github.com/dustin/go-humanize/bigbytes.go−189 vendor/github.com/dustin/go-humanize/bytes.go−143 vendor/github.com/dustin/go-humanize/comma.go−116 vendor/github.com/dustin/go-humanize/commaf.go−41 vendor/github.com/dustin/go-humanize/ftoa.go−49 vendor/github.com/dustin/go-humanize/humanize.go−8 vendor/github.com/dustin/go-humanize/number.go−192 vendor/github.com/dustin/go-humanize/ordinals.go−25 vendor/github.com/dustin/go-humanize/si.go−127 vendor/github.com/dustin/go-humanize/times.go−117 vendor/github.com/ebitengine/purego/.gitignore−1 vendor/github.com/ebitengine/purego/LICENSE−201 vendor/github.com/ebitengine/purego/README.md−119 vendor/github.com/ebitengine/purego/abi_amd64.h−99 vendor/github.com/ebitengine/purego/abi_arm64.h−39 vendor/github.com/ebitengine/purego/abi_loong64.h−60 vendor/github.com/ebitengine/purego/cgo.go−19 vendor/github.com/ebitengine/purego/dlerror.go−17 vendor/github.com/ebitengine/purego/dlfcn.go−99 vendor/github.com/ebitengine/purego/dlfcn_android.go−34 vendor/github.com/ebitengine/purego/dlfcn_darwin.go−20 vendor/github.com/ebitengine/purego/dlfcn_freebsd.go−14 vendor/github.com/ebitengine/purego/dlfcn_linux.go−16 vendor/github.com/ebitengine/purego/dlfcn_netbsd.go−15 vendor/github.com/ebitengine/purego/dlfcn_nocgo_freebsd.go−11 vendor/github.com/ebitengine/purego/dlfcn_nocgo_linux.go−19 vendor/github.com/ebitengine/purego/dlfcn_nocgo_netbsd.go−9 vendor/github.com/ebitengine/purego/dlfcn_playground.go−24 vendor/github.com/ebitengine/purego/dlfcn_stubs.s−22 vendor/github.com/ebitengine/purego/func.go−564 vendor/github.com/ebitengine/purego/gen.go−6 vendor/github.com/ebitengine/purego/go_runtime.go−13 vendor/github.com/ebitengine/purego/internal/cgo/dlfcn_cgo_unix.go−56 vendor/github.com/ebitengine/purego/internal/cgo/empty.go−6 vendor/github.com/ebitengine/purego/internal/cgo/syscall_cgo_unix.go−55 vendor/github.com/ebitengine/purego/internal/fakecgo/abi_amd64.h−99 vendor/github.com/ebitengine/purego/internal/fakecgo/abi_arm64.h−39 vendor/github.com/ebitengine/purego/internal/fakecgo/abi_loong64.h−60 vendor/github.com/ebitengine/purego/internal/fakecgo/abi_ppc64x.h−195 vendor/github.com/ebitengine/purego/internal/fakecgo/asm_386.s−29 vendor/github.com/ebitengine/purego/internal/fakecgo/asm_amd64.s−39 vendor/github.com/ebitengine/purego/internal/fakecgo/asm_arm.s−52 vendor/github.com/ebitengine/purego/internal/fakecgo/asm_arm64.s−36 vendor/github.com/ebitengine/purego/internal/fakecgo/asm_loong64.s−40 vendor/github.com/ebitengine/purego/internal/fakecgo/asm_ppc64le.s−82 vendor/github.com/ebitengine/purego/internal/fakecgo/asm_riscv64.s−78 vendor/github.com/ebitengine/purego/internal/fakecgo/asm_s390x.s−55 vendor/github.com/ebitengine/purego/internal/fakecgo/callbacks.go−93 vendor/github.com/ebitengine/purego/internal/fakecgo/doc.go−32 vendor/github.com/ebitengine/purego/internal/fakecgo/fakecgo.go−14 vendor/github.com/ebitengine/purego/internal/fakecgo/freebsd.go−27 vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin.go−88 vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd.go−100 vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go−72 vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux.go−100 vendor/github.com/ebitengine/purego/internal/fakecgo/go_netbsd.go−106 vendor/github.com/ebitengine/purego/internal/fakecgo/go_setenv.go−18 vendor/github.com/ebitengine/purego/internal/fakecgo/go_util.go−38 vendor/github.com/ebitengine/purego/internal/fakecgo/iscgo.go−19 vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo.go−39 vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_darwin.go−26 vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_freebsd.go−20 vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_linux.go−20 vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_netbsd.go−26 vendor/github.com/ebitengine/purego/internal/fakecgo/netbsd.go−23 vendor/github.com/ebitengine/purego/internal/fakecgo/setenv.go−19 vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_386.s−107 vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_amd64.s−107 vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm.s−81 vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm64.s−84 vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_loong64.s−88 vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_ppc64le.s−227 vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_riscv64.s−72 vendor/github.com/ebitengine/purego/internal/fakecgo/zsymbols.go−165 vendor/github.com/ebitengine/purego/internal/fakecgo/zsymbols_darwin.go−59 vendor/github.com/ebitengine/purego/internal/fakecgo/zsymbols_freebsd.go−48 vendor/github.com/ebitengine/purego/internal/fakecgo/zsymbols_linux.go−48 vendor/github.com/ebitengine/purego/internal/fakecgo/zsymbols_netbsd.go−59 vendor/github.com/ebitengine/purego/internal/fakecgo/ztrampolines_darwin.s−19 vendor/github.com/ebitengine/purego/internal/fakecgo/ztrampolines_freebsd.s−16 vendor/github.com/ebitengine/purego/internal/fakecgo/ztrampolines_linux.s−16 vendor/github.com/ebitengine/purego/internal/fakecgo/ztrampolines_netbsd.s−19 vendor/github.com/ebitengine/purego/internal/fakecgo/ztrampolines_stubs.s−55 vendor/github.com/ebitengine/purego/internal/strings/strings.go−40 vendor/github.com/ebitengine/purego/internal/xreflect/reflect_go124.go−15 vendor/github.com/ebitengine/purego/internal/xreflect/reflect_go125.go−12 vendor/github.com/ebitengine/purego/is_ios.go−13 vendor/github.com/ebitengine/purego/nocgo.go−25 vendor/github.com/ebitengine/purego/objc/objc_block_darwin.go−272 vendor/github.com/ebitengine/purego/objc/objc_runtime_darwin.go−709 vendor/github.com/ebitengine/purego/struct_386.go−41 vendor/github.com/ebitengine/purego/struct_amd64.go−286 vendor/github.com/ebitengine/purego/struct_arm.go−85 vendor/github.com/ebitengine/purego/struct_arm64.go−549 vendor/github.com/ebitengine/purego/struct_loong64.go−213 vendor/github.com/ebitengine/purego/struct_ppc64le.go−143 vendor/github.com/ebitengine/purego/struct_riscv64.go−143 vendor/github.com/ebitengine/purego/struct_s390x.go−143 vendor/github.com/ebitengine/purego/sys_386.s−147 vendor/github.com/ebitengine/purego/sys_amd64.s−170 vendor/github.com/ebitengine/purego/sys_arm.s−142 vendor/github.com/ebitengine/purego/sys_arm64.s−97 vendor/github.com/ebitengine/purego/sys_loong64.s−96 vendor/github.com/ebitengine/purego/sys_ppc64le.s−120 vendor/github.com/ebitengine/purego/sys_riscv64.s−101 vendor/github.com/ebitengine/purego/sys_s390x.s−114 vendor/github.com/ebitengine/purego/sys_unix_386.s−226 vendor/github.com/ebitengine/purego/sys_unix_arm.s−89 vendor/github.com/ebitengine/purego/sys_unix_arm64.s−70 vendor/github.com/ebitengine/purego/sys_unix_loong64.s−75 vendor/github.com/ebitengine/purego/sys_unix_ppc64le.s−114 vendor/github.com/ebitengine/purego/sys_unix_riscv64.s−79 vendor/github.com/ebitengine/purego/sys_unix_s390x.s−109 vendor/github.com/ebitengine/purego/syscall.go−83 vendor/github.com/ebitengine/purego/syscall_32bit.go−109 vendor/github.com/ebitengine/purego/syscall_cgo_linux.go−21 vendor/github.com/ebitengine/purego/syscall_sysv.go−317 vendor/github.com/ebitengine/purego/syscall_sysv_others.go−28 vendor/github.com/ebitengine/purego/syscall_sysv_stackargs.go−33 vendor/github.com/ebitengine/purego/syscall_windows.go−46 vendor/github.com/ebitengine/purego/zcallback_386.s−4014 vendor/github.com/ebitengine/purego/zcallback_amd64.s−2014 vendor/github.com/ebitengine/purego/zcallback_arm.s−4014 vendor/github.com/ebitengine/purego/zcallback_arm64.s−4014 vendor/github.com/ebitengine/purego/zcallback_loong64.s−4014 vendor/github.com/ebitengine/purego/zcallback_ppc64le.s−4014 vendor/github.com/ebitengine/purego/zcallback_riscv64.s−4051 vendor/github.com/ebitengine/purego/zcallback_s390x.s−4015 vendor/github.com/esiqveland/notify/.gitignore−1 vendor/github.com/esiqveland/notify/LICENSE.txt−27 vendor/github.com/esiqveland/notify/README.md−54 vendor/github.com/esiqveland/notify/doc.go−18 vendor/github.com/esiqveland/notify/notification.go−547 vendor/github.com/felixge/httpsnoop/.gitignore vendor/github.com/felixge/httpsnoop/LICENSE.txt−19 vendor/github.com/felixge/httpsnoop/Makefile−10 vendor/github.com/felixge/httpsnoop/README.md−95 vendor/github.com/felixge/httpsnoop/capture_metrics.go−121 vendor/github.com/felixge/httpsnoop/docs.go−10 vendor/github.com/felixge/httpsnoop/wrap_generated.go−16179 vendor/github.com/fsnotify/fsnotify/.cirrus.yml−14 vendor/github.com/fsnotify/fsnotify/.gitignore−10 vendor/github.com/fsnotify/fsnotify/.mailmap−2 vendor/github.com/fsnotify/fsnotify/CHANGELOG.md−602 vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md−145 vendor/github.com/fsnotify/fsnotify/LICENSE−25 vendor/github.com/fsnotify/fsnotify/README.md−182 vendor/github.com/fsnotify/fsnotify/backend_fen.go−467 vendor/github.com/fsnotify/fsnotify/backend_inotify.go−583 vendor/github.com/fsnotify/fsnotify/backend_kqueue.go−705 vendor/github.com/fsnotify/fsnotify/backend_other.go−22 vendor/github.com/fsnotify/fsnotify/backend_windows.go−680 vendor/github.com/fsnotify/fsnotify/fsnotify.go−496 vendor/github.com/fsnotify/fsnotify/internal/darwin.go−39 vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go−57 vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go−33 vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go−42 vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go−32 vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go−56 vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go−25 vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go−28 vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go−45 vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go−40 vendor/github.com/fsnotify/fsnotify/internal/freebsd.go−31 vendor/github.com/fsnotify/fsnotify/internal/internal.go−2 vendor/github.com/fsnotify/fsnotify/internal/unix.go−31 vendor/github.com/fsnotify/fsnotify/internal/unix2.go−7 vendor/github.com/fsnotify/fsnotify/internal/windows.go−41 vendor/github.com/fsnotify/fsnotify/shared.go−64 vendor/github.com/fsnotify/fsnotify/staticcheck.conf−3 vendor/github.com/fsnotify/fsnotify/system_bsd.go−7 vendor/github.com/fsnotify/fsnotify/system_darwin.go−8 vendor/github.com/gen2brain/beeep/LICENSE−23 vendor/github.com/gen2brain/beeep/README.md−41 vendor/github.com/gen2brain/beeep/alert_darwin.go−8 vendor/github.com/gen2brain/beeep/alert_js.go−12 vendor/github.com/gen2brain/beeep/alert_unix.go−17 vendor/github.com/gen2brain/beeep/alert_unsupported.go−8 vendor/github.com/gen2brain/beeep/alert_windows.go−43 vendor/github.com/gen2brain/beeep/beeep.go−55 vendor/github.com/gen2brain/beeep/beep_darwin.go−32 vendor/github.com/gen2brain/beeep/beep_js.go−39 vendor/github.com/gen2brain/beeep/beep_unix.go−100 vendor/github.com/gen2brain/beeep/beep_unsupported.go−15 vendor/github.com/gen2brain/beeep/beep_windows.go−52 vendor/github.com/gen2brain/beeep/notify_darwin.go−131 vendor/github.com/gen2brain/beeep/notify_js.go−63 vendor/github.com/gen2brain/beeep/notify_unix.go−181 vendor/github.com/gen2brain/beeep/notify_unix_nodbus.go−78 vendor/github.com/gen2brain/beeep/notify_unsupported.go−8 vendor/github.com/gen2brain/beeep/notify_windows.go−157 vendor/github.com/gen2brain/beeep/notify_windows_compat.go−109 vendor/github.com/go-git/gcfg/.gitignore−1 vendor/github.com/go-git/gcfg/LICENSE−28 vendor/github.com/go-git/gcfg/Makefile−17 vendor/github.com/go-git/gcfg/README−4 vendor/github.com/go-git/gcfg/doc.go−145 vendor/github.com/go-git/gcfg/errors.go−41 vendor/github.com/go-git/gcfg/read.go−273 vendor/github.com/go-git/gcfg/scanner/errors.go−121 vendor/github.com/go-git/gcfg/scanner/scanner.go−334 vendor/github.com/go-git/gcfg/set.go−334 vendor/github.com/go-git/gcfg/token/position.go−435 vendor/github.com/go-git/gcfg/token/serialize.go−56 vendor/github.com/go-git/gcfg/token/token.go−83 vendor/github.com/go-git/gcfg/types/bool.go−23 vendor/github.com/go-git/gcfg/types/doc.go−4 vendor/github.com/go-git/gcfg/types/enum.go−44 vendor/github.com/go-git/gcfg/types/int.go−86 vendor/github.com/go-git/gcfg/types/scan.go−23 vendor/github.com/go-git/go-billy/v5/.gitignore−4 vendor/github.com/go-git/go-billy/v5/LICENSE−201 vendor/github.com/go-git/go-billy/v5/Makefile−18 vendor/github.com/go-git/go-billy/v5/README.md−77 vendor/github.com/go-git/go-billy/v5/fs.go−210 vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go−444 vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go−114 vendor/github.com/go-git/go-billy/v5/memfs/memory.go−428 vendor/github.com/go-git/go-billy/v5/memfs/storage.go−251 vendor/github.com/go-git/go-billy/v5/osfs/os.go−132 vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go−351 vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go−126 vendor/github.com/go-git/go-billy/v5/osfs/os_js.go−25 vendor/github.com/go-git/go-billy/v5/osfs/os_options.go−3 vendor/github.com/go-git/go-billy/v5/osfs/os_plan9.go−91 vendor/github.com/go-git/go-billy/v5/osfs/os_posix.go−38 vendor/github.com/go-git/go-billy/v5/osfs/os_wasip1.go−34 vendor/github.com/go-git/go-billy/v5/osfs/os_windows.go−58 vendor/github.com/go-git/go-billy/v5/util/glob.go−111 vendor/github.com/go-git/go-billy/v5/util/util.go−282 vendor/github.com/go-git/go-billy/v5/util/walk.go−72 vendor/github.com/go-git/go-git/v5/LICENSE−201 vendor/github.com/go-git/go-git/v5/config/branch.go−123 vendor/github.com/go-git/go-git/v5/config/config.go−727 vendor/github.com/go-git/go-git/v5/config/modules.go−191 vendor/github.com/go-git/go-git/v5/config/optbool.go−82 vendor/github.com/go-git/go-git/v5/config/refspec.go−155 vendor/github.com/go-git/go-git/v5/config/url.go−81 vendor/github.com/go-git/go-git/v5/internal/path_util/path_util.go−29 vendor/github.com/go-git/go-git/v5/internal/pathutil/dotgit.go−21 vendor/github.com/go-git/go-git/v5/internal/pathutil/hfs.go−99 vendor/github.com/go-git/go-git/v5/internal/pathutil/ntfs.go−187 vendor/github.com/go-git/go-git/v5/internal/pathutil/tree.go−66 vendor/github.com/go-git/go-git/v5/internal/url/url.go−72 vendor/github.com/go-git/go-git/v5/plumbing/error.go−35 vendor/github.com/go-git/go-git/v5/plumbing/format/config/common.go−109 vendor/github.com/go-git/go-git/v5/plumbing/format/config/decoder.go−37 vendor/github.com/go-git/go-git/v5/plumbing/format/config/doc.go−122 vendor/github.com/go-git/go-git/v5/plumbing/format/config/encoder.go−82 vendor/github.com/go-git/go-git/v5/plumbing/format/config/format.go−53 vendor/github.com/go-git/go-git/v5/plumbing/format/config/option.go−127 vendor/github.com/go-git/go-git/v5/plumbing/format/config/section.go−181 vendor/github.com/go-git/go-git/v5/plumbing/format/gitignore/dir.go−148 vendor/github.com/go-git/go-git/v5/plumbing/format/gitignore/doc.go−70 vendor/github.com/go-git/go-git/v5/plumbing/format/gitignore/matcher.go−30 vendor/github.com/go-git/go-git/v5/plumbing/format/gitignore/pattern.go−155 vendor/github.com/go-git/go-git/v5/plumbing/hash.go−84 vendor/github.com/go-git/go-git/v5/plumbing/hash/hash.go−60 vendor/github.com/go-git/go-git/v5/plumbing/hash/hash_sha1.go−15 vendor/github.com/go-git/go-git/v5/plumbing/hash/hash_sha256.go−15 vendor/github.com/go-git/go-git/v5/plumbing/memory.go−72 vendor/github.com/go-git/go-git/v5/plumbing/object.go−111 vendor/github.com/go-git/go-git/v5/plumbing/reference.go−315 vendor/github.com/go-git/go-git/v5/plumbing/revision.go−11 vendor/github.com/go-git/go-git/v5/utils/ioutil/common.go−210 vendor/github.com/go-json-experiment/json/AUTHORS−3 vendor/github.com/go-json-experiment/json/CONTRIBUTORS−3 vendor/github.com/go-json-experiment/json/LICENSE−27 vendor/github.com/go-json-experiment/json/README.md−176 vendor/github.com/go-json-experiment/json/alias.go−943 vendor/github.com/go-json-experiment/json/api.png vendor/github.com/go-json-experiment/json/arshal.go−569 vendor/github.com/go-json-experiment/json/arshal_any.go−289 vendor/github.com/go-json-experiment/json/arshal_default.go−2043 vendor/github.com/go-json-experiment/json/arshal_embedded.go−230 vendor/github.com/go-json-experiment/json/arshal_funcs.go−428 vendor/github.com/go-json-experiment/json/arshal_methods.go−378 vendor/github.com/go-json-experiment/json/arshal_time.go−795 vendor/github.com/go-json-experiment/json/doc.go−247 vendor/github.com/go-json-experiment/json/errors.go−465 vendor/github.com/go-json-experiment/json/fields.go−656 vendor/github.com/go-json-experiment/json/fold.go−58 vendor/github.com/go-json-experiment/json/intern.go−88 vendor/github.com/go-json-experiment/json/internal/internal.go−42 vendor/github.com/go-json-experiment/json/internal/jsonflags/flags.go−223 vendor/github.com/go-json-experiment/json/internal/jsonopts/options.go−217 vendor/github.com/go-json-experiment/json/internal/jsonopts/options_format.go−124 vendor/github.com/go-json-experiment/json/internal/jsonwire/decode.go−606 vendor/github.com/go-json-experiment/json/internal/jsonwire/encode.go−290 vendor/github.com/go-json-experiment/json/internal/jsonwire/wire.go−209 vendor/github.com/go-json-experiment/json/jsontext/alias.go−555 vendor/github.com/go-json-experiment/json/jsontext/decode.go−1218 vendor/github.com/go-json-experiment/json/jsontext/doc.go−111 vendor/github.com/go-json-experiment/json/jsontext/encode.go−993 vendor/github.com/go-json-experiment/json/jsontext/errors.go−213 vendor/github.com/go-json-experiment/json/jsontext/export.go−91 vendor/github.com/go-json-experiment/json/jsontext/options.go−304 vendor/github.com/go-json-experiment/json/jsontext/pools.go−164 vendor/github.com/go-json-experiment/json/jsontext/quote.go−41 vendor/github.com/go-json-experiment/json/jsontext/state.go−828 vendor/github.com/go-json-experiment/json/jsontext/token.go−697 vendor/github.com/go-json-experiment/json/jsontext/value.go−414 vendor/github.com/go-json-experiment/json/migrate.sh−60 vendor/github.com/go-json-experiment/json/options.go−290 vendor/github.com/go-json-experiment/json/options_format.go−122 vendor/github.com/go-json-experiment/json/options_format_global.go−30 vendor/github.com/go-logfmt/logfmt/.gitignore−1 vendor/github.com/go-logfmt/logfmt/CHANGELOG.md−82 vendor/github.com/go-logfmt/logfmt/LICENSE−22 vendor/github.com/go-logfmt/logfmt/README.md−41 vendor/github.com/go-logfmt/logfmt/decode.go−254 vendor/github.com/go-logfmt/logfmt/doc.go−6 vendor/github.com/go-logfmt/logfmt/encode.go−322 vendor/github.com/go-logfmt/logfmt/jsonstring.go−277 vendor/github.com/go-logr/logr/.golangci.yaml−28 vendor/github.com/go-logr/logr/CHANGELOG.md−6 vendor/github.com/go-logr/logr/CONTRIBUTING.md−17 vendor/github.com/go-logr/logr/LICENSE−201 vendor/github.com/go-logr/logr/README.md−407 vendor/github.com/go-logr/logr/SECURITY.md−18 vendor/github.com/go-logr/logr/context.go−33 vendor/github.com/go-logr/logr/context_noslog.go−49 vendor/github.com/go-logr/logr/context_slog.go−83 vendor/github.com/go-logr/logr/discard.go−24 vendor/github.com/go-logr/logr/funcr/funcr.go−914 vendor/github.com/go-logr/logr/funcr/slogsink.go−105 vendor/github.com/go-logr/logr/logr.go−520 vendor/github.com/go-logr/logr/sloghandler.go−192 vendor/github.com/go-logr/logr/slogr.go−100 vendor/github.com/go-logr/logr/slogsink.go−120 vendor/github.com/go-logr/stdr/LICENSE−201 vendor/github.com/go-logr/stdr/README.md−6 vendor/github.com/go-logr/stdr/stdr.go−170 vendor/github.com/go-ole/go-ole/.travis.yml−8 vendor/github.com/go-ole/go-ole/ChangeLog.md−49 vendor/github.com/go-ole/go-ole/LICENSE−21 vendor/github.com/go-ole/go-ole/README.md−46 vendor/github.com/go-ole/go-ole/SECURITY.md−13 vendor/github.com/go-ole/go-ole/appveyor.yml−68 vendor/github.com/go-ole/go-ole/com.go−386 vendor/github.com/go-ole/go-ole/com_func.go−174 vendor/github.com/go-ole/go-ole/connect.go−192 vendor/github.com/go-ole/go-ole/constants.go−153 vendor/github.com/go-ole/go-ole/error.go−51 vendor/github.com/go-ole/go-ole/error_func.go−8 vendor/github.com/go-ole/go-ole/error_windows.go−24 vendor/github.com/go-ole/go-ole/guid.go−284 vendor/github.com/go-ole/go-ole/iconnectionpoint.go−20 vendor/github.com/go-ole/go-ole/iconnectionpoint_func.go−21 vendor/github.com/go-ole/go-ole/iconnectionpoint_windows.go−43 vendor/github.com/go-ole/go-ole/iconnectionpointcontainer.go−17 vendor/github.com/go-ole/go-ole/iconnectionpointcontainer_func.go−11 vendor/github.com/go-ole/go-ole/iconnectionpointcontainer_windows.go−25 vendor/github.com/go-ole/go-ole/idispatch.go−94 vendor/github.com/go-ole/go-ole/idispatch_func.go−19 vendor/github.com/go-ole/go-ole/idispatch_windows.go−203 vendor/github.com/go-ole/go-ole/ienumvariant.go−19 vendor/github.com/go-ole/go-ole/ienumvariant_func.go−19 vendor/github.com/go-ole/go-ole/ienumvariant_windows.go−63 vendor/github.com/go-ole/go-ole/iinspectable.go−18 vendor/github.com/go-ole/go-ole/iinspectable_func.go−15 vendor/github.com/go-ole/go-ole/iinspectable_windows.go−72 vendor/github.com/go-ole/go-ole/iprovideclassinfo.go−21 vendor/github.com/go-ole/go-ole/iprovideclassinfo_func.go−7 vendor/github.com/go-ole/go-ole/iprovideclassinfo_windows.go−21 vendor/github.com/go-ole/go-ole/itypeinfo.go−34 vendor/github.com/go-ole/go-ole/itypeinfo_func.go−7 vendor/github.com/go-ole/go-ole/itypeinfo_windows.go−21 vendor/github.com/go-ole/go-ole/iunknown.go−57 vendor/github.com/go-ole/go-ole/iunknown_func.go−19 vendor/github.com/go-ole/go-ole/iunknown_windows.go−58 vendor/github.com/go-ole/go-ole/ole.go−190 vendor/github.com/go-ole/go-ole/safearray.go−27 vendor/github.com/go-ole/go-ole/safearray_func.go−211 vendor/github.com/go-ole/go-ole/safearray_windows.go−337 vendor/github.com/go-ole/go-ole/safearrayconversion.go−140 vendor/github.com/go-ole/go-ole/safearrayslices.go−33 vendor/github.com/go-ole/go-ole/utility.go−101 vendor/github.com/go-ole/go-ole/variables.go−15 vendor/github.com/go-ole/go-ole/variant.go−105 vendor/github.com/go-ole/go-ole/variant_386.go−11 vendor/github.com/go-ole/go-ole/variant_amd64.go−12 vendor/github.com/go-ole/go-ole/variant_arm.go−11 vendor/github.com/go-ole/go-ole/variant_arm64.go−13 vendor/github.com/go-ole/go-ole/variant_date_386.go−22 vendor/github.com/go-ole/go-ole/variant_date_amd64.go−20 vendor/github.com/go-ole/go-ole/variant_date_arm.go−22 vendor/github.com/go-ole/go-ole/variant_date_arm64.go−23 vendor/github.com/go-ole/go-ole/variant_ppc64le.go−12 vendor/github.com/go-ole/go-ole/variant_s390x.go−12 vendor/github.com/go-ole/go-ole/vt_string.go−58 vendor/github.com/go-ole/go-ole/winrt.go−99 vendor/github.com/go-ole/go-ole/winrt_doc.go−36 vendor/github.com/go-openapi/jsonpointer/.editorconfig−26 vendor/github.com/go-openapi/jsonpointer/.gitignore−1 vendor/github.com/go-openapi/jsonpointer/.travis.yml−15 vendor/github.com/go-openapi/jsonpointer/CODE_OF_CONDUCT.md−74 vendor/github.com/go-openapi/jsonpointer/LICENSE−202

4795 more files changed are not listed.

.github/cla-signatures.json−32
@@ -2063,38 +2063,6 @@
20632063 "created_at": "2026-06-27T14:15:50Z",
20642064 "repoId": 987670088,
20652065 "pullRequestNo": 3133
2066− },
2067− {
2068− "name": "sablea",
2069− "id": 52388727,
2070− "comment_id": 4851979164,
2071− "created_at": "2026-07-01T08:13:03Z",
2072− "repoId": 987670088,
2073− "pullRequestNo": 3235
2074− },
2075− {
2076− "name": "Osamaali313",
2077− "id": 86572800,
2078− "comment_id": 4870561813,
2079− "created_at": "2026-07-02T21:19:15Z",
2080− "repoId": 987670088,
2081− "pullRequestNo": 3244
2082− },
2083− {
2084− "name": "lockp111",
2085− "id": 28383475,
2086− "comment_id": 4878248994,
2087− "created_at": "2026-07-03T17:12:26Z",
2088− "repoId": 987670088,
2089− "pullRequestNo": 3247
2090− },
2091− {
2092− "name": "TheRAWagent",
2093− "id": 77039476,
2094− "comment_id": 4879276761,
2095− "created_at": "2026-07-03T20:52:00Z",
2096− "repoId": 987670088,
2097− "pullRequestNo": 3251
20982066 }
20992067 ]
21002068}
.github/workflows/security.yml+5−5
@@ -30,11 +30,11 @@ jobs:
3030 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
3131 with:
3232 persist-credentials: false
33− - uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
33+ - uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
3434 with:
3535 languages: ${{ matrix.language }}
36− - uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
37− - uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
36+ - uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
37+ - uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
3838
3939 grype:
4040 runs-on: ubuntu-latest
@@ -52,7 +52,7 @@ jobs:
5252 path: "."
5353 fail-build: true
5454 severity-cutoff: critical
55− - uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
55+ - uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
5656 with:
5757 sarif_file: ${{ steps.scan.outputs.sarif }}
5858
@@ -73,7 +73,7 @@ jobs:
7373 - name: Run govulncheck
7474 run: |
7575 govulncheck -C . -format sarif ./... > results.sarif
76− - uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
76+ - uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
7777 with:
7878 sarif_file: results.sarif
7979
.github/workflows/snapshot.yml+1−1
@@ -25,7 +25,7 @@ jobs:
2525 - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
2626 with:
2727 go-version-file: go.mod
28− - uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
28+ - uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2
2929 with:
3030 version: "nightly"
3131 distribution: goreleaser-pro
.gitignore−1
@@ -44,7 +44,6 @@ Thumbs.db
4444
4545# Nix/direnv
4646.direnv/
47−/result
4847
4948**/.crush/**
5049
.golangci.yml+4−12
@@ -2,7 +2,6 @@ version: "2"
22linters:
33 enable:
44 - bodyclose
5− - errcheck
65 # - exhaustive
76 # - goconst
87 # - godot
@@ -10,7 +9,6 @@ linters:
109 # - gomoddirectives
1110 - goprintffuncname
1211 # - gosec
13− - ineffassign
1412 - misspell
1513 # - nakedret
1614 # - nestif
@@ -25,23 +23,17 @@ linters:
2523 - tparallel
2624 # - unconvert
2725 # - unparam
28− - unused
2926 - whitespace
3027 # - wrapcheck
28+ disable:
29+ - errcheck
30+ - ineffassign
31+ - unused
3132 exclusions:
3233 generated: lax
3334 presets:
3435 - common-false-positives
35− # Ignores the idiomatic-to-drop error family (*Close, *Flush,
36− # Fprint*, os.Remove/RemoveAll, os.Setenv, stdout/stderr writes) so
37− # errcheck only flags genuinely meaningful dropped errors.
38− - std-error-handling
3936 rules:
40− # Dropped errors in tests (setup writes, httptest encoders) are
41− # low-signal — test failures surface them anyway.
42− - path: _test\.go
43− linters:
44− - errcheck
4537 - text: '(slog|log)\.\w+'
4638 linters:
4739 - noctx
README.md−5
@@ -1,8 +1,5 @@
11# Crush
22
3−> [!NOTE]
4−> This is a personal fork of [charmbracelet/crush](https://github.com/charmbracelet/crush) with UI/UX tweaks and performance improvements.
5−
63<p align="center">
74 <a href="https://stuff.charm.sh/crush/charm-crush.png"><img width="450" alt="Charm Crush Logo" src="https://github.com/user-attachments/assets/cf8ca3ce-8b02-43f0-9d0f-5a331488da4b" /></a><br />
85 <a href="https://github.com/charmbracelet/crush/releases"><img src="https://img.shields.io/github/release/charmbracelet/crush" alt="Latest Release"></a>
@@ -197,7 +194,6 @@ That said, you can also set environment variables for preferred providers.
197194| `OPENROUTER_API_KEY` | OpenRouter |
198195| `IONET_API_KEY` | io.net |
199196| `ALIBABA_SINGAPORE_API_KEY` | Alibaba (Singapore) |
200−| `ALIBABA_US_API_KEY` | Alibaba (United States) |
201197| `GROQ_API_KEY` | Groq |
202198| `AVIAN_API_KEY` | Avian |
203199| `OPENCODE_API_KEY` | OpenCode Zen & Go |
@@ -211,7 +207,6 @@ That said, you can also set environment variables for preferred providers.
211207| `AZURE_OPENAI_API_ENDPOINT` | Azure OpenAI models |
212208| `AZURE_OPENAI_API_KEY` | Azure OpenAI models (optional when using Entra ID) |
213209| `AZURE_OPENAI_API_VERSION` | Azure OpenAI models |
214−| `MOONSHOT_API_KEY` | Moonshot |
215210
216211### Subscriptions
217212
flake.lock+4−3
@@ -20,15 +20,16 @@
2020 },
2121 "nixpkgs": {
2222 "locked": {
23− "lastModified": 1782964654,
24− "narHash": "sha256-oqyac50OyRdni5ZK9H7ZuAJoR3LzgsOdUcbBlUsk9lg=",
23+ "lastModified": 1775423009,
24+ "narHash": "sha256-vPKLpjhIVWdDrfiUM8atW6YkIggCEKdSAlJPzzhkQlw=",
2525 "owner": "NixOS",
2626 "repo": "nixpkgs",
27− "rev": "3130a1cf8f7c794ea5a4760c3fa4c745d64ec269",
27+ "rev": "68d8aa3d661f0e6bd5862291b5bb263b2a6595c9",
2828 "type": "github"
2929 },
3030 "original": {
3131 "owner": "NixOS",
32+ "ref": "nixos-unstable",
3233 "repo": "nixpkgs",
3334 "type": "github"
3435 }
flake.nix+1−32
@@ -2,7 +2,7 @@
22 description = "Crush development environment";
33
44 inputs = {
5− nixpkgs.url = "github:NixOS/nixpkgs";
5+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
66 flake-utils.url = "github:numtide/flake-utils";
77 };
88
@@ -14,38 +14,7 @@
1414 flake-utils.lib.eachDefaultSystem (
1515 system: let
1616 pkgs = nixpkgs.legacyPackages.${system};
17− version = "devel";
1817 in {
19− packages.default = pkgs.buildGoModule {
20− pname = "crush";
21− inherit version;
22−
23− src = ./.;
24−
25− vendorHash = null;
26−
27− # Match the Go toolchain used in the dev shell.
28− go = pkgs.go_1_26;
29−
30− env.CGO_ENABLED = 0;
31−
32− ldflags = [
33− "-s"
34− "-w"
35− "-X github.com/charmbracelet/crush/internal/version.Version=${version}"
36− ];
37−
38− # The test suite requires network access and external tools.
39− doCheck = false;
40−
41− meta = {
42− description = "Terminal-based AI coding assistant";
43− homepage = "https://github.com/charmbracelet/crush";
44− license = pkgs.lib.licenses.mit;
45− mainProgram = "crush";
46− };
47− };
48−
4918 devShells.default = pkgs.mkShell {
5019 buildInputs = with pkgs; [
5120 # Go toolchain
go.mod+11−11
@@ -3,13 +3,13 @@ module github.com/charmbracelet/crush
33go 1.26.4
44
55require (
6− charm.land/bubbles/v2 v2.1.1
7− charm.land/bubbletea/v2 v2.0.8
8− charm.land/catwalk v0.49.0
6+ charm.land/bubbles/v2 v2.1.0
7+ charm.land/bubbletea/v2 v2.0.7
8+ charm.land/catwalk v0.45.8
99 charm.land/fang/v2 v2.0.1
10− charm.land/fantasy v0.35.0
10+ charm.land/fantasy v0.34.0
1111 charm.land/glamour/v2 v2.0.1
12− charm.land/lipgloss/v2 v2.0.5
12+ charm.land/lipgloss/v2 v2.0.4
1313 charm.land/log/v2 v2.0.0
1414 charm.land/x/vcr v0.1.1
1515 github.com/JohannesKaufmann/html-to-markdown v1.6.0
@@ -22,7 +22,7 @@ require (
2222 github.com/charlievieth/fastwalk v1.0.14
2323 github.com/charmbracelet/colorprofile v0.4.3
2424 github.com/charmbracelet/openai-go v0.0.0-20260617131321-5e4b9c18c4be
25− github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7
25+ github.com/charmbracelet/ultraviolet v0.0.0-20260525132238-948f4557a654
2626 github.com/charmbracelet/x/ansi v0.11.7
2727 github.com/charmbracelet/x/editor v0.2.0
2828 github.com/charmbracelet/x/etag v0.2.0
@@ -51,8 +51,8 @@ require (
5151 github.com/ncruces/go-sqlite3 v0.35.1
5252 github.com/nxadm/tail v1.4.11
5353 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
54− github.com/posthog/posthog-go v1.17.5
55− github.com/pressly/goose/v3 v3.27.2
54+ github.com/posthog/posthog-go v1.17.2
55+ github.com/pressly/goose/v3 v3.27.1
5656 github.com/qjebbs/go-jsons v1.0.0-alpha.6
5757 github.com/rivo/uniseg v0.4.7
5858 github.com/sahilm/fuzzy v0.1.3
@@ -103,7 +103,7 @@ require (
103103 github.com/aws/aws-sdk-go-v2/service/sso v1.31.3 // indirect
104104 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 // indirect
105105 github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 // indirect
106− github.com/aws/smithy-go v1.27.3 // indirect
106+ github.com/aws/smithy-go v1.27.2 // indirect
107107 github.com/aymerick/douceur v0.2.0 // indirect
108108 github.com/bahlo/generic-list-go v0.2.0 // indirect
109109 github.com/buger/jsonparser v1.1.2 // indirect
@@ -153,7 +153,7 @@ require (
153153 github.com/klauspost/cpuid/v2 v2.3.0 // indirect
154154 github.com/klauspost/pgzip v1.2.6 // indirect
155155 github.com/mailru/easyjson v0.7.7 // indirect
156− github.com/mattn/go-runewidth v0.0.24 // indirect
156+ github.com/mattn/go-runewidth v0.0.23 // indirect
157157 github.com/mfridman/interpolate v0.0.2 // indirect
158158 github.com/microcosm-cc/bluemonday v1.0.27 // indirect
159159 github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -207,7 +207,7 @@ require (
207207 golang.org/x/time v0.15.0 // indirect
208208 golang.org/x/tools v0.45.0 // indirect
209209 google.golang.org/api v0.286.0 // indirect
210− google.golang.org/genai v1.62.0 // indirect
210+ google.golang.org/genai v1.61.0 // indirect
211211 google.golang.org/genproto/googleapis/rpc v0.0.0-20260622175928-b703f567277d // indirect
212212 google.golang.org/grpc v1.81.1 // indirect
213213 google.golang.org/protobuf v1.36.11 // indirect
go.sum+22−22
@@ -1,17 +1,17 @@
1−charm.land/bubbles/v2 v2.1.1 h1:7r55WzBxpo/R3z98hGmY7KKPd3ET6vsf0Fb9sDHOV60=
2−charm.land/bubbles/v2 v2.1.1/go.mod h1:GE6M31gaWZVXzGw73OeuTTgy4lX+OtkH0E5ymnNsHxo=
3−charm.land/bubbletea/v2 v2.0.8 h1:SxTJMhCAI3lbPmy4SgX5LWZ24AdINr4I6UEqzZvYJuY=
4−charm.land/bubbletea/v2 v2.0.8/go.mod h1:2SkdgoTXluXJHOUwAoRlRXF/28vklb1rFl6GcgV1/ss=
5−charm.land/catwalk v0.49.0 h1:pXcHh9H76VvwGqPYMZhJe5vRqMLpzGsv0anJArKw70g=
6−charm.land/catwalk v0.49.0/go.mod h1:bw6/oiChsa9Fkr8LgYn84KrbbJY2z1+m7Jmlmmv3B2A=
1+charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g=
2+charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY=
3+charm.land/bubbletea/v2 v2.0.7 h1:7qw2tTAVar7m7klOPBYfTB0mniv/RuexsYwMRNxSeL0=
4+charm.land/bubbletea/v2 v2.0.7/go.mod h1:DGW2q8gvzHnOpMpZTORs0aySVHCox5C+2Svk0fci1qs=
5+charm.land/catwalk v0.45.8 h1:fnd86JrpIDcuH8+QrTl5ZDChrwbHNMjM0GAMr2jFW5g=
6+charm.land/catwalk v0.45.8/go.mod h1:bw6/oiChsa9Fkr8LgYn84KrbbJY2z1+m7Jmlmmv3B2A=
77charm.land/fang/v2 v2.0.1 h1:zQCM8JQJ1JnQX/66B5jlCYBUxL2as5JXQZ2KJ6EL0mY=
88charm.land/fang/v2 v2.0.1/go.mod h1:S1GmkpcvK+OB5w9caywUnJcsMew45Ot8FXqoz8ALrII=
9−charm.land/fantasy v0.35.0 h1:iFES7cnt3/o20T01+y2PDkHj5dHEWRFQH0vSXYzV8xo=
10−charm.land/fantasy v0.35.0/go.mod h1:rUqUhunz+c6k66L0hHd51CrXB3SSzPNKkwBrzIe6XnY=
9+charm.land/fantasy v0.34.0 h1:InhMjeJ0u7tuJCFWSWV1hvojpDIIUMD5ur1ds333Qcw=
10+charm.land/fantasy v0.34.0/go.mod h1:DAZjJ5w98fOJkkDWKpG2jKqDg58Kvri/5I0ZdD31Hjo=
1111charm.land/glamour/v2 v2.0.1 h1:xl+r00A4aJWU0z8fgwKd9fQQ4rsphqGUzuEiXZP5n+c=
1212charm.land/glamour/v2 v2.0.1/go.mod h1:jo9z8XqVKPeEFMVdvCRLGk++RyJ3CdUwgNr7EvXLw3k=
13−charm.land/lipgloss/v2 v2.0.5 h1:kbNxgeeUOYv5J0YdpxFjfvf3dFvqH8Aci4zB6xqFtrY=
14−charm.land/lipgloss/v2 v2.0.5/go.mod h1:9oqhxt4yxIMe6q5A4kHr44DremZk7J9UNh74GlWa5nc=
13+charm.land/lipgloss/v2 v2.0.4 h1:lcPeVtcp23SNra7lHy8iYE4UC2aIipVQ47sbGyyxR5Q=
14+charm.land/lipgloss/v2 v2.0.4/go.mod h1:0653x8epbZSzdDfO/XPS1a/uYPOBeSsCssOpJOqDzik=
1515charm.land/log/v2 v2.0.0 h1:SY3Cey7ipx86/MBXQHwsguOT6X1exT94mmJRdzTNs+s=
1616charm.land/log/v2 v2.0.0/go.mod h1:c3cZSRqm20qUVVAR1WmS/7ab8bgha3C6G7DjPcaVZz0=
1717charm.land/x/vcr v0.1.1 h1:PXCFMUG0rPtyk35rhfzYCJEduOzWXCIbrXTFq4OF/9Q=
@@ -86,8 +86,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 h1:yLr03zQE/5Eu5l3QU0Si+xMb
8686github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6/go.mod h1:Q5N6icH+KJZDLh+ESNwzdv6cZ6vLFF/egy3IOxWhmz4=
8787github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 h1:VrIhKRCSK1umelSgB9RghvA9RTUYeQffyAS5ApXehNI=
8888github.com/aws/aws-sdk-go-v2/service/sts v1.43.3/go.mod h1:r8wkDOuLaaMFqFiYAb8dGY2A3gJCOujMc6CFOVC4Zhc=
89−github.com/aws/smithy-go v1.27.3 h1:F3Zb497UhhskkfpJmfkXswyo+t0sh9OTBnIHjogWbVY=
90−github.com/aws/smithy-go v1.27.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
89+github.com/aws/smithy-go v1.27.2 h1:y9NPmSE6am6LjEFPfqHqG/jJk7AauQvhCJONKh7kpzk=
90+github.com/aws/smithy-go v1.27.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
9191github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
9292github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
9393github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
@@ -108,8 +108,8 @@ github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex
108108github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
109109github.com/charmbracelet/openai-go v0.0.0-20260617131321-5e4b9c18c4be h1:pg+OWlIkk9HOe/8P5J95aKe2wGDzFUiiyFOUpwR30B4=
110110github.com/charmbracelet/openai-go v0.0.0-20260617131321-5e4b9c18c4be/go.mod h1:1DahUaExbUZx/jD+FNT2PKP4L9rLE5+ZBRuI8mZjd/E=
111−github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7 h1:3FmWoGNWK4STvqg0O0Aeav2T7rodWJAPeF0QpH+8gFw=
112−github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7/go.mod h1:f/jRa757WUmaOZrbPspXymbg/GnbF+rwe4OLsG7aXYo=
111+github.com/charmbracelet/ultraviolet v0.0.0-20260525132238-948f4557a654 h1:FpSYhY28ucg9ZRr+2wj67FAQ0Ey5yiK0072PmRDJNek=
112+github.com/charmbracelet/ultraviolet v0.0.0-20260525132238-948f4557a654/go.mod h1:hFpumms29Smx3LStRfku8vcCTBe1Kq8aCXtHUJa3mjY=
113113github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
114114github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
115115github.com/charmbracelet/x/editor v0.2.0 h1:7XLUKtaRaB8jN7bWU2p2UChiySyaAuIfYiIRg8gGWwk=
@@ -290,8 +290,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
290290github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
291291github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
292292github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
293−github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
294−github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
293+github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
294+github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
295295github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
296296github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
297297github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
@@ -341,10 +341,10 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
341341github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
342342github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
343343github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
344−github.com/posthog/posthog-go v1.17.5 h1:mrLiAdyiQpl8Yeyg23iShyAztJMaUrYzsBjKeH52Aak=
345−github.com/posthog/posthog-go v1.17.5/go.mod h1://M430hNH3e8CDv4i8SJesb26816Mpa6GIZaiP4pNQU=
346−github.com/pressly/goose/v3 v3.27.2 h1:FjKNzcmMdGrQlSIu5alMSmakQtJFBgtw+A0bb1p/LC8=
347−github.com/pressly/goose/v3 v3.27.2/go.mod h1:qWW+/8dkVtJYjJrbIpwD5xxnEJTUKvxkQ9JKQp9LaIM=
344+github.com/posthog/posthog-go v1.17.2 h1:w0TaCAd+Z3WoEgVyR/nlcXlqNN2tpoBfIyxuGssDgCE=
345+github.com/posthog/posthog-go v1.17.2/go.mod h1://M430hNH3e8CDv4i8SJesb26816Mpa6GIZaiP4pNQU=
346+github.com/pressly/goose/v3 v3.27.1 h1:6uEvcprBybDmW4hcz3gYujhARhye+GoWKhEWyzD5sh4=
347+github.com/pressly/goose/v3 v3.27.1/go.mod h1:maruOxsPnIG2yHHyo8UqKWXYKFcH7Q76csUV7+7KYoM=
348348github.com/qjebbs/go-jsons v1.0.0-alpha.6 h1:9pMjdrCT7rj9CDIM0FXf9Ilie8PEy9g1b4qMkCnedjQ=
349349github.com/qjebbs/go-jsons v1.0.0-alpha.6/go.mod h1:wNJrtinHyC3YSf6giEh4FJN8+yZV7nXBjvmfjhBIcw4=
350350github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@@ -559,8 +559,8 @@ gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
559559gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
560560google.golang.org/api v0.286.0 h1:TdTXMvzYKnWV1/lPbCdbXRqBrkDqjPto22H2xeZZ8LI=
561561google.golang.org/api v0.286.0/go.mod h1:NlOlUIr8MPoIhT9Bb/oUnRuHbJOLwxb6JSYJM8Yz+jQ=
562−google.golang.org/genai v1.62.0 h1:PaBju84orf4Vbcc6OfHe4vxhxhjwulKTgOpEc3iIc00=
563−google.golang.org/genai v1.62.0/go.mod h1:mDdPDFXo1Ats7f1WXVyZgWb/CkMzFWTWJruIMy7hGIU=
562+google.golang.org/genai v1.61.0 h1:wCyNGiaC9q5A59B80zuEtNBhq3ypEvICFkZYOfK7IO0=
563+google.golang.org/genai v1.61.0/go.mod h1:mDdPDFXo1Ats7f1WXVyZgWb/CkMzFWTWJruIMy7hGIU=
564564google.golang.org/genproto v0.0.0-20260622175928-b703f567277d h1:CP5omUq8AJTiWMrPKM1WRLJ7zZeXd9OPcQD3TbBNAyY=
565565google.golang.org/genproto v0.0.0-20260622175928-b703f567277d/go.mod h1:DrwuGJgFSEVNpv3S5Q5VxhRTvdnjauw9GtvwVOEARfA=
566566google.golang.org/genproto/googleapis/api v0.0.0-20260622175928-b703f567277d h1:xr2lwHI91bn3UiXcnyzRMQjp2LRiM8wEHzwUaE0YhTs=
internal/agent/agent.go+32−71
@@ -157,7 +157,6 @@ type sessionAgent struct {
157157 systemPromptPrefix *csync.Value[string]
158158 systemPrompt *csync.Value[string]
159159 tools *csync.Slice[fantasy.AgentTool]
160− toolsGen atomic.Uint64 // bumped by SetTools; lets PrepareStep skip re-copying unchanged tools
161160
162161 isSubAgent bool
163162 sessions session.Service
@@ -349,7 +348,7 @@ func (a *sessionAgent) sessionMu(sessionID string) *sync.Mutex {
349348func (a *sessionAgent) enqueueCall(call SessionAgentCall) {
350349 existing, ok := a.messageQueue.Get(call.SessionID)
351350 if !ok {
352− existing = make([]SessionAgentCall, 0, 4)
351+ existing = []SessionAgentCall{}
353352 }
354353 queued := call
355354 if call.Accepted != nil {
@@ -643,13 +642,6 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (result *
643642
644643 // Copy mutable fields under lock to avoid races with SetTools/SetModels.
645644 agentTools := a.tools.Copy()
646− // Per-step tool snapshot reused across PrepareStep calls; only
647− // re-copied when SetTools bumps toolsGen (see PrepareStep below).
648− var (
649− stepTools []fantasy.AgentTool
650− stepToolsGen uint64
651− stepToolsInit bool
652− )
653645 largeModel := a.largeModel.Get()
654646 systemPrompt := a.systemPrompt.Get()
655647 promptPrefix := a.systemPromptPrefix.Get()
@@ -814,14 +806,7 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (result *
814806 }
815807
816808 // Use latest tools (updated by SetTools when MCP tools change).
817− // Reuse the previous step's copy unless SetTools has bumped the
818− // generation, so an unchanged tool set isn't re-copied every step.
819− if gen := a.toolsGen.Load(); !stepToolsInit || gen != stepToolsGen {
820− stepTools = a.tools.Copy()
821− stepToolsGen = gen
822− stepToolsInit = true
823− }
824− prepared.Tools = stepTools
809+ prepared.Tools = a.tools.Copy()
825810
826811 // Drain queued follow-up prompts for this step. Calls covered
827812 // by a cancel recorded while they sat in the queue are dropped:
@@ -866,12 +851,9 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (result *
866851 prepared.Messages = append([]fantasy.Message{fantasy.NewSystemMessage(promptPrefix)}, prepared.Messages...)
867852 }
868853
869− // Save a reference to the prepared messages for token
870− // estimation in OnStepFinish. No clone needed here — the
871− // messages are not modified between PrepareStep and
872− // OnStepFinish (which run sequentially), and the clone is
873− // only needed when the provider returns zero usage.
874− stepMessages = prepared.Messages
854+ sessionLock.Lock()
855+ stepMessages = cloneFantasyMessages(prepared.Messages)
856+ sessionLock.Unlock()
875857
876858 var assistantMsg message.Message
877859 assistantMsg, err = a.messages.Create(callContext, call.SessionID, message.CreateMessageParams{
@@ -1080,9 +1062,7 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (result *
10801062 currentAssistant.FinishThinking()
10811063 toolCalls := currentAssistant.ToolCalls()
10821064 // INFO: we use the cleanup context here because the genCtx has been cancelled.
1083− // Only load messages from the current assistant message onward — tool
1084− // results for this turn's tool calls can only exist after it.
1085− msgs, createErr := a.messages.ListAfter(cleanupCtx, currentAssistant.SessionID, currentAssistant.ID)
1065+ msgs, createErr := a.messages.List(cleanupCtx, currentAssistant.SessionID)
10861066 if createErr != nil {
10871067 return nil, createErr
10881068 }
@@ -1454,10 +1434,7 @@ func (a *sessionAgent) Summarize(ctx context.Context, sessionID string, opts fan
14541434 return qErr
14551435}
14561436
1457−// cacheControlOptions is computed once for the process lifetime: the env check
1458−// is constant and callers only read (never mutate) the returned map, so the
1459−// same shared value can be reused across the ~3 calls per step.
1460−var cacheControlOptions = sync.OnceValue(func() fantasy.ProviderOptions {
1437+func (a *sessionAgent) getCacheControlOptions() fantasy.ProviderOptions {
14611438 if t, _ := strconv.ParseBool(os.Getenv("CRUSH_DISABLE_ANTHROPIC_CACHE")); t {
14621439 return fantasy.ProviderOptions{}
14631440 }
@@ -1472,10 +1449,6 @@ var cacheControlOptions = sync.OnceValue(func() fantasy.ProviderOptions {
14721449 CacheControl: anthropic.CacheControl{Type: "ephemeral"},
14731450 },
14741451 }
1475−})
1476−
1477−func (a *sessionAgent) getCacheControlOptions() fantasy.ProviderOptions {
1478− return cacheControlOptions()
14791452}
14801453
14811454func (a *sessionAgent) createUserMessage(ctx context.Context, call SessionAgentCall) (message.Message, error) {
@@ -1496,7 +1469,7 @@ func (a *sessionAgent) createUserMessage(ctx context.Context, call SessionAgentC
14961469}
14971470
14981471func (a *sessionAgent) preparePrompt(msgs []message.Message, supportsImages bool, attachments ...message.Attachment) ([]fantasy.Message, []fantasy.FilePart) {
1499− history := make([]fantasy.Message, 0, len(msgs)+1)
1472+ var history []fantasy.Message
15001473 if !a.isSubAgent {
15011474 history = append(history, fantasy.NewUserMessage(
15021475 fmt.Sprintf(
@@ -1596,7 +1569,7 @@ func filterOrphanedToolResults(m message.Message, knownToolCallIDs map[string]st
15961569 if len(aiMsgs) == 0 {
15971570 return fantasy.Message{}, false
15981571 }
1599− validParts := make([]fantasy.MessagePart, 0, len(aiMsgs[0].Content))
1572+ var validParts []fantasy.MessagePart
16001573 for _, part := range aiMsgs[0].Content {
16011574 tr, ok := fantasy.AsMessagePart[fantasy.ToolResultPart](part)
16021575 if !ok {
@@ -1628,7 +1601,7 @@ func filterOrphanedToolResults(m message.Message, knownToolCallIDs map[string]st
16281601// conversation. Returns the message and true if any synthetic results were
16291602// produced.
16301603func syntheticToolResultsForOrphanedCalls(m message.Message, knownToolResultIDs map[string]struct{}) (fantasy.Message, bool) {
1631− syntheticParts := make([]fantasy.MessagePart, 0, len(m.ToolCalls()))
1604+ var syntheticParts []fantasy.MessagePart
16321605 for _, tc := range m.ToolCalls() {
16331606 if _, hasResult := knownToolResultIDs[tc.ID]; hasResult {
16341607 continue
@@ -1655,20 +1628,23 @@ func syntheticToolResultsForOrphanedCalls(m message.Message, knownToolResultIDs
16551628}
16561629
16571630func (a *sessionAgent) getSessionMessages(ctx context.Context, session session.Session) ([]message.Message, error) {
1631+ msgs, err := a.messages.List(ctx, session.ID)
1632+ if err != nil {
1633+ return nil, fmt.Errorf("failed to list messages: %w", err)
1634+ }
1635+
16581636 if session.SummaryMessageID != "" {
1659− msgs, err := a.messages.ListAfter(ctx, session.ID, session.SummaryMessageID)
1660− if err != nil {
1661− return nil, fmt.Errorf("failed to list messages after summary: %w", err)
1637+ summaryMsgIndex := -1
1638+ for i, msg := range msgs {
1639+ if msg.ID == session.SummaryMessageID {
1640+ summaryMsgIndex = i
1641+ break
1642+ }
16621643 }
1663− if len(msgs) > 0 {
1644+ if summaryMsgIndex != -1 {
1645+ msgs = msgs[summaryMsgIndex:]
16641646 msgs[0].Role = message.User
16651647 }
1666− return msgs, nil
1667− }
1668−
1669− msgs, err := a.messages.List(ctx, session.ID)
1670− if err != nil {
1671− return nil, fmt.Errorf("failed to list messages: %w", err)
16721648 }
16731649 return msgs, nil
16741650}
@@ -1999,10 +1975,14 @@ func (a *sessionAgent) CancelAll() {
19991975}
20001976
20011977func (a *sessionAgent) IsBusy() bool {
2002− // activeRequests only ever holds non-nil cancel funcs (Set with a
2003− // real func, removed via Del), so a length check is exact — and it
2004− // avoids cloning the whole map, which Seq() does on every call.
2005− return a.activeRequests.Len() > 0
1978+ var busy bool
1979+ for cancelFunc := range a.activeRequests.Seq() {
1980+ if cancelFunc != nil {
1981+ busy = true
1982+ break
1983+ }
1984+ }
1985+ return busy
20061986}
20071987
20081988func (a *sessionAgent) IsSessionBusy(sessionID string) bool {
@@ -2037,7 +2017,6 @@ func (a *sessionAgent) SetModels(large Model, small Model) {
20372017
20382018func (a *sessionAgent) SetTools(tools []fantasy.AgentTool) {
20392019 a.tools.SetSlice(tools)
2040− a.toolsGen.Add(1)
20412020}
20422021
20432022func (a *sessionAgent) SetSystemPrompt(systemPrompt string) {
@@ -2121,8 +2100,6 @@ func (a *sessionAgent) workaroundProviderMediaLimitations(messages []fantasy.Mes
21212100 return messages
21222101 }
21232102
2124− supportsImages := largeModel.CatwalkCfg.SupportsImages
2125−
21262103 convertedMessages := make([]fantasy.Message, 0, len(messages))
21272104
21282105 for _, msg := range messages {
@@ -2142,21 +2119,6 @@ func (a *sessionAgent) workaroundProviderMediaLimitations(messages []fantasy.Mes
21422119 }
21432120
21442121 if media, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentMedia](toolResult.Output); ok {
2145− if !supportsImages {
2146− // Model cannot process images. Replace with a text
2147− // placeholder and skip creating a synthetic user
2148− // message with FilePart, which would brick the
2149− // session on text-only models.
2150− textParts = append(textParts, fantasy.ToolResultPart{
2151− ToolCallID: toolResult.ToolCallID,
2152− Output: fantasy.ToolResultOutputContentText{
2153− Text: "[Image/media content not supported by this model]",
2154− },
2155− ProviderOptions: toolResult.ProviderOptions,
2156− })
2157− continue
2158− }
2159−
21602122 decoded, err := base64.StdEncoding.DecodeString(media.Data)
21612123 if err != nil {
21622124 slog.Warn("Failed to decode media data", "error", err)
@@ -2236,8 +2198,7 @@ func providerRetryLogFields(err *fantasy.ProviderError, delay time.Duration) []a
22362198// The second return value indicates whether sanitization occurred.
22372199func sanitizeToolInput(toolName, toolCallID, input string) (string, bool) {
22382200 if !json.Valid([]byte(input)) {
2239− slog.Warn(
2240− "Malformed tool call JSON from provider, replacing with empty object",
2201+ slog.Warn("Malformed tool call JSON from provider, replacing with empty object",
22412202 "tool", toolName,
22422203 "id", toolCallID,
22432204 "input_len", len(input),
internal/agent/agent_test.go−143
@@ -1,7 +1,6 @@
11package agent
22
33import (
4− "encoding/base64"
54 "fmt"
65 "log/slog"
76 "os"
@@ -11,11 +10,9 @@ import (
1110 "testing"
1211 "time"
1312
14− "charm.land/catwalk/pkg/catwalk"
1513 "charm.land/fantasy"
1614 "charm.land/x/vcr"
1715 "github.com/charmbracelet/crush/internal/agent/tools"
18− "github.com/charmbracelet/crush/internal/config"
1916 "github.com/charmbracelet/crush/internal/message"
2017 "github.com/charmbracelet/crush/internal/session"
2118 "github.com/stretchr/testify/assert"
@@ -843,146 +840,6 @@ func TestPreparePrompt_OrphanedToolUseMixed(t *testing.T) {
843840 require.Equal(t, 1, syntheticCount, "expected exactly one synthetic result for the orphaned call")
844841}
845842
846−func TestWorkaroundProviderMediaLimitations_TextOnlyModel(t *testing.T) {
847− env := testEnv(t)
848− sa := testSessionAgent(env, nil, nil, "test prompt")
849− agent := sa.(*sessionAgent)
850−
851− pngBase64 := base64.StdEncoding.EncodeToString([]byte("fake-png-data"))
852−
853− messages := []fantasy.Message{
854− {
855− Role: fantasy.MessageRoleTool,
856− Content: []fantasy.MessagePart{
857− fantasy.ToolResultPart{
858− ToolCallID: "call_1",
859− Output: fantasy.ToolResultOutputContentMedia{
860− Data: pngBase64,
861− MediaType: "image/png",
862− },
863− },
864− },
865− },
866− }
867−
868− // Non-Anthropic provider, no image support — should replace media with
869− // a text placeholder and not create a synthetic user message.
870− largeModel := Model{
871− ModelCfg: config.SelectedModel{Provider: "openai"},
872− CatwalkCfg: catwalk.Model{
873− SupportsImages: false,
874− },
875− }
876−
877− result := agent.workaroundProviderMediaLimitations(messages, largeModel)
878−
879− // Should produce exactly one message: the tool message with a text
880− // placeholder. No synthetic user message with FilePart.
881− require.Len(t, result, 1)
882− require.Equal(t, fantasy.MessageRoleTool, result[0].Role)
883−
884− tr, ok := fantasy.AsMessagePart[fantasy.ToolResultPart](result[0].Content[0])
885− require.True(t, ok)
886− _, ok = fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentText](tr.Output)
887− require.True(t, ok)
888−}
889−
890−func TestWorkaroundProviderMediaLimitations_VisionModel(t *testing.T) {
891− env := testEnv(t)
892− sa := testSessionAgent(env, nil, nil, "test prompt")
893− agent := sa.(*sessionAgent)
894−
895− pngBase64 := base64.StdEncoding.EncodeToString([]byte("fake-png-data"))
896−
897− messages := []fantasy.Message{
898− {
899− Role: fantasy.MessageRoleTool,
900− Content: []fantasy.MessagePart{
901− fantasy.ToolResultPart{
902− ToolCallID: "call_1",
903− Output: fantasy.ToolResultOutputContentMedia{
904− Data: pngBase64,
905− MediaType: "image/png",
906− },
907− },
908− },
909− },
910− }
911−
912− // Non-Anthropic provider, image support — should create a synthetic
913− // user message with FilePart.
914− largeModel := Model{
915− ModelCfg: config.SelectedModel{Provider: "openai"},
916− CatwalkCfg: catwalk.Model{
917− SupportsImages: true,
918− },
919− }
920−
921− result := agent.workaroundProviderMediaLimitations(messages, largeModel)
922−
923− // Should produce two messages: tool message with placeholder text,
924− // and synthetic user message with FilePart.
925− require.Len(t, result, 2)
926− require.Equal(t, fantasy.MessageRoleTool, result[0].Role)
927− require.Equal(t, fantasy.MessageRoleUser, result[1].Role)
928−
929− // The tool message should have text placeholder.
930− tr, ok := fantasy.AsMessagePart[fantasy.ToolResultPart](result[0].Content[0])
931− require.True(t, ok)
932− textOutput, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentText](tr.Output)
933− require.True(t, ok)
934− require.Contains(t, textOutput.Text, "see attached file")
935−
936− // The synthetic user message should contain a TextPart and a FilePart.
937− require.Len(t, result[1].Content, 2)
938− file, ok := fantasy.AsMessagePart[fantasy.FilePart](result[1].Content[1])
939− require.True(t, ok)
940− require.Equal(t, "image/png", file.MediaType)
941−}
942−
943−func TestWorkaroundProviderMediaLimitations_AnthropicProvider(t *testing.T) {
944− env := testEnv(t)
945− sa := testSessionAgent(env, nil, nil, "test prompt")
946− agent := sa.(*sessionAgent)
947−
948− pngBase64 := base64.StdEncoding.EncodeToString([]byte("fake-png-data"))
949−
950− messages := []fantasy.Message{
951− {
952− Role: fantasy.MessageRoleTool,
953− Content: []fantasy.MessagePart{
954− fantasy.ToolResultPart{
955− ToolCallID: "call_1",
956− Output: fantasy.ToolResultOutputContentMedia{
957− Data: pngBase64,
958− MediaType: "image/png",
959− },
960− },
961− },
962− },
963− }
964−
965− // Anthropic provider — should return messages unchanged regardless of
966− // SupportsImages, since Anthropic handles media in tool results natively.
967− largeModel := Model{
968− ModelCfg: config.SelectedModel{Provider: string(catwalk.InferenceProviderAnthropic)},
969− CatwalkCfg: catwalk.Model{
970− SupportsImages: true,
971− },
972− }
973−
974− result := agent.workaroundProviderMediaLimitations(messages, largeModel)
975− require.Len(t, result, 1)
976− require.Equal(t, fantasy.MessageRoleTool, result[0].Role)
977−
978− // The media should still be in the tool result, untouched.
979− tr, ok := fantasy.AsMessagePart[fantasy.ToolResultPart](result[0].Content[0])
980− require.True(t, ok)
981− media, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentMedia](tr.Output)
982− require.True(t, ok)
983− require.Equal(t, "image/png", media.MediaType)
984−}
985−
986843func TestProviderRetryLogFields(t *testing.T) {
987844 t.Run("nil provider error", func(t *testing.T) {
988845 fields := providerRetryLogFields(nil, 2*time.Second)
internal/agent/coordinator.go+12−185
@@ -15,8 +15,6 @@ import (
1515 "path/filepath"
1616 "slices"
1717 "strings"
18− "sync"
19− "sync/atomic"
2018
2119 "charm.land/catwalk/pkg/catwalk"
2220 "charm.land/fantasy"
@@ -24,9 +22,7 @@ import (
2422 "github.com/charmbracelet/crush/internal/agent/notify"
2523 "github.com/charmbracelet/crush/internal/agent/prompt"
2624 "github.com/charmbracelet/crush/internal/agent/tools"
27− "github.com/charmbracelet/crush/internal/agent/tools/mcp"
2825 "github.com/charmbracelet/crush/internal/config"
29− "github.com/charmbracelet/crush/internal/csync"
3026 "github.com/charmbracelet/crush/internal/discover"
3127 "github.com/charmbracelet/crush/internal/event"
3228 "github.com/charmbracelet/crush/internal/filetracker"
@@ -127,32 +123,6 @@ type coordinator struct {
127123 activeSkills []*skills.Skill // Post-filter: active skills only.
128124 skillTracker *skills.Tracker
129125
130− // providerClients caches built provider clients keyed by their fully
131− // resolved configuration. UpdateModels rebuilds models every turn;
132− // without this, each turn discards the provider's HTTP client and its
133− // keep-alive connection pool, forcing a fresh TLS handshake. The key
134− // captures every input buildProvider depends on, so any config/model/
135− // credential change misses the cache and rebuilds.
136− providerClients *csync.Map[string, fantasy.Provider]
137−
138− // toolsCache avoids rebuilding the tool set on every UpdateModels call.
139− // The cache is invalidated when the config pointer changes (reload or
140− // mutation) or when mcpToolsGen is bumped (MCP server connect/disconnect
141− // or tools/list_changed notification).
142− toolsCacheMu sync.Mutex
143− toolsCache []fantasy.AgentTool
144− toolsCacheCfg *config.Config
145− toolsCacheMCP uint64
146− mcpToolsGen atomic.Uint64
147−
148− // providerOptsCache avoids repeating the 3× JSON marshal + merge +
149− // unmarshal in getProviderOptions on every turn when the model and
150− // config have not changed. Keyed by config pointer + model CatwalkCfg
151− // ID + provider name.
152− providerOptsCacheMu sync.Mutex
153− providerOptsCache fantasy.ProviderOptions
154− providerOptsCacheKey string
155−
156126 readyWg errgroup.Group
157127}
158128
@@ -196,8 +166,6 @@ func NewCoordinator(
196166 allSkills: allSkills,
197167 activeSkills: activeSkills,
198168 skillTracker: skillTracker,
199−
200− providerClients: csync.NewMap[string, fantasy.Provider](),
201169 }
202170
203171 agentCfg, ok := cfg.Config().Agents[config.AgentCoder]
@@ -217,17 +185,6 @@ func NewCoordinator(
217185 }
218186 c.currentAgent = agent
219187 c.agents[config.AgentCoder] = agent
220−
221− // Subscribe to MCP tool-list changes so the tools cache is invalidated
222− // when MCP servers connect/disconnect or announce new tools.
223− go func() {
224− for ev := range mcp.SubscribeEvents(ctx) {
225− if ev.Payload.Type == mcp.EventToolsListChanged || ev.Payload.Type == mcp.EventStateChanged {
226− c.mcpToolsGen.Add(1)
227− }
228− }
229− }()
230−
231188 return c, nil
232189}
233190
@@ -278,7 +235,7 @@ func (c *coordinator) run(ctx context.Context, accept *AcceptedRun, sessionID st
278235 return nil, errModelProviderNotConfigured
279236 }
280237
281− mergedOptions, temp, topP, topK, freqPenalty, presPenalty := c.cachedMergeCallOptions(model, providerCfg)
238+ mergedOptions, temp, topP, topK, freqPenalty, presPenalty := mergeCallOptions(model, providerCfg)
282239
283240 if err := c.refreshTokenIfExpired(ctx, providerCfg); err != nil {
284241 // NOTE(@andreynering): We don't return here because the event handling to ask the user to reauthenticate
@@ -450,7 +407,6 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
450407 options[openai.Name] = parsed
451408 }
452409 }
453−
454410 case anthropic.Name, bedrock.Name:
455411 var (
456412 _, hasEffort = mergedOptions["effort"]
@@ -459,7 +415,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
459415 )
460416
461417 switch providerCfg.ID {
462− case string(catwalk.InferenceProviderAlibabaSingapore), string(catwalk.InferenceProviderAlibabaUS):
418+ case string(catwalk.InferenceProviderAlibabaSingapore):
463419 switch {
464420 case !hasEffort && shouldSetEffort:
465421 extraBody["reasoning_effort"] = reasoningEffort
@@ -498,7 +454,6 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
498454 if err == nil {
499455 options[openrouter.Name] = parsed
500456 }
501−
502457 case vercel.Name:
503458 _, hasReasoning := mergedOptions["reasoning"]
504459 if !hasReasoning && shouldSetEffort {
@@ -511,7 +466,6 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
511466 if err == nil {
512467 options[vercel.Name] = parsed
513468 }
514−
515469 case google.Name:
516470 _, hasReasoning := mergedOptions["thinking_config"]
517471 if !hasReasoning {
@@ -531,7 +485,6 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
531485 if err == nil {
532486 options[google.Name] = parsed
533487 }
534−
535488 case openaicompat.Name, hyper.Name:
536489 extraBody := make(map[string]any)
537490
@@ -560,14 +513,16 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
560513 extraBody["reasoning"] = map[string]string{"effort": "none"}
561514 }
562515 }
563−
564516 case string(catwalk.InferenceProviderZAI), string(catwalk.InferenceProviderDeepSeek):
565517 if model.ModelCfg.Think || reasoningEffort != "" {
566− extraBody["thinking"] = map[string]any{"type": "enabled"}
518+ extraBody["thinking"] = map[string]any{
519+ "type": "enabled",
520+ }
567521 } else {
568− extraBody["thinking"] = map[string]any{"type": "disabled"}
522+ extraBody["thinking"] = map[string]any{
523+ "type": "disabled",
524+ }
569525 }
570−
571526 case string(catwalk.InferenceProviderFireworks):
572527 // NOTE: Fireworks break if we set both `reasoning_effort` and `thinking`.
573528 if reasoningEffort == "" {
@@ -577,15 +532,9 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
577532 extraBody["thinking"] = map[string]any{"type": "disabled"}
578533 }
579534 }
580−
581− case string(catwalk.InferenceProviderBaseten):
582− extraBody["chat_template_args"] = map[string]any{
583− "enable_thinking": model.ModelCfg.Think || reasoningEffort != "",
584− }
585−
586− case string(catwalk.InferenceProviderAlibabaSingapore), string(catwalk.InferenceProviderAlibabaUS):
535+ case string(catwalk.InferenceProviderAlibabaSingapore):
587536 if model.CatwalkCfg.CanReason {
588− extraBody["enable_thinking"] = model.ModelCfg.Think || reasoningEffort != ""
537+ extraBody["enable_thinking"] = model.ModelCfg.Think
589538 }
590539 }
591540
@@ -595,7 +544,6 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
595544 if err == nil {
596545 options[openaicompat.Name] = parsed
597546 }
598−
599547 default:
600548 // Known custom providers (litellm, ollama, omlx) are
601549 // openai-compat under the hood.
@@ -620,39 +568,6 @@ func mergeCallOptions(model Model, cfg config.ProviderConfig) (fantasy.ProviderO
620568 return modelOptions, temp, topP, topK, freqPenalty, presPenalty
621569}
622570
623−// cachedMergeCallOptions wraps mergeCallOptions with a memoization cache.
624−// The provider options JSON merge is expensive (3× marshal + merge +
625−// unmarshal) and the inputs rarely change between turns. The cache key
626−// combines the config pointer identity with the model's catwalk ID and
627−// provider name so any config reload or model switch misses the cache.
628−func (c *coordinator) cachedMergeCallOptions(model Model, cfg config.ProviderConfig) (fantasy.ProviderOptions, *float64, *float64, *int64, *float64, *float64) {
629− key := fmt.Sprintf("%p|%s|%s", c.cfg.Config(), model.CatwalkCfg.ID, model.ModelCfg.Provider)
630−
631− c.providerOptsCacheMu.Lock()
632− if c.providerOptsCacheKey == key {
633− opts := c.providerOptsCache
634− c.providerOptsCacheMu.Unlock()
635− // Recompute the scalar overrides (cheap) since they don't need
636− // the JSON merge — they're just cmp.Or calls.
637− temp := cmp.Or(model.ModelCfg.Temperature, model.CatwalkCfg.Options.Temperature)
638− topP := cmp.Or(model.ModelCfg.TopP, model.CatwalkCfg.Options.TopP)
639− topK := cmp.Or(model.ModelCfg.TopK, model.CatwalkCfg.Options.TopK)
640− freqPenalty := cmp.Or(model.ModelCfg.FrequencyPenalty, model.CatwalkCfg.Options.FrequencyPenalty)
641− presPenalty := cmp.Or(model.ModelCfg.PresencePenalty, model.CatwalkCfg.Options.PresencePenalty)
642− return opts, temp, topP, topK, freqPenalty, presPenalty
643− }
644− c.providerOptsCacheMu.Unlock()
645−
646− opts, temp, topP, topK, freqPenalty, presPenalty := mergeCallOptions(model, cfg)
647−
648− c.providerOptsCacheMu.Lock()
649− c.providerOptsCache = opts
650− c.providerOptsCacheKey = key
651− c.providerOptsCacheMu.Unlock()
652−
653− return opts, temp, topP, topK, freqPenalty, presPenalty
654−}
655−
656571func (c *coordinator) buildAgent(ctx context.Context, prompt *prompt.Prompt, agent config.Agent, isSubAgent bool) (SessionAgent, error) {
657572 large, small, err := c.buildAgentModels(ctx, isSubAgent)
658573 if err != nil {
@@ -1119,74 +1034,6 @@ func (c *coordinator) buildProvider(providerCfg config.ProviderConfig, model con
11191034 apiKey, _ := c.cfg.Resolve(providerCfg.APIKey)
11201035 baseURL, _ := c.cfg.Resolve(providerCfg.BaseURL)
11211036
1122− // Reuse a previously built client when nothing that affects it has
1123− // changed, so the HTTP keep-alive pool survives across turns. The key
1124− // is derived from the resolved inputs above (plus the debug flag); the
1125− // per-provider adjustments inside the switch below are deterministic
1126− // functions of these, so identical inputs always yield an identical
1127− // client.
1128− cacheKey := c.providerCacheKey(providerCfg, model, apiKey, baseURL, headers, isSubAgent)
1129− if p, ok := c.providerClients.Get(cacheKey); ok {
1130− return p, nil
1131− }
1132− provider, err := c.buildProviderUncached(providerCfg, model, apiKey, baseURL, headers, isSubAgent)
1133− if err != nil {
1134− return nil, err
1135− }
1136− c.providerClients.Set(cacheKey, provider)
1137− return provider, nil
1138−}
1139−
1140−// providerCacheKey builds a stable key from every input buildProvider's
1141−// switch consumes, so any credential, endpoint, header, or option change
1142−// produces a distinct key (and thus a rebuilt client).
1143−func (c *coordinator) providerCacheKey(providerCfg config.ProviderConfig, model config.SelectedModel, apiKey, baseURL string, headers map[string]string, isSubAgent bool) string {
1144− var b strings.Builder
1145− b.WriteString(providerCfg.ID)
1146− b.WriteByte(0)
1147− b.WriteString(string(providerCfg.Type))
1148− b.WriteByte(0)
1149− // The opencode providers pick their client type from the model, so the
1150− // model must participate in the key.
1151− b.WriteString(model.Model)
1152− b.WriteByte(0)
1153− b.WriteString(apiKey)
1154− b.WriteByte(0)
1155− // Bedrock falls back to AWS_BEARER_TOKEN_BEDROCK when no apiKey is
1156− // configured, and that token is baked into the built client. Fold it
1157− // into the key so a rotated token rebuilds the client instead of
1158− // reusing a stale one.
1159− if apiKey == "" && providerCfg.Type == bedrock.Name {
1160− b.WriteString(os.Getenv("AWS_BEARER_TOKEN_BEDROCK"))
1161− }
1162− b.WriteByte(0)
1163− b.WriteString(baseURL)
1164− b.WriteByte(0)
1165− for _, k := range slices.Sorted(maps.Keys(headers)) {
1166− b.WriteString(k)
1167− b.WriteByte('=')
1168− b.WriteString(headers[k])
1169− b.WriteByte(1)
1170− }
1171− b.WriteByte(0)
1172− if extra, err := json.Marshal(providerCfg.ExtraBody); err == nil {
1173− b.Write(extra)
1174− }
1175− b.WriteByte(0)
1176− if extra, err := json.Marshal(providerCfg.ExtraParams); err == nil {
1177− b.Write(extra)
1178− }
1179− b.WriteByte(0)
1180− if isSubAgent {
1181− b.WriteByte('s')
1182− }
1183− if c.cfg.Config().Options.Debug {
1184− b.WriteByte('d')
1185− }
1186− return b.String()
1187−}
1188−
1189−func (c *coordinator) buildProviderUncached(providerCfg config.ProviderConfig, model config.SelectedModel, apiKey, baseURL string, headers map[string]string, isSubAgent bool) (fantasy.Provider, error) {
11901037 switch providerCfg.ID {
11911038 case string(catwalk.InferenceProviderOpenCodeGo), string(catwalk.InferenceProviderOpenCodeZen):
11921039 if opencodeMessagesModels[model.Model] {
@@ -1286,34 +1133,15 @@ func (c *coordinator) UpdateModels(ctx context.Context) error {
12861133 }
12871134 c.currentAgent.SetModels(large, small)
12881135
1289− cfg := c.cfg.Config()
1290− agentCfg, ok := cfg.Agents[config.AgentCoder]
1136+ agentCfg, ok := c.cfg.Config().Agents[config.AgentCoder]
12911137 if !ok {
12921138 return errCoderAgentNotConfigured
12931139 }
12941140
1295− // Fast path: reuse cached tools when neither the config nor the MCP
1296− // tool set has changed since the last build.
1297− mcpGen := c.mcpToolsGen.Load()
1298− c.toolsCacheMu.Lock()
1299− if c.toolsCache != nil && c.toolsCacheCfg == cfg && c.toolsCacheMCP == mcpGen {
1300− c.currentAgent.SetTools(c.toolsCache)
1301− c.toolsCacheMu.Unlock()
1302− return nil
1303− }
1304− c.toolsCacheMu.Unlock()
1305−
13061141 tools, err := c.buildTools(ctx, agentCfg, false)
13071142 if err != nil {
13081143 return err
13091144 }
1310−
1311− c.toolsCacheMu.Lock()
1312− c.toolsCache = tools
1313− c.toolsCacheCfg = cfg
1314− c.toolsCacheMCP = mcpGen
1315− c.toolsCacheMu.Unlock()
1316−
13171145 c.currentAgent.SetTools(tools)
13181146 return nil
13191147}
@@ -1498,8 +1326,7 @@ func (c *coordinator) runSubAgent(ctx context.Context, params subAgentParams) (f
14981326 // Update parent session cost on a best-effort basis. A failure here must
14991327 // not discard the sub-agent output that was already produced.
15001328 if err := c.updateParentSessionCost(ctx, session.ID, params.SessionID); err != nil {
1501− slog.Warn(
1502− "Failed to update parent session cost",
1329+ slog.Warn("Failed to update parent session cost",
15031330 "child_session", session.ID,
15041331 "parent_session", params.SessionID,
15051332 "error", err,
internal/agent/hyper/provider.go+1−1
@@ -88,7 +88,7 @@ func FetchCredits(ctx context.Context, apiKey string) (int, error) {
8888 if err != nil {
8989 return 0, fmt.Errorf("failed to make request: %w", err)
9090 }
91− defer resp.Body.Close()
91+ defer resp.Body.Close() //nolint:errcheck
9292
9393 if resp.StatusCode != http.StatusOK {
9494 return 0, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
internal/agent/hyper/provider.json+25−25
@@ -43,9 +43,9 @@
4343 {
4444 "id": "gemma-4-26b-a4b-it",
4545 "name": "Gemma 4 26B A4B",
46− "cost_per_1m_in": 0.126,
47− "cost_per_1m_out": 0.42,
48− "cost_per_1m_in_cached": 0.063,
46+ "cost_per_1m_in": 0.122,
47+ "cost_per_1m_out": 0.428,
48+ "cost_per_1m_in_cached": 0.061,
4949 "cost_per_1m_out_cached": 0,
5050 "context_window": 256000,
5151 "default_max_tokens": 25600,
@@ -55,9 +55,9 @@
5555 {
5656 "id": "glm-5",
5757 "name": "GLM-5",
58− "cost_per_1m_in": 0.86,
59− "cost_per_1m_out": 2.784,
60− "cost_per_1m_in_cached": 0.43,
58+ "cost_per_1m_in": 0.89,
59+ "cost_per_1m_out": 2.848,
60+ "cost_per_1m_in_cached": 0.445,
6161 "cost_per_1m_out_cached": 0,
6262 "context_window": 202752,
6363 "default_max_tokens": 20275,
@@ -67,12 +67,12 @@
6767 {
6868 "id": "glm-5.1",
6969 "name": "GLM-5.1",
70− "cost_per_1m_in": 1.3,
71− "cost_per_1m_out": 4.3,
70+ "cost_per_1m_in": 1.4,
71+ "cost_per_1m_out": 4.4,
7272 "cost_per_1m_in_cached": 0.26,
7373 "cost_per_1m_out_cached": 0,
74− "context_window": 202000,
75− "default_max_tokens": 202000,
74+ "context_window": 202800,
75+ "default_max_tokens": 64000,
7676 "can_reason": true,
7777 "reasoning_levels": [
7878 "low",
@@ -102,9 +102,9 @@
102102 {
103103 "id": "gpt-oss-120b",
104104 "name": "gpt-oss-120b",
105− "cost_per_1m_in": 0.168,
106− "cost_per_1m_out": 0.562,
107− "cost_per_1m_in_cached": 0.084,
105+ "cost_per_1m_in": 0.178,
106+ "cost_per_1m_out": 0.68,
107+ "cost_per_1m_in_cached": 0.089,
108108 "cost_per_1m_out_cached": 0,
109109 "context_window": 131072,
110110 "default_max_tokens": 13107,
@@ -120,9 +120,9 @@
120120 {
121121 "id": "kimi-k2.5",
122122 "name": "Kimi K2.5",
123− "cost_per_1m_in": 0.51,
124− "cost_per_1m_out": 2.67,
125− "cost_per_1m_in_cached": 0.255,
123+ "cost_per_1m_in": 0.518,
124+ "cost_per_1m_out": 2.72,
125+ "cost_per_1m_in_cached": 0.259,
126126 "cost_per_1m_out_cached": 0,
127127 "context_window": 262144,
128128 "default_max_tokens": 26214,
@@ -168,9 +168,9 @@
168168 {
169169 "id": "llama-3.3-70b-instruct",
170170 "name": "Llama 3.3 70B Instruct",
171− "cost_per_1m_in": 0.6066,
172− "cost_per_1m_out": 1.0386,
173− "cost_per_1m_in_cached": 0.3033,
171+ "cost_per_1m_in": 0.5786,
172+ "cost_per_1m_out": 1.0606,
173+ "cost_per_1m_in_cached": 0.2893,
174174 "cost_per_1m_out_cached": 0,
175175 "context_window": 128000,
176176 "default_max_tokens": 12800,
@@ -270,9 +270,9 @@
270270 {
271271 "id": "qwen3-coder-480b-a35b-instruct-int4-mixed-ar",
272272 "name": "Qwen3 Coder 480B A35B Instruct INT4 Mixed AR",
273− "cost_per_1m_in": 0.557,
274− "cost_per_1m_out": 2.545,
275− "cost_per_1m_in_cached": 0.2785,
273+ "cost_per_1m_in": 0.445,
274+ "cost_per_1m_out": 2.145,
275+ "cost_per_1m_in_cached": 0.2225,
276276 "cost_per_1m_out_cached": 0,
277277 "context_window": 106000,
278278 "default_max_tokens": 10600,
@@ -282,9 +282,9 @@
282282 {
283283 "id": "qwen3-next-80b-a3b-instruct",
284284 "name": "Qwen3 Next 80B A3B Instruct",
285− "cost_per_1m_in": 0.128,
286− "cost_per_1m_out": 1.28,
287− "cost_per_1m_in_cached": 0.064,
285+ "cost_per_1m_in": 0.1275,
286+ "cost_per_1m_out": 1.216,
287+ "cost_per_1m_in_cached": 0.06375,
288288 "cost_per_1m_out_cached": 0,
289289 "context_window": 262144,
290290 "default_max_tokens": 26214,
internal/agent/loop_detection.go+17−17
@@ -1,7 +1,8 @@
11package agent
22
33import (
4− "hash/fnv"
4+ "crypto/sha256"
5+ "encoding/hex"
56 "io"
67
78 "charm.land/fantasy"
@@ -21,11 +22,11 @@ func hasRepeatedToolCalls(steps []fantasy.StepResult, windowSize, maxRepeats int
2122 }
2223
2324 window := steps[len(steps)-windowSize:]
24− counts := make(map[uint64]int, windowSize)
25+ counts := make(map[string]int)
2526
2627 for _, step := range window {
2728 sig := getToolInteractionSignature(step.Content)
28− if sig == 0 {
29+ if sig == "" {
2930 continue
3031 }
3132 counts[sig]++
@@ -39,35 +40,34 @@ func hasRepeatedToolCalls(steps []fantasy.StepResult, windowSize, maxRepeats int
3940
4041// getToolInteractionSignature computes a hash signature for the tool
4142// interactions in a single step's content. It pairs tool calls with their
42−// results (matched by ToolCallID) and returns an FNV-1a hash. FNV is ~10x
43−// faster than SHA-256 and collision risk is negligible for loop detection
44−// over a 10-step window. If the step contains no tool calls, it returns 0.
45−func getToolInteractionSignature(content fantasy.ResponseContent) uint64 {
43+// results (matched by ToolCallID) and returns a hex-encoded SHA-256 hash.
44+// If the step contains no tool calls, it returns "".
45+func getToolInteractionSignature(content fantasy.ResponseContent) string {
4646 toolCalls := content.ToolCalls()
4747 if len(toolCalls) == 0 {
48− return 0
48+ return ""
4949 }
5050
5151 // Index tool results by their ToolCallID for fast lookup.
52− resultsByID := make(map[string]fantasy.ToolResultContent, len(toolCalls))
52+ resultsByID := make(map[string]fantasy.ToolResultContent)
5353 for _, tr := range content.ToolResults() {
5454 resultsByID[tr.ToolCallID] = tr
5555 }
5656
57− h := fnv.New64a()
57+ h := sha256.New()
5858 for _, tc := range toolCalls {
5959 output := ""
6060 if tr, ok := resultsByID[tc.ToolCallID]; ok {
6161 output = toolResultOutputString(tr.Result)
6262 }
63− _, _ = io.WriteString(h, tc.ToolName)
64− h.Write([]byte{0})
65− _, _ = io.WriteString(h, tc.Input)
66− h.Write([]byte{0})
67− _, _ = io.WriteString(h, output)
68− h.Write([]byte{0})
63+ io.WriteString(h, tc.ToolName)
64+ io.WriteString(h, "\x00")
65+ io.WriteString(h, tc.Input)
66+ io.WriteString(h, "\x00")
67+ io.WriteString(h, output)
68+ io.WriteString(h, "\x00")
6969 }
70− return h.Sum64()
70+ return hex.EncodeToString(h.Sum(nil))
7171}
7272
7373// toolResultOutputString converts a ToolResultOutputContent to a stable string
internal/agent/loop_detection_test.go+8−8
@@ -145,18 +145,18 @@ func TestHasRepeatedToolCalls(t *testing.T) {
145145func TestGetToolInteractionSignature(t *testing.T) {
146146 t.Run("empty content returns empty string", func(t *testing.T) {
147147 sig := getToolInteractionSignature(fantasy.ResponseContent{})
148− if sig != 0 {
149− t.Errorf("expected zero signature, got %d", sig)
148+ if sig != "" {
149+ t.Errorf("expected empty string, got %q", sig)
150150 }
151151 })
152152
153− t.Run("text only content returns zero signature", func(t *testing.T) {
153+ t.Run("text only content returns empty string", func(t *testing.T) {
154154 content := fantasy.ResponseContent{
155155 fantasy.TextContent{Text: "hello"},
156156 }
157157 sig := getToolInteractionSignature(content)
158− if sig != 0 {
159− t.Errorf("expected zero signature, got %d", sig)
158+ if sig != "" {
159+ t.Errorf("expected empty string, got %q", sig)
160160 }
161161 })
162162
@@ -166,8 +166,8 @@ func TestGetToolInteractionSignature(t *testing.T) {
166166 fantasy.ToolResultContent{ToolCallID: "1", ToolName: "read", Result: fantasy.ToolResultOutputContentText{Text: "content"}},
167167 }
168168 sig := getToolInteractionSignature(content)
169− if sig == 0 {
170− t.Error("expected non-zero signature")
169+ if sig == "" {
170+ t.Error("expected non-empty signature")
171171 }
172172 })
173173
@@ -183,7 +183,7 @@ func TestGetToolInteractionSignature(t *testing.T) {
183183 sig1 := getToolInteractionSignature(content1)
184184 sig2 := getToolInteractionSignature(content2)
185185 if sig1 != sig2 {
186− t.Errorf("expected same signature for same interactions, got %d and %d", sig1, sig2)
186+ t.Errorf("expected same signature for same interactions, got %q and %q", sig1, sig2)
187187 }
188188 })
189189
internal/agent/prompt/prompt.go+1−1
@@ -115,7 +115,7 @@ func processContextPath(p string, store *config.ConfigStore) []ContextFile {
115115 return contexts
116116 }
117117 if info.IsDir() {
118− _ = filepath.WalkDir(fullPath, func(path string, d os.DirEntry, err error) error {
118+ filepath.WalkDir(fullPath, func(path string, d os.DirEntry, err error) error {
119119 if err != nil {
120120 return err
121121 }
internal/agent/tools/bash.go+16−4
@@ -206,7 +206,19 @@ func NewBashTool(permissions permission.Service, workingDir string, attribution
206206 // Determine working directory
207207 execWorkingDir := cmp.Or(params.WorkingDir, workingDir)
208208
209− isSafeReadOnly := isSafeReadOnly(params.Command)
209+ isSafeReadOnly := false
210+ cmdLower := strings.ToLower(params.Command)
211+
212+ if !containsCommandChaining(params.Command) {
213+ for _, safe := range safeCommands {
214+ if strings.HasPrefix(cmdLower, safe) {
215+ if len(cmdLower) == len(safe) || cmdLower[len(safe)] == ' ' || cmdLower[len(safe)] == '-' {
216+ isSafeReadOnly = true
217+ break
218+ }
219+ }
220+ }
221+ }
210222
211223 sessionID := GetSessionFromContext(ctx)
212224 if sessionID == "" {
@@ -250,7 +262,7 @@ func NewBashTool(permissions permission.Service, workingDir string, attribution
250262
251263 if done {
252264 // Command failed or completed very quickly
253− _ = bgManager.Remove(bgShell.ID)
265+ bgManager.Remove(bgShell.ID)
254266
255267 interrupted := shell.IsInterrupt(execErr)
256268 exitCode := shell.ExitCode(execErr)
@@ -325,7 +337,7 @@ func NewBashTool(permissions permission.Service, workingDir string, attribution
325337 case <-ctx.Done():
326338 // Incoming context was cancelled before we moved to background
327339 // Kill the shell and return error
328− _ = bgManager.Kill(bgShell.ID)
340+ bgManager.Kill(bgShell.ID)
329341 return fantasy.ToolResponse{}, ctx.Err()
330342 }
331343 }
@@ -334,7 +346,7 @@ func NewBashTool(permissions permission.Service, workingDir string, attribution
334346 // Command completed within threshold - return synchronously
335347 // Remove from background manager since we're returning directly
336348 // Don't call Kill() as it cancels the context and corrupts the exit code
337− _ = bgManager.Remove(bgShell.ID)
349+ bgManager.Remove(bgShell.ID)
338350
339351 interrupted := shell.IsInterrupt(execErr)
340352 exitCode := shell.ExitCode(execErr)
internal/agent/tools/download.go+3−3
@@ -67,7 +67,7 @@ func NewDownloadTool(permissions permission.Service, workingDir string, client *
6767 downloadDescription(),
6868 func(ctx context.Context, params DownloadParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
6969 if params.URL == "" {
70− return fantasy.NewTextErrorResponse("url parameter is required"), nil
70+ return fantasy.NewTextErrorResponse("URL parameter is required"), nil
7171 }
7272
7373 if params.FilePath == "" {
@@ -75,7 +75,7 @@ func NewDownloadTool(permissions permission.Service, workingDir string, client *
7575 }
7676
7777 if !strings.HasPrefix(params.URL, "http://") && !strings.HasPrefix(params.URL, "https://") {
78− return fantasy.NewTextErrorResponse("url must start with http:// or https://"), nil
78+ return fantasy.NewTextErrorResponse("URL must start with http:// or https://"), nil
7979 }
8080
8181 filePath := filepathext.SmartJoin(workingDir, params.FilePath)
@@ -131,7 +131,7 @@ func NewDownloadTool(permissions permission.Service, workingDir string, client *
131131 defer resp.Body.Close()
132132
133133 if resp.StatusCode != http.StatusOK {
134− return fantasy.NewTextErrorResponse(fmt.Sprintf("request failed with status code: %d", resp.StatusCode)), nil
134+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Request failed with status code: %d", resp.StatusCode)), nil
135135 }
136136
137137 // Create parent directories if they don't exist
internal/agent/tools/edit.go+1−4
@@ -352,7 +352,7 @@ func replaceContent(edit editContext, filePath, oldString, newString string, rep
352352
353353 sessionID := GetSessionFromContext(edit.ctx)
354354 if sessionID == "" {
355− return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for editing a file")
355+ return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for edit a file")
356356 }
357357
358358 lastRead := edit.filetracker.LastReadTime(edit.ctx, sessionID, filePath)
@@ -381,9 +381,6 @@ func replaceContent(edit editContext, filePath, oldString, newString string, rep
381381
382382 if replaceAll {
383383 newContent = strings.ReplaceAll(oldContent, oldString, newString)
384− if newContent == oldContent {
385− return oldStringNotFoundErr, nil
386− }
387384 } else {
388385 index := strings.Index(oldContent, oldString)
389386 if index == -1 {
internal/agent/tools/fetch.go+12−12
@@ -60,21 +60,21 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
6060 fetchDescription(),
6161 func(ctx context.Context, params FetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
6262 if params.URL == "" {
63− return fantasy.NewTextErrorResponse("url parameter is required"), nil
63+ return fantasy.NewTextErrorResponse("URL parameter is required"), nil
6464 }
6565
6666 format := strings.ToLower(params.Format)
6767 if format != "text" && format != "markdown" && format != "html" {
68− return fantasy.NewTextErrorResponse("format must be one of: text, markdown, html"), nil
68+ return fantasy.NewTextErrorResponse("Format must be one of: text, markdown, html"), nil
6969 }
7070
7171 if !strings.HasPrefix(params.URL, "http://") && !strings.HasPrefix(params.URL, "https://") {
72− return fantasy.NewTextErrorResponse("url must start with http:// or https://"), nil
72+ return fantasy.NewTextErrorResponse("URL must start with http:// or https://"), nil
7373 }
7474
7575 sessionID := GetSessionFromContext(ctx)
7676 if sessionID == "" {
77− return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for fetching content")
77+ return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for creating a new file")
7878 }
7979
8080 p, err := permissions.Request(
@@ -124,19 +124,19 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
124124 defer resp.Body.Close()
125125
126126 if resp.StatusCode != http.StatusOK {
127− return fantasy.NewTextErrorResponse(fmt.Sprintf("request failed with status code: %d", resp.StatusCode)), nil
127+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Request failed with status code: %d", resp.StatusCode)), nil
128128 }
129129
130130 body, err := io.ReadAll(io.LimitReader(resp.Body, MaxFetchSize))
131131 if err != nil {
132− return fantasy.NewTextErrorResponse("failed to read response body: " + err.Error()), nil
132+ return fantasy.NewTextErrorResponse("Failed to read response body: " + err.Error()), nil
133133 }
134134
135135 content := string(body)
136136
137137 validUTF8 := utf8.ValidString(content)
138138 if !validUTF8 {
139− return fantasy.NewTextErrorResponse("response content is not valid UTF-8"), nil
139+ return fantasy.NewTextErrorResponse("Response content is not valid UTF-8"), nil
140140 }
141141 contentType := resp.Header.Get("Content-Type")
142142
@@ -145,7 +145,7 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
145145 if strings.Contains(contentType, "text/html") {
146146 text, err := extractTextFromHTML(content)
147147 if err != nil {
148− return fantasy.NewTextErrorResponse("failed to extract text from HTML: " + err.Error()), nil
148+ return fantasy.NewTextErrorResponse("Failed to extract text from HTML: " + err.Error()), nil
149149 }
150150 content = text
151151 }
@@ -154,7 +154,7 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
154154 if strings.Contains(contentType, "text/html") {
155155 markdown, err := convertHTMLToMarkdown(content)
156156 if err != nil {
157− return fantasy.NewTextErrorResponse("failed to convert HTML to Markdown: " + err.Error()), nil
157+ return fantasy.NewTextErrorResponse("Failed to convert HTML to Markdown: " + err.Error()), nil
158158 }
159159 content = markdown
160160 }
@@ -166,14 +166,14 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
166166 if strings.Contains(contentType, "text/html") {
167167 doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
168168 if err != nil {
169− return fantasy.NewTextErrorResponse("failed to parse HTML: " + err.Error()), nil
169+ return fantasy.NewTextErrorResponse("Failed to parse HTML: " + err.Error()), nil
170170 }
171171 body, err := doc.Find("body").Html()
172172 if err != nil {
173− return fantasy.NewTextErrorResponse("failed to extract body from HTML: " + err.Error()), nil
173+ return fantasy.NewTextErrorResponse("Failed to extract body from HTML: " + err.Error()), nil
174174 }
175175 if body == "" {
176− return fantasy.NewTextErrorResponse("no body content found in HTML"), nil
176+ return fantasy.NewTextErrorResponse("No body content found in HTML"), nil
177177 }
178178 content = "<html>\n<body>\n" + body + "\n</body>\n</html>"
179179 }
internal/agent/tools/grep.go+4−11
@@ -235,8 +235,6 @@ func searchWithRipgrep(ctx context.Context, pattern, path, include string) ([]gr
235235 }
236236
237237 var matches []grepMatch
238− // modTime is only used for sorting; stat each unique path once.
239− modTimes := make(map[string]time.Time)
240238 for line := range bytes.SplitSeq(bytes.TrimSpace(output), []byte{'\n'}) {
241239 if len(line) == 0 {
242240 continue
@@ -249,18 +247,13 @@ func searchWithRipgrep(ctx context.Context, pattern, path, include string) ([]gr
249247 continue
250248 }
251249 for _, m := range match.Data.Submatches {
252− modTime, ok := modTimes[match.Data.Path.Text]
253− if !ok {
254− fi, err := os.Stat(match.Data.Path.Text)
255− if err != nil {
256− continue // Skip files we can't access
257− }
258− modTime = fi.ModTime()
259− modTimes[match.Data.Path.Text] = modTime
250+ fi, err := os.Stat(match.Data.Path.Text)
251+ if err != nil {
252+ continue // Skip files we can't access
260253 }
261254 matches = append(matches, grepMatch{
262255 path: match.Data.Path.Text,
263− modTime: modTime,
256+ modTime: fi.ModTime(),
264257 lineNum: match.Data.LineNumber,
265258 charNum: m.Start + 1, // ensure 1-based
266259 lineText: strings.TrimSpace(match.Data.Lines.Text),
internal/agent/tools/ls.go+1−1
@@ -163,7 +163,7 @@ func ListDirectoryTree(searchPath string, params LSParams, lsConfig config.ToolL
163163 output = fmt.Sprintf("There are more than %d files in the directory. Use a more specific path or use the Glob tool to find specific files. The first %[1]d files and directories are included below.\n", maxFiles)
164164 }
165165 if depth > 0 {
166− output += fmt.Sprintf("The directory tree is shown up to a depth of %d. Use a higher depth and a specific path to see more levels.\n", cmp.Or(params.Depth, depth))
166+ output = fmt.Sprintf("The directory tree is shown up to a depth of %d. Use a higher depth and a specific path to see more levels.\n", cmp.Or(params.Depth, depth))
167167 }
168168 return output + "\n" + printTree(tree, searchPath), metadata, nil
169169}
internal/agent/tools/mcp-tools.go+2−2
@@ -99,7 +99,7 @@ func (m *Tool) Info() fantasy.ToolInfo {
9999func (m *Tool) Run(ctx context.Context, params fantasy.ToolCall) (fantasy.ToolResponse, error) {
100100 sessionID := GetSessionFromContext(ctx)
101101 if sessionID == "" {
102− return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for executing MCP tool")
102+ return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for creating a new file")
103103 }
104104
105105 // Skip permission for whitelisted Docker MCP tools.
@@ -134,7 +134,7 @@ func (m *Tool) Run(ctx context.Context, params fantasy.ToolCall) (fantasy.ToolRe
134134 case "image", "media":
135135 if !GetSupportsImagesFromContext(ctx) {
136136 modelName := GetModelNameFromContext(ctx)
137− return fantasy.NewTextErrorResponse(fmt.Sprintf("this model (%s) does not support image data.", modelName)), nil
137+ return fantasy.NewTextErrorResponse(fmt.Sprintf("This model (%s) does not support image data.", modelName)), nil
138138 }
139139
140140 var response fantasy.ToolResponse
internal/agent/tools/mcp/init.go+3−22
@@ -55,19 +55,11 @@ func (s *ClientSession) Close() error {
5555var (
5656 sessions = csync.NewMap[string, *ClientSession]()
5757 states = csync.NewMap[string, ClientInfo]()
58− // sessionLastOK records the last time a session was confirmed alive
59− // (successful ping or freshly created). Used to skip redundant pings
60− // within livenessTTL before each MCP tool call.
61− sessionLastOK = csync.NewMap[string, time.Time]()
62− broker = pubsub.NewBroker[Event]()
63− initOnce sync.Once
64− initDone = make(chan struct{})
58+ broker = pubsub.NewBroker[Event]()
59+ initOnce sync.Once
60+ initDone = make(chan struct{})
6561)
6662
67−// livenessTTL is how long a session is trusted to be alive after a successful
68−// ping/create before getOrRenewClient pings it again.
69−const livenessTTL = 30 * time.Second
70−
7163// State represents the current state of an MCP client
7264type State int
7365
@@ -268,7 +260,6 @@ func initClient(ctx context.Context, cfg *config.ConfigStore, name string, m con
268260 toolCount := updateTools(cfg, name, tools)
269261 updatePrompts(name, prompts)
270262 sessions.Set(name, session)
271− sessionLastOK.Set(name, time.Now())
272263
273264 updateState(name, StateConnected, nil, session, Counts{
274265 Tools: toolCount,
@@ -289,7 +280,6 @@ func DisableSingle(cfg *config.ConfigStore, name string) error {
289280 slog.Warn("Error closing MCP session", "name", name, "error", err)
290281 }
291282 sessions.Del(name)
292− sessionLastOK.Del(name)
293283 }
294284
295285 // Clear tools and prompts for this MCP.
@@ -309,11 +299,6 @@ func getOrRenewClient(ctx context.Context, cfg *config.ConfigStore, name string)
309299 return nil, fmt.Errorf("mcp '%s' not available", name)
310300 }
311301
312− // Skip the ping if this session was confirmed alive very recently.
313− if last, ok := sessionLastOK.Get(name); ok && time.Since(last) < livenessTTL {
314− return sess, nil
315− }
316−
317302 m := cfg.Config().MCP[name]
318303 state, _ := states.Get(name)
319304
@@ -322,10 +307,8 @@ func getOrRenewClient(ctx context.Context, cfg *config.ConfigStore, name string)
322307 defer cancel()
323308 err := sess.Ping(pingCtx, nil)
324309 if err == nil {
325− sessionLastOK.Set(name, time.Now())
326310 return sess, nil
327311 }
328− sessionLastOK.Del(name)
329312 updateState(name, StateError, maybeTimeoutErr(err, timeout), nil, state.Counts)
330313
331314 sess, err = createSession(ctx, name, m, cfg.Resolver())
@@ -335,7 +318,6 @@ func getOrRenewClient(ctx context.Context, cfg *config.ConfigStore, name string)
335318
336319 updateState(name, StateConnected, nil, sess, state.Counts)
337320 sessions.Set(name, sess)
338− sessionLastOK.Set(name, time.Now())
339321 return sess, nil
340322}
341323
@@ -353,7 +335,6 @@ func updateState(name string, state State, err error, client *ClientSession, cou
353335 info.ConnectedAt = time.Now()
354336 case StateError:
355337 sessions.Del(name)
356− sessionLastOK.Del(name)
357338 }
358339 states.Set(name, info)
359340
internal/agent/tools/mcp/tools.go−14
@@ -206,20 +206,6 @@ func ensureRawBytes(data []byte) []byte {
206206}
207207
208208func normalizeBase64Input(data []byte) []byte {
209− // Fast path: if the data is all-ASCII and contains no whitespace, there is
210− // nothing for strings.Fields to strip, so avoid copying the (potentially
211− // multi-MB) payload. Bytes >= 0x80 could be part of a non-ASCII Unicode
212− // space that strings.Fields would strip, so fall back to the slow path.
213− fast := true
214− for _, b := range data {
215− if b >= 0x80 || b == ' ' || b == '\t' || b == '\n' || b == '\v' || b == '\f' || b == '\r' {
216− fast = false
217− break
218− }
219− }
220− if fast {
221− return data
222− }
223209 normalized := strings.Join(strings.Fields(string(data)), "")
224210 return []byte(normalized)
225211}
internal/agent/tools/multiedit.go+1
@@ -445,6 +445,7 @@ func applyEditToContent(content string, edit MultiEditOperation) (string, error)
445445 }
446446
447447 newContent = content[:index] + edit.NewString + content[index+len(edit.OldString):]
448+ replacementCount = 1
448449 }
449450
450451 return newContent, nil
internal/agent/tools/references.go+8
@@ -24,6 +24,10 @@ type ReferencesParams struct {
2424 Path string `json:"path,omitempty" description:"The directory to search in. Use a directory/file to narrow down the symbol search. Defaults to the current working directory."`
2525}
2626
27+type referencesTool struct {
28+ lspManager *lsp.Manager
29+}
30+
2731const ReferencesToolName = "lsp_references"
2832
2933//go:embed references.md
@@ -87,6 +91,10 @@ func NewReferencesTool(lspManager *lsp.Manager) fantasy.AgentTool {
8791 )
8892}
8993
94+func (r *referencesTool) Name() string {
95+ return ReferencesToolName
96+}
97+
9098func find(ctx context.Context, lspManager *lsp.Manager, symbol string, match grepMatch) ([]protocol.Location, error) {
9199 absPath, err := filepath.Abs(match.path)
92100 if err != nil {
internal/agent/tools/safe.go+22−95
@@ -2,16 +2,10 @@ package tools
22
33import (
44 "runtime"
5+ "slices"
56 "strings"
6−
7− "mvdan.cc/sh/v3/syntax"
87)
98
10−// safeCommands are read-only commands that can run without a permission
11−// prompt. Entries may be multi-word (e.g. "git log") to scope a
12−// subcommand. Do NOT add wrapper commands that execute an arbitrary
13−// argument (env, time, timeout, nohup, nice, xargs, sh, bash, find -exec,
14−// ...): those would let an unapproved command ride in on a safe prefix.
159var safeCommands = []string{
1610 // Bash builtins and core utils
1711 "cal",
@@ -19,6 +13,7 @@ var safeCommands = []string{
1913 "df",
2014 "du",
2115 "echo",
16+ "env",
2217 "free",
2318 "groups",
2419 "hostname",
@@ -26,10 +21,14 @@ var safeCommands = []string{
2621 "kill",
2722 "killall",
2823 "ls",
24+ "nice",
25+ "nohup",
2926 "printenv",
3027 "ps",
3128 "pwd",
3229 "set",
30+ "time",
31+ "timeout",
3332 "top",
3433 "type",
3534 "uname",
@@ -59,6 +58,22 @@ var safeCommands = []string{
5958 "git tag",
6059}
6160
61+var chainingMetacharacters = []string{
62+ ";",
63+ "|",
64+ "&&",
65+ "$(",
66+ "`",
67+}
68+
69+// containsCommandChaining reports whether s contains shell metacharacters
70+// that enable command chaining or substitution.
71+func containsCommandChaining(s string) bool {
72+ return slices.ContainsFunc(chainingMetacharacters, func(c string) bool {
73+ return strings.Contains(s, c)
74+ })
75+}
76+
6277func init() {
6378 if runtime.GOOS == "windows" {
6479 safeCommands = append(
@@ -72,92 +87,4 @@ func init() {
7287 "where",
7388 )
7489 }
75−
76− // Precompute the tokenized form of each safe command once, so
77− // isSafeReadOnly doesn't re-split on every call.
78− safeCommandTokens = make([][]string, len(safeCommands))
79− for i, safeCmd := range safeCommands {
80− safeCommandTokens[i] = strings.Fields(safeCmd)
81− }
82−}
83−
84−// safeCommandTokens holds the tokenized form of safeCommands, populated in
85−// init after any platform-specific entries are appended.
86−var safeCommandTokens [][]string
87−
88−// isSafeReadOnly reports whether command is a single, simple, read-only
89−// command that can run without a permission prompt.
90−//
91−// It parses the command and only returns true when the whole input is
92−// exactly one simple command (no pipes, chaining, subshells, blocks, or
93−// negation), with no redirections, no backgrounding, no inline variable
94−// assignments (which could inject e.g. LD_PRELOAD), and no command or
95−// process substitution anywhere. The command name (and any subcommand
96−// tokens) must be plain literals matching an entry in safeCommands.
97−//
98−// This replaces raw-string prefix matching, which let wrapper commands
99−// and redirects/chaining smuggle unapproved commands past the gate
100−// (e.g. "time rm -rf x", "echo x > file", "echo hi & rm -rf x").
101−func isSafeReadOnly(command string) bool {
102− parser := syntax.NewParser()
103− file, err := parser.Parse(strings.NewReader(command), "")
104− if err != nil {
105− return false
106− }
107−
108− // Exactly one statement, and it must be a plain foreground command.
109− if len(file.Stmts) != 1 {
110− return false
111− }
112− stmt := file.Stmts[0]
113− if stmt.Background || stmt.Negated || len(stmt.Redirs) > 0 {
114− return false
115− }
116− call, ok := stmt.Cmd.(*syntax.CallExpr)
117− if !ok || len(call.Args) == 0 || len(call.Assigns) > 0 {
118− return false
119− }
120−
121− // Reject any command/process substitution anywhere in the statement:
122− // those execute arbitrary commands the allowlist never sees.
123− safe := true
124− syntax.Walk(file, func(node syntax.Node) bool {
125− switch node.(type) {
126− case *syntax.CmdSubst, *syntax.ProcSubst:
127− safe = false
128− return false
129− }
130− return true
131− })
132− if !safe {
133− return false
134− }
135−
136− // Extract literal argv. Non-literal words (quoting, parameter
137− // expansion, globbing) yield "" via Word.Lit(); the command name and
138− // any matched subcommand tokens must be plain literals.
139− argv := make([]string, len(call.Args))
140− for i, w := range call.Args {
141− argv[i] = w.Lit()
142− }
143− if argv[0] == "" {
144− return false
145− }
146−
147− for _, tokens := range safeCommandTokens {
148− if len(argv) < len(tokens) {
149− continue
150− }
151− match := true
152− for i, tok := range tokens {
153− if argv[i] != tok {
154− match = false
155− break
156− }
157− }
158− if match {
159− return true
160− }
161− }
162− return false
16390}
internal/agent/tools/safe_test.go+24−51
@@ -6,7 +6,7 @@ import (
66 "github.com/stretchr/testify/assert"
77)
88
9−func TestIsSafeReadOnly(t *testing.T) {
9+func TestContainsCommandChaining(t *testing.T) {
1010 t.Parallel()
1111
1212 tests := []struct {
@@ -14,61 +14,34 @@ func TestIsSafeReadOnly(t *testing.T) {
1414 input string
1515 expected bool
1616 }{
17− // Plain safe commands.
18− {"plain ls", "ls -la", true},
19− {"plain echo", "echo hello world", true},
20− {"plain pwd", "pwd", true},
21− {"git status", "git status", true},
22− {"git log with flags", "git log --oneline", true},
23− {"git config --get", "git config --get user.name", true},
24− {"echo with param expansion", "echo $HOME", true},
25− {"simple kill", "kill 1234", true},
26−
27− // Not on the allowlist.
28− {"rm", "rm -rf /tmp/x", false},
29− {"unknown", "frobnicate", false},
30− {"git push not allowed", "git push", false},
31− {"prefix is not a match (lsof)", "lsof", false},
32−
33− // Chaining / multiple statements.
34− {"pipe", "ls | grep foo", false},
35− {"and", "ls && echo done", false},
36− {"or", "ls || echo fail", false},
37− {"semicolon", "ls; echo done", false},
38− {"newline", "echo hi\nrm -rf x", false},
39− {"background then rm", "echo hi & rm -rf x", false},
40− {"rm first with and", "rm -rf / && ls", false},
41−
42− // Substitution.
43− {"command subst", "ls $(rm -rf x)", false},
44− {"backticks", "ls `rm -rf x`", false},
45− {"param default with subst", "echo ${x:-$(rm -rf x)}", false},
46−
47− // Redirection.
48− {"redirect out", "echo x > /tmp/out", false},
49− {"redirect append", "echo x >> ~/.bashrc", false},
50− {"redirect in", "cat < /etc/passwd", false},
51−
52− // Wrapper commands must not smuggle an unapproved command through.
53− {"time wrapper", "time rm -rf x", false},
54− {"env wrapper", "env FOO=bar rm -rf x", false},
55− {"timeout wrapper", "timeout 10 rm -rf x", false},
56− {"nohup wrapper", "nohup rm -rf x", false},
57− {"nice wrapper", "nice rm -rf x", false},
58−
59− // Inline assignment (e.g. LD_PRELOAD) is not safe.
60− {"inline assignment", "LD_PRELOAD=/evil.so ls", false},
61−
62− // Malformed / empty.
63− {"empty", "", false},
64− {"unbalanced quote", "echo \"unterminated", false},
17+ {"plain ls", "ls -la", false},
18+ {"plain echo", "echo hello world", false},
19+ {"plain pwd", "pwd", false},
20+ {"plain git status", "git status", false},
21+ {"ls with redirect", "ls > /tmp/out", false},
22+ {"ls with pipe", "ls | grep foo", true},
23+ {"ls with double ampersand", "ls && echo done", true},
24+ {"ls with semicolon", "ls; echo done", true},
25+ {"ls with pipe pipe", "ls || echo fail", true},
26+ {"ls with backticks", "ls `echo foo`", true},
27+ {"ls with subshell", "ls $(echo foo)", true},
28+ {"ls with background ampersand", "ls & echo done", false},
29+ {"rm -rf with && ls (rm first)", "rm -rf / && ls", true},
30+ {"redirect with ampersand gt", "ls &> /dev/null", false},
31+ {"redirect with gt ampersand", "ls >& /dev/null", false},
32+ {"simple kill", "kill 1234", false},
33+ {"kill with pipe", "kill 1234 | echo foo", true},
34+ {"git log", "git log --oneline", false},
35+ {"git log with pipe", "git log | head", true},
36+ {"empty string", "", false},
37+ {"dollar sign in argument", "echo $HOME", false},
6538 }
6639
6740 for _, tt := range tests {
6841 t.Run(tt.name, func(t *testing.T) {
6942 t.Parallel()
70− got := isSafeReadOnly(tt.input)
71− assert.Equal(t, tt.expected, got, "isSafeReadOnly(%q)", tt.input)
43+ got := containsCommandChaining(tt.input)
44+ assert.Equal(t, tt.expected, got, "containsCommandChaining(%q)", tt.input)
7245 })
7346 }
7447}
internal/agent/tools/sourcegraph.go+4−4
@@ -64,7 +64,7 @@ func NewSourcegraphTool(client *http.Client) fantasy.AgentTool {
6464 sourcegraphDescription(),
6565 func(ctx context.Context, params SourcegraphParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
6666 if params.Query == "" {
67− return fantasy.NewTextErrorResponse("query parameter is required"), nil
67+ return fantasy.NewTextErrorResponse("Query parameter is required"), nil
6868 }
6969
7070 if params.Count <= 0 {
@@ -129,10 +129,10 @@ func NewSourcegraphTool(client *http.Client) fantasy.AgentTool {
129129 if resp.StatusCode != http.StatusOK {
130130 body, _ := io.ReadAll(resp.Body)
131131 if len(body) > 0 {
132− return fantasy.NewTextErrorResponse(fmt.Sprintf("request failed with status code: %d, response: %s", resp.StatusCode, string(body))), nil
132+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Request failed with status code: %d, response: %s", resp.StatusCode, string(body))), nil
133133 }
134134
135− return fantasy.NewTextErrorResponse(fmt.Sprintf("request failed with status code: %d", resp.StatusCode)), nil
135+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Request failed with status code: %d", resp.StatusCode)), nil
136136 }
137137 body, err := io.ReadAll(resp.Body)
138138 if err != nil {
@@ -146,7 +146,7 @@ func NewSourcegraphTool(client *http.Client) fantasy.AgentTool {
146146
147147 formattedResults, err := formatSourcegraphResults(result, params.ContextWindow)
148148 if err != nil {
149− return fantasy.NewTextErrorResponse("failed to format results: " + err.Error()), nil
149+ return fantasy.NewTextErrorResponse("Failed to format results: " + err.Error()), nil
150150 }
151151
152152 return fantasy.NewTextResponse(formattedResults), nil
internal/agent/tools/view.go+24−25
@@ -12,7 +12,6 @@ import (
1212 "net/http"
1313 "os"
1414 "path/filepath"
15− "strconv"
1615 "strings"
1716 "time"
1817 "unicode/utf8"
@@ -177,19 +176,19 @@ func NewViewTool(
177176 }
178177
179178 if len(suggestions) > 0 {
180− return fantasy.NewTextErrorResponse(fmt.Sprintf("file not found: %s\n\nDid you mean one of these?\n%s",
179+ return fantasy.NewTextErrorResponse(fmt.Sprintf("File not found: %s\n\nDid you mean one of these?\n%s",
181180 filePath, strings.Join(suggestions, "\n"))), nil
182181 }
183182 }
184183
185− return fantasy.NewTextErrorResponse(fmt.Sprintf("file not found: %s", filePath)), nil
184+ return fantasy.NewTextErrorResponse(fmt.Sprintf("File not found: %s", filePath)), nil
186185 }
187186 return fantasy.ToolResponse{}, fmt.Errorf("error accessing file: %w", err)
188187 }
189188
190189 // Check if it's a directory
191190 if fileInfo.IsDir() {
192− return fantasy.NewTextErrorResponse(fmt.Sprintf("path is a directory, not a file: %s", filePath)), nil
191+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Path is a directory, not a file: %s", filePath)), nil
193192 }
194193
195194 // Set default limit if not provided (no limit for SKILL.md files)
@@ -204,12 +203,12 @@ func NewViewTool(
204203 isSupportedImage, mimeType := getImageMimeType(filePath)
205204 if isSupportedImage {
206205 if fileInfo.Size() > MaxViewSize {
207− return fantasy.NewTextErrorResponse(fmt.Sprintf("image file is too large (%d bytes). Maximum size is %d bytes",
206+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Image file is too large (%d bytes). Maximum size is %d bytes",
208207 fileInfo.Size(), MaxViewSize)), nil
209208 }
210209 if !GetSupportsImagesFromContext(ctx) {
211210 modelName := GetModelNameFromContext(ctx)
212− return fantasy.NewTextErrorResponse(fmt.Sprintf("this model (%s) does not support image data.", modelName)), nil
211+ return fantasy.NewTextErrorResponse(fmt.Sprintf("This model (%s) does not support image data.", modelName)), nil
213212 }
214213
215214 imageData, readErr := os.ReadFile(filePath)
@@ -237,13 +236,13 @@ func NewViewTool(
237236 if err != nil {
238237 var tooLarge contentTooLargeError
239238 if errors.As(err, &tooLarge) {
240− return fantasy.NewTextErrorResponse(fmt.Sprintf("content section is too large (%d bytes). Maximum size is %d bytes",
239+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Content section is too large (%d bytes). Maximum size is %d bytes",
241240 tooLarge.Size, tooLarge.Max)), nil
242241 }
243242 return fantasy.ToolResponse{}, fmt.Errorf("error reading file: %w", err)
244243 }
245244 if !utf8.ValidString(content) {
246− return fantasy.NewTextErrorResponse("file content is not valid UTF-8"), nil
245+ return fantasy.NewTextErrorResponse("File content is not valid UTF-8"), nil
247246 }
248247
249248 openInLSPs(ctx, lspManager, filePath)
@@ -253,7 +252,7 @@ func NewViewTool(
253252
254253 if hasMore {
255254 output += fmt.Sprintf("\n\n(File has more lines. Use 'offset' parameter to read beyond line %d)",
256− params.Offset+strings.Count(content, "\n")+1)
255+ params.Offset+len(strings.Split(content, "\n")))
257256 }
258257 output += "\n</file>\n"
259258 output += getDiagnostics(filePath, lspManager)
@@ -285,24 +284,24 @@ func addLineNumbers(content string, startLine int) string {
285284 return ""
286285 }
287286
288− var b strings.Builder
289− for i, line := range strings.Split(content, "\n") {
290− if i > 0 {
291− b.WriteByte('\n')
292− }
287+ lines := strings.Split(content, "\n")
288+
289+ var result []string
290+ for i, line := range lines {
293291 line = strings.TrimSuffix(line, "\r")
294292
295− numStr := strconv.Itoa(i + startLine)
296− // Right-align to a width of 6 (numbers with >=6 digits are unpadded).
297− for pad := 6 - len(numStr); pad > 0; pad-- {
298− b.WriteByte(' ')
293+ lineNum := i + startLine
294+ numStr := fmt.Sprintf("%d", lineNum)
295+
296+ if len(numStr) >= 6 {
297+ result = append(result, fmt.Sprintf("%s|%s", numStr, line))
298+ } else {
299+ paddedNum := fmt.Sprintf("%6s", numStr)
300+ result = append(result, fmt.Sprintf("%s|%s", paddedNum, line))
299301 }
300− b.WriteString(numStr)
301− b.WriteByte('|')
302− b.WriteString(line)
303302 }
304303
305− return b.String()
304+ return strings.Join(result, "\n")
306305}
307306
308307func readTextFile(filePath string, offset, limit, maxContentSize int) (string, bool, error) {
@@ -448,12 +447,12 @@ func readBuiltinFile(params ViewParams, skillTracker *skills.Tracker) (fantasy.T
448447
449448 data, err := fs.ReadFile(builtinFS, embeddedPath)
450449 if err != nil {
451− return fantasy.NewTextErrorResponse(fmt.Sprintf("builtin file not found: %s", params.FilePath)), nil
450+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Builtin file not found: %s", params.FilePath)), nil
452451 }
453452
454453 content := string(data)
455454 if !utf8.ValidString(content) {
456− return fantasy.NewTextErrorResponse("file content is not valid UTF-8"), nil
455+ return fantasy.NewTextErrorResponse("File content is not valid UTF-8"), nil
457456 }
458457
459458 limit := params.Limit
@@ -462,7 +461,7 @@ func readBuiltinFile(params ViewParams, skillTracker *skills.Tracker) (fantasy.T
462461 }
463462
464463 lines := strings.Split(content, "\n")
465− offset := max(0, min(params.Offset, len(lines)))
464+ offset := min(params.Offset, len(lines))
466465 lines = lines[offset:]
467466
468467 hasMore := len(lines) > limit
internal/agent/tools/view_test.go+2−2
@@ -151,7 +151,7 @@ func TestViewToolBlocksOversizedReturnedSections(t *testing.T) {
151151 })
152152
153153 require.True(t, resp.IsError)
154− require.Contains(t, resp.Content, "content section is too large")
154+ require.Contains(t, resp.Content, "Content section is too large")
155155}
156156
157157func TestViewToolBlocksOversizedImages(t *testing.T) {
@@ -169,7 +169,7 @@ func TestViewToolBlocksOversizedImages(t *testing.T) {
169169 })
170170
171171 require.True(t, resp.IsError)
172− require.Contains(t, resp.Content, "image file is too large")
172+ require.Contains(t, resp.Content, "Image file is too large")
173173}
174174
175175func TestReadTextFileEnforcesMaxContentSize(t *testing.T) {
internal/agent/tools/web_fetch.go+4−4
@@ -45,7 +45,7 @@ func NewWebFetchTool(workingDir string, client *http.Client) fantasy.AgentTool {
4545
4646 content, err := FetchURLAndConvert(ctx, client, params.URL)
4747 if err != nil {
48− return fantasy.NewTextErrorResponse(fmt.Sprintf("failed to fetch URL: %s", err)), nil
48+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to fetch URL: %s", err)), nil
4949 }
5050
5151 hasLargeContent := len(content) > LargeContentThreshold
@@ -54,16 +54,16 @@ func NewWebFetchTool(workingDir string, client *http.Client) fantasy.AgentTool {
5454 if hasLargeContent {
5555 tempFile, err := os.CreateTemp(workingDir, "page-*.md")
5656 if err != nil {
57− return fantasy.NewTextErrorResponse(fmt.Sprintf("failed to create temporary file: %s", err)), nil
57+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to create temporary file: %s", err)), nil
5858 }
5959 tempFilePath := tempFile.Name()
6060
6161 if _, err := tempFile.WriteString(content); err != nil {
6262 _ = tempFile.Close() // Best effort close
63− return fantasy.NewTextErrorResponse(fmt.Sprintf("failed to write content to file: %s", err)), nil
63+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to write content to file: %s", err)), nil
6464 }
6565 if err := tempFile.Close(); err != nil {
66− return fantasy.NewTextErrorResponse(fmt.Sprintf("failed to close temporary file: %s", err)), nil
66+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to close temporary file: %s", err)), nil
6767 }
6868
6969 fmt.Fprintf(&result, "Fetched content from %s (large page)\n\n", params.URL)
internal/agent/tools/web_search.go+1−1
@@ -53,7 +53,7 @@ func NewWebSearchTool(client *http.Client) fantasy.AgentTool {
5353 results, err := searchDuckDuckGo(ctx, client, params.Query, maxResults)
5454 slog.Debug("Web search completed", "query", params.Query, "results", len(results), "err", err)
5555 if err != nil {
56− return fantasy.NewTextErrorResponse("failed to search: " + err.Error()), nil
56+ return fantasy.NewTextErrorResponse("Failed to search: " + err.Error()), nil
5757 }
5858
5959 return fantasy.NewTextResponse(formatSearchResults(results)), nil
internal/agent/tools/write.go+14−15
@@ -66,25 +66,21 @@ func NewWriteTool(
6666 filePath := filepathext.SmartJoin(workingDir, params.FilePath)
6767
6868 fileInfo, err := os.Stat(filePath)
69− oldContent := ""
7069 if err == nil {
7170 if fileInfo.IsDir() {
72− return fantasy.NewTextErrorResponse(fmt.Sprintf("path is a directory, not a file: %s", filePath)), nil
71+ return fantasy.NewTextErrorResponse(fmt.Sprintf("Path is a directory, not a file: %s", filePath)), nil
7372 }
7473
7574 modTime := fileInfo.ModTime().Truncate(time.Second)
7675 lastRead := filetracker.LastReadTime(ctx, sessionID, filePath)
7776 if modTime.After(lastRead) {
78− return fantasy.NewTextErrorResponse(fmt.Sprintf("file %s has been modified since it was last read.\nLast modification: %s\nLast read: %s\n\nPlease read the file again before modifying it.",
77+ return fantasy.NewTextErrorResponse(fmt.Sprintf("File %s has been modified since it was last read.\nLast modification: %s\nLast read: %s\n\nPlease read the file again before modifying it.",
7978 filePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339))), nil
8079 }
8180
82− oldBytes, readErr := os.ReadFile(filePath)
83− if readErr == nil {
84− oldContent = string(oldBytes)
85− if oldContent == params.Content {
86− return fantasy.NewTextErrorResponse(fmt.Sprintf("file %s already contains the exact content. No changes made.", filePath)), nil
87− }
81+ oldContent, readErr := os.ReadFile(filePath)
82+ if readErr == nil && string(oldContent) == params.Content {
83+ return fantasy.NewTextErrorResponse(fmt.Sprintf("File %s already contains the exact content. No changes made.", filePath)), nil
8884 }
8985 } else if !os.IsNotExist(err) {
9086 return fantasy.ToolResponse{}, fmt.Errorf("error checking file: %w", err)
@@ -95,17 +91,20 @@ func NewWriteTool(
9591 return fantasy.ToolResponse{}, fmt.Errorf("error creating directory: %w", err)
9692 }
9793
94+ oldContent := ""
95+ if fileInfo != nil && !fileInfo.IsDir() {
96+ oldBytes, readErr := os.ReadFile(filePath)
97+ if readErr == nil {
98+ oldContent = string(oldBytes)
99+ }
100+ }
101+
98102 diff, additions, removals := diff.GenerateDiff(
99103 oldContent,
100104 params.Content,
101105 strings.TrimPrefix(filePath, workingDir),
102106 )
103107
104− description := fmt.Sprintf("Create file %s", filePath)
105− if fileInfo != nil && !fileInfo.IsDir() {
106− description = fmt.Sprintf("Overwrite file %s", filePath)
107− }
108−
109108 p, err := permissions.Request(
110109 ctx,
111110 permission.CreatePermissionRequest{
@@ -114,7 +113,7 @@ func NewWriteTool(
114113 ToolCallID: call.ID,
115114 ToolName: WriteToolName,
116115 Action: "write",
117− Description: description,
116+ Description: fmt.Sprintf("Create file %s", filePath),
118117 Params: WritePermissionsParams{
119118 FilePath: filePath,
120119 OldContent: oldContent,
internal/agent/usage_fallback.go+9
@@ -33,6 +33,15 @@ func fallbackStepUsage(messages []fantasy.Message, step fantasy.StepResult) (fan
3333 }, true
3434}
3535
36+func cloneFantasyMessages(messages []fantasy.Message) []fantasy.Message {
37+ cloned := make([]fantasy.Message, len(messages))
38+ for i, msg := range messages {
39+ cloned[i] = msg
40+ cloned[i].Content = append([]fantasy.MessagePart(nil), msg.Content...)
41+ }
42+ return cloned
43+}
44+
3645func estimateMessageTokens(messages []fantasy.Message) int64 {
3746 var tokens int64
3847 for _, msg := range messages {
internal/app/app.go+6−77
@@ -27,7 +27,6 @@ import (
2727 "github.com/charmbracelet/crush/internal/event"
2828 "github.com/charmbracelet/crush/internal/filetracker"
2929 "github.com/charmbracelet/crush/internal/format"
30− "github.com/charmbracelet/crush/internal/herdr"
3130 "github.com/charmbracelet/crush/internal/history"
3231 "github.com/charmbracelet/crush/internal/log"
3332 "github.com/charmbracelet/crush/internal/lsp"
@@ -74,13 +73,8 @@ type App struct {
7473 tuiWG *sync.WaitGroup
7574
7675 // global context and cleanup functions
77− globalCtx context.Context
78− cleanupFuncs []func(context.Context) error
79− // bgCancel cancels the context passed to the fire-and-forget
80− // goroutines launched from New (update check, MCP init, LSP tracking);
81− // bgWG tracks them so Shutdown can join them before closing the DB.
82− bgCancel context.CancelFunc
83− bgWG sync.WaitGroup
76+ globalCtx context.Context
77+ cleanupFuncs []func(context.Context) error
8478 agentNotifications *pubsub.Broker[notify.Notification]
8579 // runCompletions is the authoritative per-run completion signal,
8680 // emitted once per top-level agent turn after all message
@@ -89,10 +83,6 @@ type App struct {
8983 // drive their exit on a deterministic, payload-bearing event
9084 // instead of guessing from message finish parts.
9185 runCompletions *pubsub.Broker[notify.RunComplete]
92−
93− // herdrClient reports agent state to herdr when running inside
94− // a herdr-managed pane. Nil when not in a herdr environment.
95− herdrClient *herdr.Client
9686}
9787
9888// New initializes a new application instance. skillsMgr carries the
@@ -139,25 +129,10 @@ func New(ctx context.Context, conn *sql.DB, store *config.ConfigStore, skillsMgr
139129 slog.Warn("Clipboard initialization failed", "error", err)
140130 }
141131
142− // Background goroutines run on a cancelable context so Shutdown can
143− // stop and join them (see goBackground / Shutdown) rather than leaking
144− // them past teardown.
145− bgCtx, bgCancel := context.WithCancel(ctx)
146− app.bgCancel = bgCancel
147−
148132 // Check for updates in the background.
149− app.goBackground(func() { app.checkForUpdates(bgCtx) })
133+ go app.checkForUpdates(ctx)
150134
151− app.goBackground(func() { mcp.Initialize(bgCtx, app.Permissions, store) })
152−
153− // Start herdr integration when running inside a herdr pane.
154− app.herdrClient = herdr.Init()
155− herdr.BridgeLocal(ctx, app.herdrClient, herdr.BridgeSources{
156− PermRequests: app.Permissions,
157− PermNotifications: app.Permissions,
158− RunCompletions: app.runCompletions,
159− Messages: app.Messages,
160− })
135+ go mcp.Initialize(ctx, app.Permissions, store)
161136
162137 // Release the shared database connection on shutdown. The pool
163138 // closes the underlying *sql.DB when the last reference is released.
@@ -186,22 +161,11 @@ func New(ctx context.Context, conn *sql.DB, store *config.ConfigStore, skillsMgr
186161 client.SetDiagnosticsCallback(updateLSPDiagnostics)
187162 updateLSPState(name, client.GetServerState(), nil, client, 0)
188163 })
189− app.goBackground(app.LSPManager.TrackConfigured)
164+ go app.LSPManager.TrackConfigured()
190165
191166 return app, nil
192167}
193168
194−// goBackground runs fn in a goroutine tracked by bgWG so Shutdown can join
195−// it. Callers whose work is cancelable should close over the app's
196−// background context (see New) so bgCancel actually interrupts them.
197−func (app *App) goBackground(fn func()) {
198− app.bgWG.Add(1)
199− go func() {
200− defer app.bgWG.Done()
201− fn()
202− }()
203−}
204−
205169// Config returns the pure-data configuration.
206170func (app *App) Config() *config.Config {
207171 return app.config.Config()
@@ -236,15 +200,6 @@ func (app *App) RunCompletions() *pubsub.Broker[notify.RunComplete] {
236200 return app.runCompletions
237201}
238202
239−// ReportCurrentSession tells herdr which session the user is now
240−// viewing so it can persist a resumable reference for the pane. Safe
241−// to call when not running inside a herdr pane; the underlying client
242−// is nil-safe. Call this whenever the active session changes (load,
243−// new, or select).
244−func (app *App) ReportCurrentSession(sessionID string) {
245− app.herdrClient.SetSessionID(sessionID)
246−}
247−
248203// resolveSession resolves which session to use for a non-interactive run
249204// If continueSessionID is set, it looks up that session by ID
250205// If useLast is set, it returns the most recently updated top-level session
@@ -342,9 +297,7 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt,
342297 }
343298
344299 // force update of agent models before running so mcp tools are loaded
345− if err := app.AgentCoordinator.UpdateModels(ctx); err != nil {
346− slog.Warn("failed to update agent models", "error", err)
347− }
300+ app.AgentCoordinator.UpdateModels(ctx)
348301
349302 defer stopSpinner()
350303
@@ -363,9 +316,6 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt,
363316 // session.
364317 app.Permissions.AutoApproveSession(sess.ID)
365318
366− // Report session identity to herdr.
367− app.ReportCurrentSession(sess.ID)
368−
369319 type response struct {
370320 result *fantasy.AgentResult
371321 err error
@@ -714,24 +664,6 @@ func (app *App) Shutdown() {
714664 }
715665 }
716666
717− // Cancel and join the fire-and-forget goroutines from New before the
718− // DB/MCP cleanup below, so none keep touching a closing store, DB, or
719− // MCP registry. Bounded by shutdownCtx so a goroutine that ignores
720− // cancellation can't hang shutdown.
721− if app.bgCancel != nil {
722− app.bgCancel()
723− }
724− bgJoined := make(chan struct{})
725− go func() {
726− app.bgWG.Wait()
727− close(bgJoined)
728− }()
729− select {
730− case <-bgJoined:
731− case <-shutdownCtx.Done():
732− slog.Warn("Timed out waiting for background goroutines on shutdown")
733− }
734−
735667 // Now run remaining cleanup tasks in parallel.
736668 var wg sync.WaitGroup
737669
@@ -745,9 +677,6 @@ func (app *App) Shutdown() {
745677 shell.GetBackgroundShellManager().KillAll(shutdownCtx)
746678 })
747679
748− // Close herdr client to stop its background writer.
749− app.herdrClient.Close()
750−
751680 // Shutdown all LSP clients.
752681 wg.Go(func() {
753682 app.LSPManager.KillAll(shutdownCtx)
internal/backend/agent.go+10−1
@@ -8,6 +8,7 @@ import (
88 "github.com/charmbracelet/crush/internal/agent"
99 "github.com/charmbracelet/crush/internal/agent/notify"
1010 "github.com/charmbracelet/crush/internal/config"
11+ "github.com/charmbracelet/crush/internal/message"
1112 "github.com/charmbracelet/crush/internal/proto"
1213 "github.com/charmbracelet/crush/internal/pubsub"
1314 "github.com/charmbracelet/crush/internal/shell"
@@ -252,7 +253,15 @@ func (b *Backend) RunShellCommand(ctx context.Context, workspaceID string, req p
252253 var persist shell.PersistFunc
253254 if req.SessionID != "" {
254255 persist = func(cmd, output string, exitCode int) error {
255− return shell.PersistOutput(ctx, ws.Messages, req.SessionID, cmd, output, exitCode)
256+ _, err := ws.Messages.Create(ctx, req.SessionID, message.CreateMessageParams{
257+ Role: message.User,
258+ Parts: []message.ContentPart{message.ShellCommand{
259+ Command: cmd,
260+ Output: output,
261+ ExitCode: exitCode,
262+ }},
263+ })
264+ return err
256265 }
257266 }
258267
internal/backend/backend.go−18
@@ -649,24 +649,6 @@ func (w *Workspace) AttachedClientsForSession(sessionID string) int {
649649 return n
650650}
651651
652−// AttachedClientCountsBySession returns, for every session with at least
653−// one attached client, the number of clients currently viewing it. Only
654−// clients with a live SSE stream (streams > 0) are counted; hold-only
655−// clients (streams == 0) do not contribute. It acquires [clientsMu] once
656−// and iterates the client set a single time, so callers that need counts
657−// for many sessions avoid an O(sessions x clients) scan.
658−func (w *Workspace) AttachedClientCountsBySession() map[string]int {
659− w.clientsMu.Lock()
660− defer w.clientsMu.Unlock()
661− counts := make(map[string]int)
662− for _, cs := range w.clients {
663− if cs.streams > 0 {
664− counts[cs.currentSessionID]++
665− }
666− }
667− return counts
668−}
669−
670652// GetWorkspaceProto returns the proto representation of a workspace.
671653func (b *Backend) GetWorkspaceProto(id string) (proto.Workspace, error) {
672654 ws, err := b.GetWorkspace(id)
internal/backend/config_test.go+9−7
@@ -81,13 +81,15 @@ func TestRemoveConfigField_PublishesConfigChanged(t *testing.T) {
8181
8282func TestUpdatePreferredModel_PublishesConfigChanged(t *testing.T) {
8383 if raceEnabled {
84− // The backend package has pre-existing, flaky data races under
85− // -race in its agent-run/attach/hold lifecycle that surface only
86− // under full-package timing pressure. This test spins up the full
87− // coordinator, so it is skipped under -race to keep CI stable
88− // until that concurrency debt is addressed. The mutator → publish
89− // wiring is unit-tested via publishConfigChanged regardless.
90− t.Skip("skipped under -race: pre-existing backend lifecycle races")
84+ // UpdatePreferredModel writes config.Models concurrently
85+ // with the agent coordinator's async sub-agent builder
86+ // that reads it via buildAgentModels. That race is
87+ // pre-existing in the codebase and unrelated to this
88+ // item; ConfigStore mutations are not currently
89+ // synchronized against background readers in [app.App].
90+ // The mutator → publish wiring is unit-tested via
91+ // publishConfigChanged regardless.
92+ t.Skip("skipped under -race: pre-existing race between ConfigStore writes and agent coordinator startup")
9193 }
9294 b, ws, evc := newPublishingWorkspace(t)
9395
internal/backend/shell_command_test.godeleted−54
@@ -1,54 +0,0 @@
1−package backend
2−
3−import (
4− "context"
5− "testing"
6−
7− "github.com/charmbracelet/crush/internal/app"
8− "github.com/charmbracelet/crush/internal/db"
9− "github.com/charmbracelet/crush/internal/message"
10− "github.com/charmbracelet/crush/internal/proto"
11− "github.com/charmbracelet/crush/internal/session"
12− "github.com/google/uuid"
13− "github.com/stretchr/testify/require"
14−)
15−
16−func TestRunShellCommand_SkipsPersistenceForMissingSession(t *testing.T) {
17− t.Parallel()
18−
19− conn, err := db.Connect(t.Context(), t.TempDir())
20− require.NoError(t, err)
21− t.Cleanup(func() { conn.Close() })
22−
23− q := db.New(conn)
24− sessions := session.NewService(q, conn)
25− messages := message.NewService(q)
26−
27− b, _ := newTestBackend(t)
28− ws := &Workspace{
29− ID: uuid.New().String(),
30− Path: t.TempDir(),
31− resolvedPath: t.TempDir(),
32− clients: make(map[string]*clientState),
33− shutdownFn: func() {},
34− }
35− ws.App = &app.App{
36− Sessions: sessions,
37− Messages: messages,
38− }
39− ws.ctx, ws.cancel = context.WithCancel(b.ctx)
40− InsertWorkspaceForTest(b, ws)
41−
42− missingSessionID := uuid.New().String()
43− resp, err := b.RunShellCommand(t.Context(), ws.ID, proto.ShellCommandRequest{
44− SessionID: missingSessionID,
45− Command: "echo hello",
46− })
47− require.NoError(t, err)
48− require.Equal(t, "hello\n", resp.Output)
49− require.Zero(t, resp.ExitCode)
50−
51− stored, err := messages.List(t.Context(), missingSessionID)
52− require.NoError(t, err)
53− require.Empty(t, stored)
54−}
internal/client/proto.go+4−2
@@ -661,9 +661,11 @@ func (c *Client) GetConfig(ctx context.Context, id string) (*config.Config, erro
661661 return &cfg, nil
662662}
663663
664−func jsonBody(v any) io.Reader {
664+func jsonBody(v any) *bytes.Buffer {
665+ b := new(bytes.Buffer)
665666 m, _ := json.Marshal(v)
666− return bytes.NewReader(m)
667+ b.Write(m)
668+ return b
667669}
668670
669671// SaveSession updates a session in a workspace, returning a proto type.
internal/cmd/logs.go+3−3
@@ -95,7 +95,7 @@ func followLogs(ctx context.Context, logsFile string, tailLines int) error {
9595 lines = lines[len(lines)-tailLines:]
9696 }
9797 }
98− _ = t.Stop()
98+ t.Stop()
9999
100100 for _, line := range lines {
101101 printLogLine(line)
@@ -115,7 +115,7 @@ func followLogs(ctx context.Context, logsFile string, tailLines int) error {
115115 if err != nil {
116116 return fmt.Errorf("failed to tail log file: %v", err)
117117 }
118− defer func() { _ = t.Stop() }()
118+ defer t.Stop()
119119
120120 for {
121121 select {
@@ -140,7 +140,7 @@ func showLogs(logsFile string, tailLines int) error {
140140 if err != nil {
141141 return fmt.Errorf("failed to tail log file: %v", err)
142142 }
143− defer func() { _ = t.Stop() }()
143+ defer t.Stop()
144144
145145 var lines []string
146146 for line := range t.Lines {
internal/cmd/root.go+26−1
@@ -3,6 +3,7 @@ package cmd
33import (
44 "bytes"
55 "context"
6+ _ "embed"
67 "errors"
78 "fmt"
89 "io"
@@ -124,7 +125,7 @@ crush --continue
124125 event.AppInitialized()
125126
126127 com := common.DefaultCommon(ws)
127− model := ui.New(cmd.Context(), com, sessionID, continueLast)
128+ model := ui.New(com, sessionID, continueLast)
128129
129130 inputFilter := ui.NewFilter()
130131 var env uv.Environ = os.Environ()
@@ -839,3 +840,27 @@ func ResolveCwd(cmd *cobra.Command) (string, error) {
839840 }
840841 return cwd, nil
841842}
843+
844+func createDotCrushDir(dir string) error {
845+ if err := os.MkdirAll(dir, 0o700); err != nil {
846+ return fmt.Errorf("failed to create data directory: %q %w", dir, err)
847+ }
848+
849+ gitIgnorePath := filepath.Join(dir, ".gitignore")
850+ content, err := os.ReadFile(gitIgnorePath)
851+
852+ // create or update if old version
853+ if os.IsNotExist(err) || string(content) == oldGitIgnore {
854+ if err := os.WriteFile(gitIgnorePath, []byte(defaultGitIgnore), 0o644); err != nil {
855+ return fmt.Errorf("failed to create .gitignore file: %q %w", gitIgnorePath, err)
856+ }
857+ }
858+
859+ return nil
860+}
861+
862+//go:embed gitignore/old
863+var oldGitIgnore string
864+
865+//go:embed gitignore/default
866+var defaultGitIgnore string
internal/cmd/run.go−11
@@ -16,7 +16,6 @@ import (
1616 "github.com/charmbracelet/crush/internal/config"
1717 "github.com/charmbracelet/crush/internal/event"
1818 "github.com/charmbracelet/crush/internal/format"
19− "github.com/charmbracelet/crush/internal/herdr"
2019 "github.com/charmbracelet/crush/internal/proto"
2120 "github.com/charmbracelet/crush/internal/pubsub"
2221 "github.com/charmbracelet/crush/internal/session"
@@ -263,11 +262,6 @@ func runNonInteractive(
263262 read: make(map[string]int),
264263 }
265264
266− // Start herdr integration when running inside a herdr pane.
267− hc := herdr.Init()
268− hc.SetSessionID(sess.ID)
269− defer hc.Close()
270−
271265 defer func() {
272266 if progress && stderrTTY {
273267 _, _ = fmt.Fprintf(os.Stderr, ansi.ResetProgressBar)
@@ -287,11 +281,6 @@ func runNonInteractive(
287281 return nil
288282 }
289283
290− // Forward events to herdr if running inside a herdr pane.
291− if hev := herdr.Translate(ev); hev != nil {
292− hc.HandleEvent(hev)
293− }
294−
295284 done, err := stream.handle(ev, stopSpinner)
296285 if err != nil {
297286 return err
internal/cmd/session.go+1−1
@@ -485,7 +485,7 @@ func outputSessionHuman(ctx context.Context, cfg *config.ConfigStore, sess sessi
485485
486486 first := true
487487 for _, msg := range msgs {
488− items := chat.ExtractMessageItems(&styles, msg, toolResults, "")
488+ items := chat.ExtractMessageItems(&styles, msg, toolResults)
489489 for _, item := range items {
490490 if !first {
491491 fmt.Fprintln(&buf)
internal/config/config.go+2−13
@@ -221,15 +221,11 @@ type LSPConfig struct {
221221type TUIOptions struct {
222222 CompactMode bool `json:"compact_mode,omitempty" jsonschema:"description=Enable compact mode for the TUI interface,default=false"`
223223 DiffMode string `json:"diff_mode,omitempty" jsonschema:"description=Diff mode for the TUI interface,enum=unified,enum=split"`
224− DiffTool string `json:"diff_tool,omitempty" jsonschema:"description=External diff tool for edit diffs in the TUI,enum=builtin,enum=difftastic,default=builtin"`
225− // Theme selects the TUI color scheme. "auto" (the default) picks light
226− // or dark based on the detected terminal background; "dark"/"light"
227− // force a specific scheme.
228− Theme string `json:"theme,omitempty" jsonschema:"description=TUI color scheme,enum=auto,enum=dark,enum=light,default=auto"`
224+ // Here we can add themes later or any TUI related options
225+ //
229226
230227 Completions Completions `json:"completions,omitzero" jsonschema:"description=Completions UI options"`
231228 Transparent *bool `json:"transparent,omitempty" jsonschema:"description=Enable transparent background for the TUI interface,default=false"`
232− Scrollbar string `json:"scrollbar,omitempty" jsonschema:"description=Chat scrollbar visibility,enum=default,enum=always,enum=never,default=default"`
233229}
234230
235231// Completions defines options for the completions UI.
@@ -242,13 +238,6 @@ func (c Completions) Limits() (depth, items int) {
242238 return ptrValOr(c.MaxDepth, 0), ptrValOr(c.MaxItems, 0)
243239}
244240
245−// Scrollbar visibility options.
246−const (
247− ScrollbarDefault = "default" // Auto-hide after 2 seconds
248− ScrollbarAlways = "always" // Always show when content exceeds viewport
249− ScrollbarNever = "never" // Never show scrollbar
250−)
251−
252241type Permissions struct {
253242 AllowedTools []string `json:"allowed_tools,omitempty" jsonschema:"description=List of tools that don't require permission prompts,example=bash,example=view"`
254243}
internal/config/hyper.go+1−1
@@ -106,7 +106,7 @@ func (r realHyperClient) Get(ctx context.Context, etag string) (catwalk.Provider
106106 if err != nil {
107107 return result, fmt.Errorf("failed to make request: %w", err)
108108 }
109− defer resp.Body.Close()
109+ defer resp.Body.Close() //nolint:errcheck
110110
111111 if resp.StatusCode == http.StatusNotModified {
112112 return result, catwalk.ErrNotModified
internal/config/load.go+1−17
@@ -292,9 +292,7 @@ func (c *Config) configureProviders(ctx context.Context, store *ConfigStore, env
292292 // state is kept consistent by the Providers.Del call below; any
293293 // concurrent reload that races with this write will also see the
294294 // removal because it re-reads from disk.
295− if err := store.RemoveConfigField(ScopeGlobal, "providers.anthropic"); err != nil {
296− slog.Warn("failed to remove anthropic provider config field", "error", err)
297− }
295+ store.RemoveConfigField(ScopeGlobal, "providers.anthropic")
298296 c.Providers.Del(string(p.ID))
299297 continue
300298 case p.ID == catwalk.InferenceProviderCopilot && config.OAuthToken != nil:
@@ -971,26 +969,12 @@ func hasAWSCredentials(env env.Env) bool {
971969 return false
972970}
973971
974−// migrateDisableNotificationsOnce guards migrateDisableNotifications so the
975−// one-shot migration for the removed disable_notifications field runs at most
976−// once per process. Without this, every config reload re-read both config
977−// files and re-ran gjson scans over them even though the migration is only
978−// meaningful on first run.
979−var migrateDisableNotificationsOnce sync.Once
980−
981972// migrateDisableNotifications migrates the deprecated disable_notifications
982973// field to notification_style. It checks both the user config (~/.config) and
983974// data config (~/.local) files. If disable_notifications is true, it sets
984975// notification_style to "disabled" in the data file. Regardless of value, it
985976// removes disable_notifications from any file that contains it.
986−//
987−// It is safe to call from both Load and the reload path: the actual work runs
988−// at most once per process via migrateDisableNotificationsOnce.
989977func migrateDisableNotifications() {
990− migrateDisableNotificationsOnce.Do(migrateDisableNotificationsImpl)
991−}
992−
993−func migrateDisableNotificationsImpl() {
994978 globalConfig := GlobalConfig()
995979 dataConfig := GlobalConfigData()
996980
internal/config/resolve.go−11
@@ -3,7 +3,6 @@ package config
33import (
44 "context"
55 "fmt"
6− "strings"
76 "time"
87
98 "github.com/charmbracelet/crush/internal/env"
@@ -95,16 +94,6 @@ func (r *shellVariableResolver) ResolveValue(value string) (string, error) {
9594 return "", fmt.Errorf("invalid value format: %s", value)
9695 }
9796
98− // Fast path: a value with no shell-expansion metacharacters expands to
99− // itself, so we can skip materialising the process environment via
100− // r.env.Env() (which copies all of os.Environ()) and the whole shell
101− // round-trip. The metacharacter set mirrors shell.ExpandValue's own
102− // fast-path check ($, backtick, backslash, and both quote styles); any
103− // value lacking all of them is a literal and cannot expand.
104− if !strings.ContainsAny(value, "$`\\'\"") {
105− return value, nil
106− }
107−
10897 ctx, cancel := context.WithTimeout(context.Background(), resolveTimeout)
10998 defer cancel()
11099
internal/config/store.go+18−41
@@ -133,28 +133,19 @@ func (s *ConfigStore) WorkingDir() string {
133133
134134// Resolver returns the variable resolver.
135135func (s *ConfigStore) Resolver() VariableResolver {
136− s.configMu.RLock()
137− defer s.configMu.RUnlock()
138136 return s.resolver
139137}
140138
141139// Resolve resolves a variable reference using the configured resolver.
142140func (s *ConfigStore) Resolve(key string) (string, error) {
143− // Snapshot the resolver under the lock, then resolve without it:
144− // ResolveValue may be slow and can call back into the store.
145− s.configMu.RLock()
146− resolver := s.resolver
147− s.configMu.RUnlock()
148− if resolver == nil {
141+ if s.resolver == nil {
149142 return "", fmt.Errorf("no variable resolver configured")
150143 }
151− return resolver.ResolveValue(key)
144+ return s.resolver.ResolveValue(key)
152145}
153146
154147// KnownProviders returns the list of known providers.
155148func (s *ConfigStore) KnownProviders() []catwalk.Provider {
156− s.configMu.RLock()
157− defer s.configMu.RUnlock()
158149 return s.knownProviders
159150}
160151
@@ -170,8 +161,6 @@ func (s *ConfigStore) Overrides() *RuntimeOverrides {
170161
171162// LoadedPaths returns the config file paths that were successfully loaded.
172163func (s *ConfigStore) LoadedPaths() []string {
173− s.configMu.RLock()
174− defer s.configMu.RUnlock()
175164 return slices.Clone(s.loadedPaths)
176165}
177166
@@ -244,13 +233,10 @@ func (s *ConfigStore) atomicWrite(scope Scope, fn func(current []byte) ([]byte,
244233func (s *ConfigStore) configPath(scope Scope) (string, error) {
245234 switch scope {
246235 case ScopeWorkspace:
247− s.configMu.RLock()
248− workspacePath := s.workspacePath
249− s.configMu.RUnlock()
250− if workspacePath == "" {
236+ if s.workspacePath == "" {
251237 return "", ErrNoWorkspaceConfig
252238 }
253− return workspacePath, nil
239+ return s.workspacePath, nil
254240 default:
255241 return s.globalDataPath, nil
256242 }
@@ -498,11 +484,8 @@ func (s *ConfigStore) SetProviderAPIKey(scope Scope, providerID string, apiKey a
498484 return nil
499485 }
500486
501− s.configMu.RLock()
502− knownProviders := s.knownProviders
503− s.configMu.RUnlock()
504487 var foundProvider *catwalk.Provider
505− for _, p := range knownProviders {
488+ for _, p := range s.knownProviders {
506489 if string(p.ID) == providerID {
507490 foundProvider = &p
508491 break
@@ -777,7 +760,7 @@ func (s *ConfigStore) ImportCopilot() (*oauth.Token, bool) {
777760 }
778761
779762 slog.Info("Found existing GitHub Copilot token on disk. Authenticating...")
780− token, err := copilot.RefreshToken(context.Background(), diskToken)
763+ token, err := copilot.RefreshToken(context.TODO(), diskToken)
781764 if err != nil {
782765 slog.Error("Unable to import GitHub Copilot token", "error", err)
783766 return nil, false
@@ -902,11 +885,8 @@ func (s *ConfigStore) CaptureStalenessSnapshot(paths []string) {
902885 }
903886
904887 // Also track workspace and global config paths if set
905− s.configMu.RLock()
906− workspacePath := s.workspacePath
907− s.configMu.RUnlock()
908− if workspacePath != "" {
909− abs, err := filepath.Abs(workspacePath)
888+ if s.workspacePath != "" {
889+ abs, err := filepath.Abs(s.workspacePath)
910890 if err == nil {
911891 seen[abs] = struct{}{}
912892 }
@@ -926,9 +906,7 @@ func (s *ConfigStore) CaptureStalenessSnapshot(paths []string) {
926906 slices.Sort(s.trackedConfigPaths)
927907
928908 // Capture initial snapshots
929− if err := s.RefreshStalenessSnapshot(); err != nil {
930− slog.Warn("failed to refresh staleness snapshot", "error", err)
931− }
909+ s.RefreshStalenessSnapshot()
932910}
933911
934912// captureStalenessSnapshot is an alias for CaptureStalenessSnapshot for internal use.
@@ -988,6 +966,9 @@ func (s *ConfigStore) reloadFromDiskLocked(ctx context.Context) error {
988966 return fmt.Errorf("invalid hook configuration on reload: %w", err)
989967 }
990968
969+ // Preserve runtime overrides
970+ overrides := s.overrides
971+
991972 // Reconfigure providers
992973 env := env.New()
993974 resolver := NewShellVariableResolver(env)
@@ -1005,19 +986,16 @@ func (s *ConfigStore) reloadFromDiskLocked(ctx context.Context) error {
1005986 oldLoadedPaths := s.loadedPaths
1006987 oldResolver := s.resolver
1007988 oldKnownProviders := s.knownProviders
989+ oldOverrides := s.overrides
1008990 oldWorkspacePath := s.workspacePath
1009991
1010− // Update store state BEFORE running model/agent setup (so they see new
1011− // config). All reload-mutated fields are published atomically under
1012− // configMu so the lock-free public readers (Resolver, KnownProviders,
1013− // LoadedPaths, configPath) never observe a torn update.
1014− s.configMu.Lock()
1015− s.config = cfg
992+ // Update store state BEFORE running model/agent setup (so they see new config)
993+ s.setConfig(cfg)
1016994 s.loadedPaths = loadedPaths
1017995 s.resolver = resolver
1018996 s.knownProviders = providers
997+ s.overrides = overrides
1019998 s.workspacePath = workspacePath
1020− s.configMu.Unlock()
1021999
10221000 // Mirror startup flow: setup models and agents against NEW config.
10231001 var setupErr error
@@ -1036,13 +1014,12 @@ func (s *ConfigStore) reloadFromDiskLocked(ctx context.Context) error {
10361014
10371015 // Rollback on setup failure
10381016 if setupErr != nil {
1039− s.configMu.Lock()
1040− s.config = oldConfig
1017+ s.setConfig(oldConfig)
10411018 s.loadedPaths = oldLoadedPaths
10421019 s.resolver = oldResolver
10431020 s.knownProviders = oldKnownProviders
1021+ s.overrides = oldOverrides
10441022 s.workspacePath = oldWorkspacePath
1045− s.configMu.Unlock()
10461023 return setupErr
10471024 }
10481025
internal/csync/maps.go+5−20
@@ -75,30 +75,15 @@ func (m *Map[K, V]) Len() int {
7575 return len(m.inner)
7676}
7777
78−// GetOrSet gets and returns the value for key if it exists, otherwise it
79−// executes fn, stores its return value for key, and returns it.
80−//
81−// The stored value and the returned value are always the same: if two
82−// goroutines race on a missing key, exactly one fn result is stored and
83−// every caller receives that stored value (the losers discard their own
84−// fn result). This keeps singleton values (a mutex, a client, a cache
85−// entry others mutate) consistent. fn may run more than once under
86−// contention, so it must be side-effect-free and safe to call redundantly.
78+// GetOrSet gets and returns the key if it exists, otherwise, it executes the
79+// given function, set its return value for the given key, and returns it.
8780func (m *Map[K, V]) GetOrSet(key K, fn func() V) V {
88− if got, ok := m.Get(key); ok {
81+ got, ok := m.Get(key)
82+ if ok {
8983 return got
9084 }
91− // Compute outside the lock so a slow fn (I/O, parsing) does not
92− // serialize other map users.
9385 value := fn()
94− m.mu.Lock()
95− defer m.mu.Unlock()
96− // Re-check under the write lock: another goroutine may have won the
97− // race between our Get above and acquiring the lock.
98− if got, ok := m.inner[key]; ok {
99− return got
100− }
101− m.inner[key] = value
86+ m.Set(key, value)
10287 return value
10388}
10489
internal/db/db.go+4−24
@@ -1,6 +1,6 @@
11// Code generated by sqlc. DO NOT EDIT.
22// versions:
3−// sqlc v1.31.1
3+// sqlc v1.30.0
44
55package db
66
@@ -11,10 +11,10 @@ import (
1111)
1212
1313type DBTX interface {
14− ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
14+ ExecContext(context.Context, string, ...any) (sql.Result, error)
1515 PrepareContext(context.Context, string) (*sql.Stmt, error)
16− QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
17− QueryRowContext(context.Context, string, ...interface{}) *sql.Row
16+ QueryContext(context.Context, string, ...any) (*sql.Rows, error)
17+ QueryRowContext(context.Context, string, ...any) *sql.Row
1818}
1919
2020func New(db DBTX) *Queries {
@@ -66,9 +66,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
6666 if q.getLastSessionStmt, err = db.PrepareContext(ctx, getLastSession); err != nil {
6767 return nil, fmt.Errorf("error preparing query GetLastSession: %w", err)
6868 }
69− if q.getMaxFileVersionByPathStmt, err = db.PrepareContext(ctx, getMaxFileVersionByPath); err != nil {
70− return nil, fmt.Errorf("error preparing query GetMaxFileVersionByPath: %w", err)
71− }
7269 if q.getMessageStmt, err = db.PrepareContext(ctx, getMessage); err != nil {
7370 return nil, fmt.Errorf("error preparing query GetMessage: %w", err)
7471 }
@@ -111,9 +108,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
111108 if q.listMessagesBySessionStmt, err = db.PrepareContext(ctx, listMessagesBySession); err != nil {
112109 return nil, fmt.Errorf("error preparing query ListMessagesBySession: %w", err)
113110 }
114− if q.listMessagesBySessionAfterStmt, err = db.PrepareContext(ctx, listMessagesBySessionAfter); err != nil {
115− return nil, fmt.Errorf("error preparing query ListMessagesBySessionAfter: %w", err)
116− }
117111 if q.listNewFilesStmt, err = db.PrepareContext(ctx, listNewFiles); err != nil {
118112 return nil, fmt.Errorf("error preparing query ListNewFiles: %w", err)
119113 }
@@ -216,11 +210,6 @@ func (q *Queries) Close() error {
216210 err = fmt.Errorf("error closing getLastSessionStmt: %w", cerr)
217211 }
218212 }
219− if q.getMaxFileVersionByPathStmt != nil {
220− if cerr := q.getMaxFileVersionByPathStmt.Close(); cerr != nil {
221− err = fmt.Errorf("error closing getMaxFileVersionByPathStmt: %w", cerr)
222− }
223− }
224213 if q.getMessageStmt != nil {
225214 if cerr := q.getMessageStmt.Close(); cerr != nil {
226215 err = fmt.Errorf("error closing getMessageStmt: %w", cerr)
@@ -291,11 +280,6 @@ func (q *Queries) Close() error {
291280 err = fmt.Errorf("error closing listMessagesBySessionStmt: %w", cerr)
292281 }
293282 }
294− if q.listMessagesBySessionAfterStmt != nil {
295− if cerr := q.listMessagesBySessionAfterStmt.Close(); cerr != nil {
296− err = fmt.Errorf("error closing listMessagesBySessionAfterStmt: %w", cerr)
297− }
298− }
299283 if q.listNewFilesStmt != nil {
300284 if cerr := q.listNewFilesStmt.Close(); cerr != nil {
301285 err = fmt.Errorf("error closing listNewFilesStmt: %w", cerr)
@@ -394,7 +378,6 @@ type Queries struct {
394378 getFileReadStmt *sql.Stmt
395379 getHourDayHeatmapStmt *sql.Stmt
396380 getLastSessionStmt *sql.Stmt
397− getMaxFileVersionByPathStmt *sql.Stmt
398381 getMessageStmt *sql.Stmt
399382 getRecentActivityStmt *sql.Stmt
400383 getSessionByIDStmt *sql.Stmt
@@ -409,7 +392,6 @@ type Queries struct {
409392 listFilesBySessionStmt *sql.Stmt
410393 listLatestSessionFilesStmt *sql.Stmt
411394 listMessagesBySessionStmt *sql.Stmt
412− listMessagesBySessionAfterStmt *sql.Stmt
413395 listNewFilesStmt *sql.Stmt
414396 listSessionReadFilesStmt *sql.Stmt
415397 listSessionsStmt *sql.Stmt
@@ -439,7 +421,6 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries {
439421 getFileReadStmt: q.getFileReadStmt,
440422 getHourDayHeatmapStmt: q.getHourDayHeatmapStmt,
441423 getLastSessionStmt: q.getLastSessionStmt,
442− getMaxFileVersionByPathStmt: q.getMaxFileVersionByPathStmt,
443424 getMessageStmt: q.getMessageStmt,
444425 getRecentActivityStmt: q.getRecentActivityStmt,
445426 getSessionByIDStmt: q.getSessionByIDStmt,
@@ -454,7 +435,6 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries {
454435 listFilesBySessionStmt: q.listFilesBySessionStmt,
455436 listLatestSessionFilesStmt: q.listLatestSessionFilesStmt,
456437 listMessagesBySessionStmt: q.listMessagesBySessionStmt,
457− listMessagesBySessionAfterStmt: q.listMessagesBySessionAfterStmt,
458438 listNewFilesStmt: q.listNewFilesStmt,
459439 listSessionReadFilesStmt: q.listSessionReadFilesStmt,
460440 listSessionsStmt: q.listSessionsStmt,
internal/db/files.sql.go+5−21
@@ -1,6 +1,6 @@
11// Code generated by sqlc. DO NOT EDIT.
22// versions:
3−// sqlc v1.31.1
3+// sqlc v1.30.0
44// source: files.sql
55
66package db
@@ -122,21 +122,6 @@ func (q *Queries) GetFileByPathAndSession(ctx context.Context, arg GetFileByPath
122122 return i, err
123123}
124124
125−const getMaxFileVersionByPath = `-- name: GetMaxFileVersionByPath :one
126−SELECT version
127−FROM files
128−WHERE path = ?
129−ORDER BY version DESC, created_at DESC
130−LIMIT 1
131−`
132−
133−func (q *Queries) GetMaxFileVersionByPath(ctx context.Context, path string) (int64, error) {
134− row := q.queryRow(ctx, q.getMaxFileVersionByPathStmt, getMaxFileVersionByPath, path)
135− var version int64
136− err := row.Scan(&version)
137− return version, err
138−}
139−
140125const listFilesByPath = `-- name: ListFilesByPath :many
141126SELECT id, session_id, path, content, version, created_at, updated_at
142127FROM files
@@ -217,12 +202,11 @@ const listLatestSessionFiles = `-- name: ListLatestSessionFiles :many
217202SELECT f.id, f.session_id, f.path, f.content, f.version, f.created_at, f.updated_at
218203FROM files f
219204INNER JOIN (
220− SELECT f2.path, MAX(f2.version) as max_version, MAX(f2.created_at) as max_created_at
221− FROM files f2
222− WHERE f2.session_id = ?1
223− GROUP BY f2.path
205+ SELECT path, MAX(version) as max_version, MAX(created_at) as max_created_at
206+ FROM files
207+ GROUP BY path
224208) latest ON f.path = latest.path AND f.version = latest.max_version AND f.created_at = latest.max_created_at
225−WHERE f.session_id = ?1
209+WHERE f.session_id = ?
226210ORDER BY f.path
227211`
228212
internal/db/messages.sql.go+2−50
@@ -1,6 +1,6 @@
11// Code generated by sqlc. DO NOT EDIT.
22// versions:
3−// sqlc v1.31.1
3+// sqlc v1.30.0
44// source: messages.sql
55
66package db
@@ -38,8 +38,7 @@ type CreateMessageParams struct {
3838}
3939
4040func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) (Message, error) {
41− row := q.queryRow(
42− ctx, q.createMessageStmt, createMessage,
41+ row := q.queryRow(ctx, q.createMessageStmt, createMessage,
4342 arg.ID,
4443 arg.SessionID,
4544 arg.Role,
@@ -190,53 +189,6 @@ func (q *Queries) ListMessagesBySession(ctx context.Context, sessionID string) (
190189 return items, nil
191190}
192191
193−const listMessagesBySessionAfter = `-- name: ListMessagesBySessionAfter :many
194−SELECT m.id, m.session_id, m.role, m.parts, m.model, m.created_at, m.updated_at, m.finished_at, m.provider, m.is_summary_message
195−FROM messages m
196−WHERE m.session_id = ?
197− AND m.created_at >= (SELECT m2.created_at FROM messages m2 WHERE m2.id = ?)
198−ORDER BY m.created_at ASC
199−`
200−
201−type ListMessagesBySessionAfterParams struct {
202− SessionID string `json:"session_id"`
203− ID string `json:"id"`
204−}
205−
206−func (q *Queries) ListMessagesBySessionAfter(ctx context.Context, arg ListMessagesBySessionAfterParams) ([]Message, error) {
207− rows, err := q.query(ctx, q.listMessagesBySessionAfterStmt, listMessagesBySessionAfter, arg.SessionID, arg.ID)
208− if err != nil {
209− return nil, err
210− }
211− defer rows.Close()
212− items := []Message{}
213− for rows.Next() {
214− var i Message
215− if err := rows.Scan(
216− &i.ID,
217− &i.SessionID,
218− &i.Role,
219− &i.Parts,
220− &i.Model,
221− &i.CreatedAt,
222− &i.UpdatedAt,
223− &i.FinishedAt,
224− &i.Provider,
225− &i.IsSummaryMessage,
226− ); err != nil {
227− return nil, err
228− }
229− items = append(items, i)
230− }
231− if err := rows.Close(); err != nil {
232− return nil, err
233− }
234− if err := rows.Err(); err != nil {
235− return nil, err
236− }
237− return items, nil
238−}
239−
240192const listUserMessagesBySession = `-- name: ListUserMessagesBySession :many
241193SELECT id, session_id, role, parts, model, created_at, updated_at, finished_at, provider, is_summary_message
242194FROM messages
internal/db/migrations/20260701000000_add_composite_indexes.sqldeleted−12
@@ -1,12 +0,0 @@
1−-- +goose Up
2−-- +goose StatementBegin
3−-- Add composite indexes to speed up per-session ordered lookups.
4−CREATE INDEX IF NOT EXISTS idx_messages_session_created ON messages (session_id, created_at);
5−CREATE INDEX IF NOT EXISTS idx_files_session_version_created ON files (session_id, version, created_at);
6−-- +goose StatementEnd
7−
8−-- +goose Down
9−-- +goose StatementBegin
10−DROP INDEX IF EXISTS idx_messages_session_created;
11−DROP INDEX IF EXISTS idx_files_session_version_created;
12−-- +goose StatementEnd
internal/db/migrations/20260702000000_add_hot_path_indexes.sqldeleted−14
@@ -1,14 +0,0 @@
1−-- +goose Up
2−-- +goose StatementBegin
3−-- Add indexes for hot-path queries that previously required full table scans.
4−CREATE INDEX IF NOT EXISTS idx_sessions_updated_at ON sessions (updated_at DESC);
5−CREATE INDEX IF NOT EXISTS idx_messages_role_created ON messages (role, created_at DESC);
6−CREATE INDEX IF NOT EXISTS idx_files_path_version ON files (path, version DESC);
7−-- +goose StatementEnd
8−
9−-- +goose Down
10−-- +goose StatementBegin
11−DROP INDEX IF EXISTS idx_sessions_updated_at;
12−DROP INDEX IF EXISTS idx_messages_role_created;
13−DROP INDEX IF EXISTS idx_files_path_version;
14−-- +goose StatementEnd
internal/db/models.go+1−1
@@ -1,6 +1,6 @@
11// Code generated by sqlc. DO NOT EDIT.
22// versions:
3−// sqlc v1.31.1
3+// sqlc v1.30.0
44
55package db
66
internal/db/querier.go+3−5
@@ -1,6 +1,6 @@
11// Code generated by sqlc. DO NOT EDIT.
22// versions:
3−// sqlc v1.31.1
3+// sqlc v1.30.0
44
55package db
66
@@ -23,7 +23,6 @@ type Querier interface {
2323 GetFileRead(ctx context.Context, arg GetFileReadParams) (ReadFile, error)
2424 GetHourDayHeatmap(ctx context.Context) ([]GetHourDayHeatmapRow, error)
2525 GetLastSession(ctx context.Context) (Session, error)
26− GetMaxFileVersionByPath(ctx context.Context, path string) (int64, error)
2726 GetMessage(ctx context.Context, id string) (Message, error)
2827 GetRecentActivity(ctx context.Context) ([]GetRecentActivityRow, error)
2928 GetSessionByID(ctx context.Context, id string) (Session, error)
@@ -38,16 +37,15 @@ type Querier interface {
3837 ListFilesBySession(ctx context.Context, sessionID string) ([]File, error)
3938 ListLatestSessionFiles(ctx context.Context, sessionID string) ([]File, error)
4039 ListMessagesBySession(ctx context.Context, sessionID string) ([]Message, error)
41− ListMessagesBySessionAfter(ctx context.Context, arg ListMessagesBySessionAfterParams) ([]Message, error)
4240 ListNewFiles(ctx context.Context) ([]File, error)
4341 ListSessionReadFiles(ctx context.Context, sessionID string) ([]ReadFile, error)
4442 ListSessions(ctx context.Context) ([]Session, error)
4543 ListUserMessagesBySession(ctx context.Context, sessionID string) ([]Message, error)
4644 RecordFileRead(ctx context.Context, arg RecordFileReadParams) error
47− RenameSession(ctx context.Context, arg RenameSessionParams) (Session, error)
45+ RenameSession(ctx context.Context, arg RenameSessionParams) error
4846 UpdateMessage(ctx context.Context, arg UpdateMessageParams) error
4947 UpdateSession(ctx context.Context, arg UpdateSessionParams) (Session, error)
50− UpdateSessionTitleAndUsage(ctx context.Context, arg UpdateSessionTitleAndUsageParams) (Session, error)
48+ UpdateSessionTitleAndUsage(ctx context.Context, arg UpdateSessionTitleAndUsageParams) error
5149}
5250
5351var _ Querier = (*Queries)(nil)
internal/db/read_files.sql.go+1−1
@@ -1,6 +1,6 @@
11// Code generated by sqlc. DO NOT EDIT.
22// versions:
3−// sqlc v1.31.1
3+// sqlc v1.30.0
44// source: read_files.sql
55
66package db
internal/db/sessions.sql.go+9−39
@@ -1,6 +1,6 @@
11// Code generated by sqlc. DO NOT EDIT.
22// versions:
3−// sqlc v1.31.1
3+// sqlc v1.30.0
44// source: sessions.sql
55
66package db
@@ -176,12 +176,11 @@ func (q *Queries) ListSessions(ctx context.Context) ([]Session, error) {
176176 return items, nil
177177}
178178
179−const renameSession = `-- name: RenameSession :one
179+const renameSession = `-- name: RenameSession :exec
180180UPDATE sessions
181181SET
182182 title = ?
183183WHERE id = ?
184−RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary_message_id, todos
185184`
186185
187186type RenameSessionParams struct {
@@ -189,23 +188,9 @@ type RenameSessionParams struct {
189188 ID string `json:"id"`
190189}
191190
192−func (q *Queries) RenameSession(ctx context.Context, arg RenameSessionParams) (Session, error) {
193− row := q.queryRow(ctx, q.renameSessionStmt, renameSession, arg.Title, arg.ID)
194− var i Session
195− err := row.Scan(
196− &i.ID,
197− &i.ParentSessionID,
198− &i.Title,
199− &i.MessageCount,
200− &i.PromptTokens,
201− &i.CompletionTokens,
202− &i.Cost,
203− &i.UpdatedAt,
204− &i.CreatedAt,
205− &i.SummaryMessageID,
206− &i.Todos,
207− )
208− return i, err
191+func (q *Queries) RenameSession(ctx context.Context, arg RenameSessionParams) error {
192+ _, err := q.exec(ctx, q.renameSessionStmt, renameSession, arg.Title, arg.ID)
193+ return err
209194}
210195
211196const updateSession = `-- name: UpdateSession :one
@@ -258,7 +243,7 @@ func (q *Queries) UpdateSession(ctx context.Context, arg UpdateSessionParams) (S
258243 return i, err
259244}
260245
261−const updateSessionTitleAndUsage = `-- name: UpdateSessionTitleAndUsage :one
246+const updateSessionTitleAndUsage = `-- name: UpdateSessionTitleAndUsage :exec
262247UPDATE sessions
263248SET
264249 title = ?,
@@ -267,7 +252,6 @@ SET
267252 cost = cost + ?,
268253 updated_at = strftime('%s', 'now')
269254WHERE id = ?
270−RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary_message_id, todos
271255`
272256
273257type UpdateSessionTitleAndUsageParams struct {
@@ -278,27 +262,13 @@ type UpdateSessionTitleAndUsageParams struct {
278262 ID string `json:"id"`
279263}
280264
281−func (q *Queries) UpdateSessionTitleAndUsage(ctx context.Context, arg UpdateSessionTitleAndUsageParams) (Session, error) {
282− row := q.queryRow(ctx, q.updateSessionTitleAndUsageStmt, updateSessionTitleAndUsage,
265+func (q *Queries) UpdateSessionTitleAndUsage(ctx context.Context, arg UpdateSessionTitleAndUsageParams) error {
266+ _, err := q.exec(ctx, q.updateSessionTitleAndUsageStmt, updateSessionTitleAndUsage,
283267 arg.Title,
284268 arg.PromptTokens,
285269 arg.CompletionTokens,
286270 arg.Cost,
287271 arg.ID,
288272 )
289− var i Session
290− err := row.Scan(
291− &i.ID,
292− &i.ParentSessionID,
293− &i.Title,
294− &i.MessageCount,
295− &i.PromptTokens,
296− &i.CompletionTokens,
297− &i.Cost,
298− &i.UpdatedAt,
299− &i.CreatedAt,
300− &i.SummaryMessageID,
301− &i.Todos,
302− )
303− return i, err
273+ return err
304274}
internal/db/sql/files.sql+4−12
@@ -22,13 +22,6 @@ FROM files
2222WHERE path = ?
2323ORDER BY version DESC, created_at DESC;
2424
25−-- name: GetMaxFileVersionByPath :one
26−SELECT version
27−FROM files
28−WHERE path = ?
29−ORDER BY version DESC, created_at DESC
30−LIMIT 1;
31−
3225-- name: CreateFile :one
3326INSERT INTO files (
3427 id,
@@ -55,12 +48,11 @@ WHERE session_id = ?;
5548SELECT f.*
5649FROM files f
5750INNER JOIN (
58− SELECT f2.path, MAX(f2.version) as max_version, MAX(f2.created_at) as max_created_at
59− FROM files f2
60− WHERE f2.session_id = @session_id
61− GROUP BY f2.path
51+ SELECT path, MAX(version) as max_version, MAX(created_at) as max_created_at
52+ FROM files
53+ GROUP BY path
6254) latest ON f.path = latest.path AND f.version = latest.max_version AND f.created_at = latest.max_created_at
63−WHERE f.session_id = @session_id
55+WHERE f.session_id = ?
6456ORDER BY f.path;
6557
6658-- name: ListNewFiles :many
internal/db/sql/messages.sql−7
@@ -9,13 +9,6 @@ FROM messages
99WHERE session_id = ?
1010ORDER BY created_at ASC;
1111
12−-- name: ListMessagesBySessionAfter :many
13−SELECT m.*
14−FROM messages m
15−WHERE m.session_id = ?
16− AND m.created_at >= (SELECT m2.created_at FROM messages m2 WHERE m2.id = ?)
17−ORDER BY m.created_at ASC;
18−
1912-- name: CreateMessage :one
2013INSERT INTO messages (
2114 id,
internal/db/sql/sessions.sql+4−6
@@ -52,7 +52,7 @@ SET
5252WHERE id = ?
5353RETURNING *;
5454
55−-- name: UpdateSessionTitleAndUsage :one
55+-- name: UpdateSessionTitleAndUsage :exec
5656UPDATE sessions
5757SET
5858 title = ?,
@@ -60,16 +60,14 @@ SET
6060 completion_tokens = completion_tokens + ?,
6161 cost = cost + ?,
6262 updated_at = strftime('%s', 'now')
63−WHERE id = ?
64−RETURNING *;
63+WHERE id = ?;
6564
6665
67−-- name: RenameSession :one
66+-- name: RenameSession :exec
6867UPDATE sessions
6968SET
7069 title = ?
71−WHERE id = ?
72−RETURNING *;
70+WHERE id = ?;
7371
7472-- name: DeleteSession :exec
7573DELETE FROM sessions
internal/db/stats.sql.go+1−1
@@ -1,6 +1,6 @@
11// Code generated by sqlc. DO NOT EDIT.
22// versions:
3−// sqlc v1.31.1
3+// sqlc v1.30.0
44// source: stats.sql
55
66package db
internal/event/event.go+2−15
@@ -7,7 +7,6 @@ import (
77 "path/filepath"
88 "reflect"
99 "runtime"
10− "sync"
1110 "time"
1211
1312 "github.com/charmbracelet/crush/internal/version"
@@ -26,10 +25,7 @@ const (
2625var (
2726 client posthog.Client
2827
29− // basePropsMu guards baseProps: the Set* mutators reassign it during
30− // startup while send() reads it concurrently from event goroutines.
31− basePropsMu sync.RWMutex
32− baseProps = posthog.NewProperties().
28+ baseProps = posthog.NewProperties().
3329 Set("GOOS", runtime.GOOS).
3430 Set("GOARCH", runtime.GOARCH).
3531 Set("TERM", os.Getenv("TERM")).
@@ -40,20 +36,14 @@ var (
4036)
4137
4238func SetNonInteractive(nonInteractive bool) {
43− basePropsMu.Lock()
44− defer basePropsMu.Unlock()
4539 baseProps = baseProps.Set(nonInteractiveAttrName, nonInteractive)
4640}
4741
4842func SetContinueBySessionID(continueBySessionID bool) {
49− basePropsMu.Lock()
50− defer basePropsMu.Unlock()
5143 baseProps = baseProps.Set(continueSessionByIDAttrName, continueBySessionID)
5244}
5345
5446func SetContinueLastSession(continueLastSession bool) {
55− basePropsMu.Lock()
56− defer basePropsMu.Unlock()
5747 baseProps = baseProps.Set(continueLastSessionAttrName, continueLastSession)
5848}
5949
@@ -91,13 +81,10 @@ func send(event string, props ...any) {
9181 if client == nil {
9282 return
9383 }
94− basePropsMu.RLock()
95− properties := pairsToProps(props...).Merge(baseProps)
96− basePropsMu.RUnlock()
9784 err := client.Enqueue(posthog.Capture{
9885 DistinctId: distinctId,
9986 Event: event,
100− Properties: properties,
87+ Properties: pairsToProps(props...).Merge(baseProps),
10188 })
10289 if err != nil {
10390 slog.Error("Failed to enqueue PostHog event", "event", event, "props", props, "error", err)
internal/filetracker/service.go+2−19
@@ -7,7 +7,6 @@ import (
77 "log/slog"
88 "os"
99 "path/filepath"
10− "sync"
1110 "time"
1211
1312 "github.com/charmbracelet/crush/internal/db"
@@ -59,25 +58,9 @@ func (s *service) LastReadTime(ctx context.Context, sessionID, path string) time
5958 return time.Unix(readFile.ReadAt, 0)
6059}
6160
62−// workingDir caches the process working directory. The working directory
63−// cannot legitimately change during normal operation, so resolving it once
64−// avoids an os.Getwd() syscall on every RecordRead/LastReadTime call.
65−var (
66− workingDirOnce sync.Once
67− workingDir string
68− workingDirErr error
69−)
70−
71−func getwd() (string, error) {
72− workingDirOnce.Do(func() {
73− workingDir, workingDirErr = os.Getwd()
74− })
75− return workingDir, workingDirErr
76−}
77−
7861func relpath(path string) string {
7962 path = filepath.Clean(path)
80− basepath, err := getwd()
63+ basepath, err := os.Getwd()
8164 if err != nil {
8265 slog.Warn("Error getting basepath", "error", err)
8366 return path
@@ -97,7 +80,7 @@ func (s *service) ListReadFiles(ctx context.Context, sessionID string) ([]string
9780 return nil, fmt.Errorf("listing read files: %w", err)
9881 }
9982
100− basepath, err := getwd()
83+ basepath, err := os.Getwd()
10184 if err != nil {
10285 return nil, fmt.Errorf("getting working directory: %w", err)
10386 }
internal/fsext/fileutil.go+22−29
@@ -14,7 +14,6 @@ import (
1414 "github.com/charlievieth/fastwalk"
1515 "github.com/charmbracelet/crush/internal/csync"
1616 "github.com/charmbracelet/crush/internal/home"
17− "github.com/charmbracelet/x/ansi"
1817)
1918
2019type FileInfo struct {
@@ -22,29 +21,6 @@ type FileInfo struct {
2221 ModTime time.Time
2322}
2423
25−// commonIgnoredDirs is a set of directory names that SkipHidden always ignores.
26−// Hoisted to package scope so it is built once instead of on every call.
27−var commonIgnoredDirs = map[string]bool{
28− ".crush": true,
29− "node_modules": true,
30− "vendor": true,
31− "dist": true,
32− "build": true,
33− "target": true,
34− ".git": true,
35− ".idea": true,
36− ".vscode": true,
37− "__pycache__": true,
38− "bin": true,
39− "obj": true,
40− "out": true,
41− "coverage": true,
42− "logs": true,
43− "generated": true,
44− "bower_components": true,
45− "jspm_packages": true,
46−}
47−
4824func SkipHidden(path string) bool {
4925 // Check for hidden files (starting with a dot)
5026 base := filepath.Base(path)
@@ -52,6 +28,27 @@ func SkipHidden(path string) bool {
5228 return true
5329 }
5430
31+ commonIgnoredDirs := map[string]bool{
32+ ".crush": true,
33+ "node_modules": true,
34+ "vendor": true,
35+ "dist": true,
36+ "build": true,
37+ "target": true,
38+ ".git": true,
39+ ".idea": true,
40+ ".vscode": true,
41+ "__pycache__": true,
42+ "bin": true,
43+ "obj": true,
44+ "out": true,
45+ "coverage": true,
46+ "logs": true,
47+ "generated": true,
48+ "bower_components": true,
49+ "jspm_packages": true,
50+ }
51+
5552 parts := strings.SplitSeq(path, string(os.PathSeparator))
5653 for part := range parts {
5754 if commonIgnoredDirs[part] {
@@ -207,11 +204,7 @@ func DirTrim(pwd string, lim int) string {
207204 if i == len(dirs)-1 {
208205 out = dirs[i]
209206 } else if i >= len(dirs)-lim {
210− // Keep the first grapheme cluster, not the first byte: CJK,
211− // combining marks, and emoji can span multiple bytes and runes,
212− // so a byte or single rune would render the wrong character.
213− first, _ := ansi.FirstGraphemeCluster(dirs[i], ansi.GraphemeWidth)
214− out = first + out
207+ out = string(dirs[i][0]) + out
215208 } else {
216209 out = "..." + out
217210 break
internal/fsext/fileutil_dirtrim_test.godeleted−32
@@ -1,32 +0,0 @@
1−package fsext
2−
3−import (
4− "path/filepath"
5− "testing"
6−
7− "github.com/stretchr/testify/require"
8−)
9−
10−func TestDirTrimUnicode(t *testing.T) {
11− sep := string(filepath.Separator)
12−
13− // An abbreviated component keeps its first character.
14− require.Equal(t,
15− filepath.Join("~", "...", "p", "file"),
16− DirTrim(sep+"home"+sep+"user"+sep+"project"+sep+"file", 2),
17− )
18−
19− // A multi-byte (e.g. CJK) component must keep its first whole character,
20− // not its first byte, which would render a wrong character.
21− require.Equal(t,
22− filepath.Join("~", "...", "项", "file"),
23− DirTrim(sep+"home"+sep+"项目"+sep+"file", 2),
24− )
25−
26− // A grapheme cluster spanning multiple runes (a base letter plus a
27− // combining accent) must be kept whole, not split after the base rune.
28− require.Equal(t,
29− filepath.Join("~", "...", "e\u0301", "file"),
30− DirTrim(sep+"home"+sep+"e\u0301cole"+sep+"file", 2),
31− )
32−}
internal/fsext/lookup.go+7−2
@@ -258,7 +258,7 @@ func canonicalize(path string) string {
258258
259259// probeEnt checks if entity at given path exists and belongs to given owner
260260func probeEnt(fspath string, owner int) error {
261− info, err := os.Stat(fspath)
261+ _, err := os.Stat(fspath)
262262 if err != nil {
263263 return fmt.Errorf("cannot stat %s: %w", fspath, err)
264264 }
@@ -268,7 +268,12 @@ func probeEnt(fspath string, owner int) error {
268268 return nil
269269 }
270270
271− if ownerFromInfo(info) != owner {
271+ fowner, err := Owner(fspath)
272+ if err != nil {
273+ return fmt.Errorf("cannot get ownership for %s: %w", fspath, err)
274+ }
275+
276+ if fowner != owner {
272277 return os.ErrPermission
273278 }
274279
internal/fsext/owner_others.go+5−8
@@ -14,14 +14,11 @@ func Owner(path string) (int, error) {
1414 if err != nil {
1515 return 0, err
1616 }
17− return ownerFromInfo(info), nil
18−}
19−
20−// ownerFromInfo derives the owner user ID from an already-obtained
21−// os.FileInfo, avoiding a redundant stat.
22−func ownerFromInfo(info os.FileInfo) int {
17+ var uid int
2318 if stat, ok := info.Sys().(*syscall.Stat_t); ok {
24− return int(stat.Uid)
19+ uid = int(stat.Uid)
20+ } else {
21+ uid = os.Getuid()
2522 }
26− return os.Getuid()
23+ return uid, nil
2724}
internal/fsext/owner_windows.go−7
@@ -13,10 +13,3 @@ func Owner(path string) (int, error) {
1313 }
1414 return -1, nil
1515}
16−
17−// ownerFromInfo derives the owner user ID from an already-obtained
18−// os.FileInfo, avoiding a redundant stat. Windows does not resolve
19−// ownership, so it always returns the bypass sentinel.
20−func ownerFromInfo(_ os.FileInfo) int {
21− return -1
22−}
internal/herdr/client.godeleted−405
@@ -1,405 +0,0 @@
1−// Package herdr provides native integration with the herdr terminal
2−// multiplexer. When Crush runs inside a herdr-managed pane it reports
3−// agent state (idle, working, blocked) and session identity over
4−// herdr's Unix socket API so herdr can display accurate status without
5−// screen scraping.
6−//
7−// The client consumes a small, herdr-specific event vocabulary rather
8−// than accepting raw proto or domain types. Callers translate their
9−// events into herdr.Event before forwarding. This keeps the client
10−// decoupled from both the proto and internal domain layers.
11−package herdr
12−
13−import (
14− "context"
15− "encoding/json"
16− "flag"
17− "fmt"
18− "io"
19− "log/slog"
20− "net"
21− "os"
22− "sync"
23− "time"
24−)
25−
26−// State values matching herdr's PaneAgentState enum.
27−const (
28− stateIdle = "idle"
29− stateWorking = "working"
30− stateBlocked = "blocked"
31−)
32−
33−// Event is the herdr-specific event vocabulary. Each type maps to a
34−// distinct state transition in the agent lifecycle. Callers translate
35−// from proto or domain types into these before calling HandleEvent.
36−type Event interface {
37− herdrEvent()
38−}
39−
40−// AssistantMessage indicates the agent produced output. Transitions
41−// to working if not already active.
42−type AssistantMessage struct {
43− SessionID string
44−}
45−
46−func (AssistantMessage) herdrEvent() {}
47−
48−// RunComplete indicates the agent finished a turn. Transitions to idle.
49−type RunComplete struct {
50− SessionID string
51−}
52−
53−func (RunComplete) herdrEvent() {}
54−
55−// PermissionRequested indicates the agent is waiting for user approval.
56−// Transitions to blocked.
57−type PermissionRequested struct{}
58−
59−func (PermissionRequested) herdrEvent() {}
60−
61−// PermissionResolved indicates a permission decision was made.
62−// Transitions back to working if a run is active, idle otherwise.
63−type PermissionResolved struct{}
64−
65−func (PermissionResolved) herdrEvent() {}
66−
67−// Summarizing indicates the agent is compacting context. Transitions
68−// to working if not already active.
69−type Summarizing struct{}
70−
71−func (Summarizing) herdrEvent() {}
72−
73−// sender abstracts the transport layer for reporting state to herdr.
74−// Production uses a Unix socket; tests use a recorder.
75−type sender interface {
76− send(req reportRequest) error
77− close()
78−}
79−
80−// Client reports Crush agent state to a running herdr instance.
81−type Client struct {
82− socketPath string
83− paneID string
84−
85− mu sync.Mutex
86− sessionID string
87− state string
88− runActive bool
89− seq uint64
90−
91− snd sender
92−}
93−
94−// defaultClient is the process-wide herdr client. Initialized once
95−// via Init(). All integration sites share this single instance so
96−// only one Unix socket connection exists per process.
97−var (
98− defaultClient *Client
99− initOnce sync.Once
100−)
101−
102−// Init returns the process-wide herdr Client, creating it on first
103−// call from environment variables. Returns nil when Crush is not
104−// running inside a herdr pane. Safe to call from any goroutine.
105−func Init() *Client {
106− initOnce.Do(func() {
107− defaultClient = newFromEnv()
108− })
109− return defaultClient
110−}
111−
112−func newFromEnv() *Client {
113− if os.Getenv("HERDR_ENV") != "1" {
114− return nil
115− }
116− // A test binary inherits the launching shell's HERDR_* env, so
117− // without this it would attach to the developer's live pane and
118− // release its agent on teardown. Skip herdr entirely under test.
119− if flag.Lookup("test.v") != nil {
120− slog.Debug("Herdr integration disabled: running under go test")
121− return nil
122− }
123− socketPath := os.Getenv("HERDR_SOCKET_PATH")
124− paneID := os.Getenv("HERDR_PANE_ID")
125− if socketPath == "" || paneID == "" {
126− slog.Debug("Herdr integration disabled: incomplete environment",
127− "has_socket", socketPath != "",
128− "has_pane_id", paneID != "",
129− )
130− return nil
131− }
132− c := &Client{
133− socketPath: socketPath,
134− paneID: paneID,
135− state: stateIdle,
136− seq: uint64(time.Now().UnixNano()),
137− snd: newUnixSender(socketPath),
138− }
139− c.registerInitial()
140− return c
141−}
142−
143−// registerInitial sends an initial idle-state report to herdr so the
144−// pane knows about the agent immediately, not just after the first
145−// event. Called once during client creation. Bypasses the dedup
146−// check since the initial state must always be reported regardless
147−// of redundancy.
148−//
149−// herdr remembers the highest seq it has seen per source for the
150−// lifetime of a pane and silently drops any report with a seq that
151−// is not strictly greater. Because crush seeds seq from the wall
152−// clock at startup (see newFromEnv), a restarted crush in the same
153−// pane always reports above the previous run's high-water mark, so
154−// the first report is accepted instead of being rejected as stale.
155−func (c *Client) registerInitial() {
156− if c == nil {
157− return
158− }
159− c.mu.Lock()
160− defer c.mu.Unlock()
161− if err := c.snd.send(c.newRequestLocked("pane.report_agent", "init", stateIdle)); err != nil {
162− slog.Warn("failed to send herdr agent report", "error", err)
163− }
164−}
165−
166−// Close releases the agent's authority on the pane and shuts down
167−// the background writer. Safe to call on a nil client.
168−func (c *Client) Close() {
169− if c == nil {
170− return
171− }
172− c.releaseAgent()
173− c.snd.close()
174−}
175−
176−// releaseAgent sends a pane.release_agent request to herdr so the
177−// pane is freed for a new agent to claim authority. This is the
178−// clean-shutdown protocol per herdr's socket API. Sends directly
179−// on the socket to ensure delivery even if the write loop is busy.
180−func (c *Client) releaseAgent() {
181− c.mu.Lock()
182− defer c.mu.Unlock()
183− req := c.newRequestLocked("pane.release_agent", "release", "")
184− if err := dialSend(c.socketPath, req); err != nil {
185− slog.Debug("Herdr release_agent failed", "error", err)
186− }
187−}
188−
189−// HandleEvent processes a single herdr event and reports state changes.
190−// Safe to call from any goroutine.
191−func (c *Client) HandleEvent(ev Event) {
192− if c == nil {
193− return
194− }
195− switch e := ev.(type) {
196− case AssistantMessage:
197− c.onAssistantMessage(e.SessionID)
198− case RunComplete:
199− c.onRunComplete(e.SessionID)
200− case PermissionRequested:
201− c.onPermissionRequest()
202− case PermissionResolved:
203− c.onPermissionResolved()
204− case Summarizing:
205− c.onSummarizing()
206− }
207−}
208−
209−// SetSessionID sets the session ID for reporting. Call this when the
210−// session is created or resolved, before events start flowing.
211−func (c *Client) SetSessionID(id string) {
212− if c == nil {
213− return
214− }
215− c.mu.Lock()
216− defer c.mu.Unlock()
217− c.sessionID = id
218−}
219−
220−func (c *Client) onAssistantMessage(sessionID string) {
221− c.mu.Lock()
222− defer c.mu.Unlock()
223− if sessionID != "" {
224− c.sessionID = sessionID
225− }
226− if !c.runActive {
227− c.runActive = true
228− c.reportLocked(stateWorking)
229− }
230−}
231−
232−func (c *Client) onRunComplete(sessionID string) {
233− c.mu.Lock()
234− defer c.mu.Unlock()
235− c.runActive = false
236− if sessionID != "" {
237− c.sessionID = sessionID
238− }
239− c.reportLocked(stateIdle)
240−}
241−
242−func (c *Client) onPermissionRequest() {
243− c.mu.Lock()
244− defer c.mu.Unlock()
245− // A permission request implies a run is active, even if no
246− // assistant message has arrived yet (e.g. tool calls that fire
247− // before any text output).
248− if !c.runActive {
249− c.runActive = true
250− }
251− c.reportLocked(stateBlocked)
252−}
253−
254−func (c *Client) onPermissionResolved() {
255− c.mu.Lock()
256− defer c.mu.Unlock()
257− if c.runActive {
258− c.reportLocked(stateWorking)
259− } else {
260− c.reportLocked(stateIdle)
261− }
262−}
263−
264−func (c *Client) onSummarizing() {
265− c.mu.Lock()
266− defer c.mu.Unlock()
267− if !c.runActive {
268− c.runActive = true
269− }
270− c.reportLocked(stateWorking)
271−}
272−
273−// newRequestLocked builds a seq-stamped JSON-RPC request to herdr.
274−// Must be called with c.mu held. Every request increments c.seq so
275−// herdr accepts it as strictly newer than the last (see
276−// registerInitial for why monotonic seq matters). State is empty for
277−// requests that carry no agent state, such as pane.release_agent.
278−func (c *Client) newRequestLocked(method, idPrefix, state string) reportRequest {
279− c.seq++
280− return reportRequest{
281− ID: fmt.Sprintf("crush:%s:%d", idPrefix, time.Now().UnixNano()),
282− Method: method,
283− Params: reportParams{
284− PaneID: c.paneID,
285− Source: "crush",
286− Agent: "crush",
287− State: state,
288− Seq: c.seq,
289− AgentSessionID: c.sessionID,
290− },
291− }
292−}
293−
294−// reportLocked sends a pane.report_agent request to herdr. Must be
295−// called with c.mu held. Skips redundant reports when the state has
296−// not changed.
297−func (c *Client) reportLocked(state string) {
298− if state == c.state {
299− return
300− }
301− c.state = state
302− if err := c.snd.send(c.newRequestLocked("pane.report_agent", "report", state)); err != nil {
303− slog.Warn("failed to send herdr agent report", "error", err)
304− }
305−}
306−
307−// reportRequest is the JSON-RPC envelope sent to herdr.
308−type reportRequest struct {
309− ID string `json:"id"`
310− Method string `json:"method"`
311− Params reportParams `json:"params"`
312−}
313−
314−// reportParams carries the agent state payload.
315−type reportParams struct {
316− PaneID string `json:"pane_id"`
317− Source string `json:"source"`
318− Agent string `json:"agent"`
319− State string `json:"state"`
320− Seq uint64 `json:"seq"`
321− AgentSessionID string `json:"agent_session_id"`
322−}
323−
324−// unixSender sends JSON-RPC requests over a Unix domain socket using
325−// a single background writer goroutine and a buffered channel. This
326−// serializes writes and avoids spawning unbounded goroutines under
327−// high event throughput. Each report opens a short-lived connection.
328−type unixSender struct {
329− socketPath string
330− ch chan reportRequest
331− cancel context.CancelFunc
332−}
333−
334−func newUnixSender(socketPath string) *unixSender {
335− ctx, cancel := context.WithCancel(context.Background())
336− s := &unixSender{
337− socketPath: socketPath,
338− ch: make(chan reportRequest, 16),
339− cancel: cancel,
340− }
341− go s.writeLoop(ctx)
342− return s
343−}
344−
345−func (s *unixSender) send(req reportRequest) error {
346− select {
347− case s.ch <- req:
348− default:
349− // Drop if the buffer is full. State reports are
350− // best-effort; blocking the agent is worse than
351− // missing a transition.
352− }
353− return nil
354−}
355−
356−func (s *unixSender) close() {
357− s.cancel()
358−}
359−
360−func (s *unixSender) writeLoop(ctx context.Context) {
361− for {
362− select {
363− case req, ok := <-s.ch:
364− if !ok {
365− return
366− }
367− if err := dialSend(s.socketPath, req); err != nil {
368− slog.Debug("Herdr report failed", "error", err)
369− }
370− case <-ctx.Done():
371− return
372− }
373− }
374−}
375−
376−// dialSend opens a short-lived Unix socket connection to herdr,
377−// sends a single JSON-RPC request, and drains the response.
378−func dialSend(socketPath string, req reportRequest) error {
379− ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
380− defer cancel()
381−
382− dialer := net.Dialer{}
383− conn, err := dialer.DialContext(ctx, "unix", socketPath)
384− if err != nil {
385− return err
386− }
387− defer conn.Close()
388−
389− _ = conn.SetDeadline(time.Now().Add(500 * time.Millisecond))
390−
391− data, err := json.Marshal(req)
392− if err != nil {
393− return err
394− }
395− data = append(data, '\n')
396−
397− _, err = conn.Write(data)
398− if err != nil {
399− return err
400− }
401−
402− // Drain the response to complete the request cycle.
403− _, _ = io.Copy(io.Discard, conn)
404− return nil
405−}
internal/herdr/client_test.godeleted−158
@@ -1,158 +0,0 @@
1−package herdr
2−
3−import (
4− "testing"
5−
6− "github.com/stretchr/testify/assert"
7−)
8−
9−// recordingSender captures state transitions without connecting to a
10−// real Unix socket.
11−type recordingSender struct {
12− states []string
13−}
14−
15−func (r *recordingSender) send(req reportRequest) error {
16− r.states = append(r.states, req.Params.State)
17− return nil
18−}
19−
20−func (r *recordingSender) close() {}
21−
22−// newTestClient creates a Client that records state transitions
23−// without connecting to a real Unix socket.
24−func newTestClient() *Client {
25− rec := &recordingSender{states: make([]string, 0, 16)}
26− return &Client{
27− state: stateIdle,
28− snd: rec,
29− }
30−}
31−
32−// reportedStates returns the states recorded by the test sender.
33−func reportedStates(c *Client) []string {
34− return c.snd.(*recordingSender).states
35−}
36−
37−func TestBasicLifecycle(t *testing.T) {
38− t.Parallel()
39− c := newTestClient()
40−
41− // Assistant message starts working.
42− c.HandleEvent(AssistantMessage{SessionID: "sess-1"})
43− assert.Equal(t, []string{stateWorking}, reportedStates(c))
44−
45− // Run complete returns to idle.
46− c.HandleEvent(RunComplete{SessionID: "sess-1"})
47− assert.Equal(t, []string{stateWorking, stateIdle}, reportedStates(c))
48−}
49−
50−func TestPermissionBlockAndUnblock(t *testing.T) {
51− t.Parallel()
52− c := newTestClient()
53−
54− // Start working.
55− c.HandleEvent(AssistantMessage{SessionID: "sess-1"})
56−
57− // Permission request blocks.
58− c.HandleEvent(PermissionRequested{})
59− assert.Equal(t, []string{stateWorking, stateBlocked}, reportedStates(c))
60−
61− // Permission granted returns to working (run still active).
62− c.HandleEvent(PermissionResolved{})
63− assert.Equal(t, []string{stateWorking, stateBlocked, stateWorking}, reportedStates(c))
64−
65− // Run complete returns to idle.
66− c.HandleEvent(RunComplete{SessionID: "sess-1"})
67− assert.Equal(t, []string{stateWorking, stateBlocked, stateWorking, stateIdle}, reportedStates(c))
68−}
69−
70−func TestPermissionBeforeAssistantMessage(t *testing.T) {
71− t.Parallel()
72− c := newTestClient()
73−
74− // Permission request arrives before any assistant message.
75− // This can happen when tool calls fire before text output.
76− c.HandleEvent(PermissionRequested{})
77− assert.Equal(t, []string{stateBlocked}, reportedStates(c))
78−
79− // Permission resolved should return to working, not idle,
80− // because the permission request implied a run was active.
81− c.HandleEvent(PermissionResolved{})
82− assert.Equal(t, []string{stateBlocked, stateWorking}, reportedStates(c))
83−}
84−
85−func TestSessionIDPropagation(t *testing.T) {
86− t.Parallel()
87− c := newTestClient()
88−
89− // SetSessionID before events.
90− c.SetSessionID("early-session")
91− assert.Equal(t, "early-session", c.sessionID)
92−
93− // RunComplete also updates session ID.
94− c.HandleEvent(RunComplete{SessionID: "final-session"})
95− assert.Equal(t, "final-session", c.sessionID)
96−}
97−
98−func TestDedupSkipsRedundantState(t *testing.T) {
99− t.Parallel()
100− c := newTestClient()
101−
102− // Two assistant messages in a row should only report working once.
103− c.HandleEvent(AssistantMessage{SessionID: "s1"})
104− c.HandleEvent(AssistantMessage{SessionID: "s1"})
105− assert.Equal(t, []string{stateWorking}, reportedStates(c))
106−}
107−
108−func TestSummarizingTriggersWorking(t *testing.T) {
109− t.Parallel()
110− c := newTestClient()
111−
112− // Summarizing event should trigger working.
113− c.HandleEvent(Summarizing{})
114− assert.Equal(t, []string{stateWorking}, reportedStates(c))
115−
116− // Second summarizing should not trigger another state change.
117− c.HandleEvent(Summarizing{})
118− assert.Equal(t, []string{stateWorking}, reportedStates(c))
119−}
120−
121−func TestNilClientSafe(t *testing.T) {
122− t.Parallel()
123− var c *Client
124− // These should not panic on a nil receiver.
125− c.SetSessionID("s1")
126− c.HandleEvent(AssistantMessage{SessionID: "s1"})
127− c.HandleEvent(RunComplete{SessionID: "s1"})
128− c.HandleEvent(PermissionRequested{})
129− c.HandleEvent(PermissionResolved{})
130− c.HandleEvent(Summarizing{})
131−}
132−
133−func TestRegisterInitial(t *testing.T) {
134− t.Parallel()
135− rec := &recordingSender{states: make([]string, 0, 16)}
136− c := &Client{
137− state: stateIdle,
138− seq: 100,
139− snd: rec,
140− }
141− c.registerInitial()
142− assert.Equal(t, []string{stateIdle}, rec.states)
143− // seq must strictly increase so herdr accepts the report.
144− assert.Equal(t, uint64(101), c.seq)
145−}
146−
147−// TestInitDisabledUnderTest guards the critical safety property that
148−// herdr never attaches to a real pane from a test binary. Test
149−// processes inherit the developer's HERDR_* environment, so a missing
150−// guard would release the live pane's agent on teardown. Because this
151−// test itself runs under `go test`, Init must return nil even with a
152−// complete, valid-looking environment.
153−func TestInitDisabledUnderTest(t *testing.T) {
154− t.Setenv("HERDR_ENV", "1")
155− t.Setenv("HERDR_SOCKET_PATH", "/tmp/does-not-matter.sock")
156− t.Setenv("HERDR_PANE_ID", "test:pane")
157− assert.Nil(t, newFromEnv())
158−}
internal/herdr/translate.godeleted−150
@@ -1,150 +0,0 @@
1−package herdr
2−
3−import (
4− "context"
5− "time"
6−
7− "github.com/charmbracelet/crush/internal/agent/notify"
8− "github.com/charmbracelet/crush/internal/message"
9− "github.com/charmbracelet/crush/internal/permission"
10− "github.com/charmbracelet/crush/internal/proto"
11− "github.com/charmbracelet/crush/internal/pubsub"
12−)
13−
14−// Translate converts a pub/sub event (domain or proto) into a herdr
15−// Event. Returns nil for event types herdr doesn't care about. This
16−// is the single translation point for all integration modes.
17−func Translate(ev any) Event {
18− switch e := ev.(type) {
19− // Domain types (TUI / local headless).
20− case pubsub.Event[message.Message]:
21− return translateMessage(
22− e.Payload.Role == message.Assistant,
23− e.Payload.SessionID,
24− e.Payload.IsSummaryMessage,
25− )
26− case pubsub.Event[notify.RunComplete]:
27− return RunComplete{SessionID: e.Payload.SessionID}
28− case pubsub.Event[permission.PermissionRequest]:
29− return PermissionRequested{}
30− case pubsub.Event[permission.PermissionNotification]:
31− return PermissionResolved{}
32−
33− // Proto types (client/server mode).
34− case pubsub.Event[proto.Message]:
35− return translateMessage(
36− e.Payload.Role == proto.Assistant,
37− e.Payload.SessionID,
38− false,
39− )
40− case pubsub.Event[proto.RunComplete]:
41− return RunComplete{SessionID: e.Payload.SessionID}
42− case pubsub.Event[proto.PermissionRequest]:
43− return PermissionRequested{}
44− case pubsub.Event[proto.PermissionNotification]:
45− return PermissionResolved{}
46− case pubsub.Event[proto.AgentEvent]:
47− if e.Payload.Type == proto.AgentEventTypeSummarize && !e.Payload.Done {
48− return Summarizing{}
49− }
50− return nil
51−
52− default:
53− return nil
54− }
55−}
56−
57−// translateMessage is the shared message-mapping logic for both domain
58−// and proto message types.
59−func translateMessage(isAssistant bool, sessionID string, isSummary bool) Event {
60− if !isAssistant {
61− return nil
62− }
63− if isSummary {
64− return Summarizing{}
65− }
66− return AssistantMessage{SessionID: sessionID}
67−}
68−
69−// permNotificationSubscriber is the subset of the permission service
70−// needed by BridgeLocal to subscribe to permission notifications.
71−type permNotificationSubscriber interface {
72− SubscribeNotifications(context.Context) <-chan pubsub.Event[permission.PermissionNotification]
73−}
74−
75−// BridgeSources groups the pub/sub sources that BridgeLocal subscribes
76−// to. Adding a new event type means adding a field here rather than
77−// growing the function signature.
78−type BridgeSources struct {
79− PermRequests pubsub.Subscriber[permission.PermissionRequest]
80− PermNotifications permNotificationSubscriber
81− RunCompletions pubsub.Subscriber[notify.RunComplete]
82− Messages pubsub.Subscriber[message.Message]
83−}
84−
85−// BridgeLocal subscribes to local pub/sub brokers and forwards
86−// translated events to the client. Used in TUI and local headless
87−// modes where the agent runs in-process. Cancelling ctx stops the
88−// bridge goroutines.
89−//
90−// The spawned goroutines are best-effort and may briefly outlive
91−// Client.Close(). This is safe: HandleEvent is nil-safe, and the
92−// unixSender drops messages on a full buffer rather than blocking.
93−//
94−// Each goroutine uses a resilient subscription loop that re-subscribes
95−// if the channel closes unexpectedly, ensuring the bridge survives
96−// transient pub/sub broker resets.
97−func BridgeLocal(ctx context.Context, c *Client, src BridgeSources) {
98− if c == nil {
99− return
100− }
101− go forward(ctx, c, func(subCtx context.Context) <-chan pubsub.Event[permission.PermissionRequest] {
102− return src.PermRequests.Subscribe(subCtx)
103− })
104− go forward(ctx, c, func(subCtx context.Context) <-chan pubsub.Event[permission.PermissionNotification] {
105− return src.PermNotifications.SubscribeNotifications(subCtx)
106− })
107− go forward(ctx, c, func(subCtx context.Context) <-chan pubsub.Event[notify.RunComplete] {
108− return src.RunCompletions.Subscribe(subCtx)
109− })
110− go forward(ctx, c, func(subCtx context.Context) <-chan pubsub.Event[message.Message] {
111− return src.Messages.Subscribe(subCtx)
112− })
113−}
114−
115−// forward reads from a pub/sub channel and forwards translated
116−// events to the herdr client. If the channel closes (e.g., due to
117−// broker reset), it re-subscribes after a brief delay. Runs until ctx
118−// is cancelled.
119−func forward[T any](ctx context.Context, c *Client, subscribe func(context.Context) <-chan pubsub.Event[T]) {
120− for {
121− select {
122− case <-ctx.Done():
123− return
124− default:
125− }
126−
127− subCtx, cancel := context.WithCancel(ctx)
128− ch := subscribe(subCtx)
129−
130− inner:
131− for {
132− select {
133− case <-ctx.Done():
134− cancel()
135− return
136− case ev, ok := <-ch:
137− if !ok {
138− // Channel closed — broker may have reset.
139− // Cancel the sub-context and re-subscribe.
140− cancel()
141− time.Sleep(100 * time.Millisecond)
142− break inner
143− }
144− if hev := Translate(ev); hev != nil {
145− c.HandleEvent(hev)
146− }
147− }
148− }
149− }
150−}
internal/herdr/translate_test.godeleted−145
@@ -1,145 +0,0 @@
1−package herdr
2−
3−import (
4− "testing"
5−
6− "github.com/charmbracelet/crush/internal/agent/notify"
7− "github.com/charmbracelet/crush/internal/message"
8− "github.com/charmbracelet/crush/internal/permission"
9− "github.com/charmbracelet/crush/internal/proto"
10− "github.com/charmbracelet/crush/internal/pubsub"
11− "github.com/stretchr/testify/assert"
12−)
13−
14−// Domain type translation.
15−
16−func TestTranslateDomainAssistantMessage(t *testing.T) {
17− t.Parallel()
18− ev := pubsub.Event[message.Message]{
19− Payload: message.Message{Role: message.Assistant, SessionID: "s1"},
20− }
21− assert.Equal(t, AssistantMessage{SessionID: "s1"}, Translate(ev))
22−}
23−
24−func TestTranslateDomainSummaryMessage(t *testing.T) {
25− t.Parallel()
26− ev := pubsub.Event[message.Message]{
27− Payload: message.Message{
28− Role: message.Assistant,
29− SessionID: "s1",
30− IsSummaryMessage: true,
31− },
32− }
33− assert.Equal(t, Summarizing{}, Translate(ev))
34−}
35−
36−func TestTranslateDomainNonAssistantIgnored(t *testing.T) {
37− t.Parallel()
38− ev := pubsub.Event[message.Message]{
39− Payload: message.Message{Role: message.System},
40− }
41− assert.Nil(t, Translate(ev))
42−}
43−
44−func TestTranslateDomainRunComplete(t *testing.T) {
45− t.Parallel()
46− ev := pubsub.Event[notify.RunComplete]{
47− Payload: notify.RunComplete{SessionID: "s1"},
48− }
49− assert.Equal(t, RunComplete{SessionID: "s1"}, Translate(ev))
50−}
51−
52−func TestTranslateDomainPermissionRequest(t *testing.T) {
53− t.Parallel()
54− ev := pubsub.Event[permission.PermissionRequest]{
55− Payload: permission.PermissionRequest{ToolName: "bash"},
56− }
57− assert.Equal(t, PermissionRequested{}, Translate(ev))
58−}
59−
60−func TestTranslateDomainPermissionNotification(t *testing.T) {
61− t.Parallel()
62− ev := pubsub.Event[permission.PermissionNotification]{
63− Payload: permission.PermissionNotification{Granted: true},
64− }
65− assert.Equal(t, PermissionResolved{}, Translate(ev))
66−}
67−
68−// Proto type translation.
69−
70−func TestTranslateProtoAssistantMessage(t *testing.T) {
71− t.Parallel()
72− ev := pubsub.Event[proto.Message]{
73− Payload: proto.Message{Role: proto.Assistant, SessionID: "s1"},
74− }
75− assert.Equal(t, AssistantMessage{SessionID: "s1"}, Translate(ev))
76−}
77−
78−func TestTranslateProtoNonAssistantIgnored(t *testing.T) {
79− t.Parallel()
80− ev := pubsub.Event[proto.Message]{
81− Payload: proto.Message{Role: proto.User, SessionID: "s1"},
82− }
83− assert.Nil(t, Translate(ev))
84−}
85−
86−func TestTranslateProtoRunComplete(t *testing.T) {
87− t.Parallel()
88− ev := pubsub.Event[proto.RunComplete]{
89− Payload: proto.RunComplete{SessionID: "s1"},
90− }
91− assert.Equal(t, RunComplete{SessionID: "s1"}, Translate(ev))
92−}
93−
94−func TestTranslateProtoPermissionRequest(t *testing.T) {
95− t.Parallel()
96− ev := pubsub.Event[proto.PermissionRequest]{
97− Payload: proto.PermissionRequest{ToolName: "bash"},
98− }
99− assert.Equal(t, PermissionRequested{}, Translate(ev))
100−}
101−
102−func TestTranslateProtoPermissionNotification(t *testing.T) {
103− t.Parallel()
104− ev := pubsub.Event[proto.PermissionNotification]{
105− Payload: proto.PermissionNotification{Granted: true},
106− }
107− assert.Equal(t, PermissionResolved{}, Translate(ev))
108−}
109−
110−func TestTranslateProtoSummarizing(t *testing.T) {
111− t.Parallel()
112− ev := pubsub.Event[proto.AgentEvent]{
113− Payload: proto.AgentEvent{
114− Type: proto.AgentEventTypeSummarize,
115− Done: false,
116− },
117− }
118− assert.Equal(t, Summarizing{}, Translate(ev))
119−}
120−
121−func TestTranslateProtoSummarizeDoneIgnored(t *testing.T) {
122− t.Parallel()
123− ev := pubsub.Event[proto.AgentEvent]{
124− Payload: proto.AgentEvent{
125− Type: proto.AgentEventTypeSummarize,
126− Done: true,
127− },
128− }
129− assert.Nil(t, Translate(ev))
130−}
131−
132−func TestTranslateProtoSessionIgnored(t *testing.T) {
133− t.Parallel()
134− ev := pubsub.Event[proto.Session]{
135− Payload: proto.Session{ID: "s1"},
136− }
137− assert.Nil(t, Translate(ev))
138−}
139−
140−// Unknown types.
141−
142−func TestTranslateUnknownReturnsNil(t *testing.T) {
143− t.Parallel()
144− assert.Nil(t, Translate("not an event"))
145−}
internal/history/file.go+15−20
@@ -3,7 +3,6 @@ package history
33import (
44 "context"
55 "database/sql"
6− "errors"
76 "fmt"
87 "strings"
98
@@ -64,22 +63,20 @@ func (s *service) Create(ctx context.Context, sessionID, path, content string) (
6463// number. If no previous versions exist for the path, it creates the initial
6564// version. The provided content is stored as the new version.
6665func (s *service) CreateVersion(ctx context.Context, sessionID, path, content string) (File, error) {
67− // Get the latest version for this path. Only the version column is read
68− // (via ORDER BY version DESC LIMIT 1) to avoid loading every version's
69− // full content just to compute the max. Versions start at InitialVersion
70− // (0), so MAX cannot distinguish "no rows" from "only the initial
71− // version"; sql.ErrNoRows is the reliable signal for "no previous
72− // version", in which case we create the initial version.
73− maxVersion, err := s.q.GetMaxFileVersionByPath(ctx, path)
66+ // Get the latest version for this path
67+ files, err := s.q.ListFilesByPath(ctx, path)
7468 if err != nil {
75− if errors.Is(err, sql.ErrNoRows) {
76− // No previous versions, create initial
77− return s.Create(ctx, sessionID, path, content)
78− }
7969 return File{}, err
8070 }
8171
82− nextVersion := maxVersion + 1
72+ if len(files) == 0 {
73+ // No previous versions, create initial
74+ return s.Create(ctx, sessionID, path, content)
75+ }
76+
77+ // Get the latest version
78+ latestFile := files[0] // Files are ordered by version DESC, created_at DESC
79+ nextVersion := latestFile.Version + 1
8380
8481 return s.createWithVersion(ctx, sessionID, path, content, nextVersion)
8582}
@@ -111,7 +108,7 @@ func (s *service) createWithVersion(ctx context.Context, sessionID, path, conten
111108 })
112109 if txErr != nil {
113110 // Rollback the transaction
114− _ = tx.Rollback()
111+ tx.Rollback()
115112
116113 // Check if this is a uniqueness constraint violation
117114 if strings.Contains(txErr.Error(), "UNIQUE constraint failed") {
@@ -194,17 +191,15 @@ func (s *service) Delete(ctx context.Context, id string) error {
194191}
195192
196193func (s *service) DeleteSessionFiles(ctx context.Context, sessionID string) error {
197− // Fetch all files first so we can publish per-file DeletedEvent.
198194 files, err := s.ListBySession(ctx, sessionID)
199195 if err != nil {
200196 return err
201197 }
202− // Single batch DELETE instead of N individual deletes.
203− if err := s.q.DeleteSessionFiles(ctx, sessionID); err != nil {
204− return err
205− }
206198 for _, file := range files {
207− s.Publish(pubsub.DeletedEvent, file)
199+ err = s.Delete(ctx, file.ID)
200+ if err != nil {
201+ return err
202+ }
208203 }
209204 return nil
210205}
internal/lsp/client.go−5
@@ -53,11 +53,6 @@ type Client struct {
5353 // Diagnostic cache
5454 diagnostics *csync.VersionedMap[protocol.DocumentURI, []protocol.Diagnostic]
5555
56− // diagnosticsTotal is a running total of diagnostics across all files,
57− // maintained incrementally as diagnostics are set to avoid recounting
58− // every file on each publishDiagnostics notification.
59− diagnosticsTotal atomic.Int64
60−
6156 // Cached diagnostic counts to avoid map copy on every UI render.
6257 diagCountsCache DiagnosticCounts
6358 diagCountsVersion uint64
internal/lsp/handlers.go+6−6
@@ -108,13 +108,13 @@ func HandleDiagnostics(client *Client, params json.RawMessage) {
108108 return
109109 }
110110
111− // Maintain the running total incrementally: subtract the previous
112− // diagnostics for this URI (if any) and add the new ones. This avoids
113− // recounting every file's diagnostics on each notification.
114− previous, _ := client.diagnostics.Get(diagParams.URI)
115111 client.diagnostics.Set(diagParams.URI, diagParams.Diagnostics)
116− delta := len(diagParams.Diagnostics) - len(previous)
117− totalCount := int(client.diagnosticsTotal.Add(int64(delta)))
112+
113+ // Calculate total diagnostic count
114+ totalCount := 0
115+ for _, diagnostics := range client.diagnostics.Seq2() {
116+ totalCount += len(diagnostics)
117+ }
118118
119119 // Trigger callback if set
120120 if client.onDiagnosticsChanged != nil {
internal/lsp/manager.go+8−28
@@ -27,7 +27,6 @@ const unavailableRetryDelay = 30 * time.Second
2727type Manager struct {
2828 clients *csync.Map[string, *Client]
2929 unavailable *csync.Map[string, time.Time]
30− rootMarkers *csync.Map[string, bool]
3130 cfg *config.ConfigStore
3231 manager *powernapconfig.Manager
3332 callback func(name string, client *Client)
@@ -37,9 +36,7 @@ type Manager struct {
3736// NewManager creates a new LSP manager service.
3837func NewManager(cfg *config.ConfigStore) *Manager {
3938 manager := powernapconfig.NewManager()
40− if err := manager.LoadDefaults(); err != nil {
41− slog.Warn("failed to load LSP defaults", "error", err)
42− }
39+ manager.LoadDefaults()
4340
4441 // Merge user-configured LSPs into the manager.
4542 for name, clientConfig := range cfg.Config().LSP {
@@ -66,7 +63,6 @@ func NewManager(cfg *config.ConfigStore) *Manager {
6663 return &Manager{
6764 clients: csync.NewMap[string, *Client](),
6865 unavailable: csync.NewMap[string, time.Time](),
69− rootMarkers: csync.NewMap[string, bool](),
7066 cfg: cfg,
7167 manager: manager,
7268 callback: func(string, *Client) {}, // default no-op callback
@@ -172,14 +168,6 @@ func (s *Manager) startServer(ctx context.Context, name, filepath string, server
172168 }
173169 }
174170
175− // Cheap, pure string check first: if this server does not handle the
176− // file type at all, skip the expensive PATH lookup and workspace
177− // root-marker glob below. This gates the costly I/O on the zero-I/O
178− // filetype match, without changing whether the server ultimately starts.
179− if !handlesFiletype(server.Command, server.FileTypes, filepath) {
180− return
181− }
182−
183171 if !isUserConfigured {
184172 if s.recentlyUnavailable(name) {
185173 return
@@ -196,8 +184,8 @@ func (s *Manager) startServer(ctx context.Context, name, filepath string, server
196184 }
197185 }
198186
199− // hasRootMarkers is the slowest bit (a workspace glob), so we do it last.
200− if !s.hasRootMarkers(s.cfg.WorkingDir(), server.RootMarkers) {
187+ // this is the slowest bit, so we do it last.
188+ if !handles(server, filepath, s.cfg.WorkingDir()) {
201189 // nothing to do
202190 return
203191 }
@@ -341,19 +329,6 @@ func handlesFiletype(sname string, fileTypes []string, filePath string) bool {
341329 return false
342330}
343331
344−// hasRootMarkers reports whether dir contains any of the given root
345−// markers. The result is invariant for a fixed (dir, markers) pair, so it
346−// is memoized to avoid re-running the workspace glob on every file view.
347−func (s *Manager) hasRootMarkers(dir string, markers []string) bool {
348− if len(markers) == 0 {
349− return true
350− }
351− key := dir + "\x00" + strings.Join(markers, "\x00")
352− return s.rootMarkers.GetOrSet(key, func() bool {
353− return hasRootMarkers(dir, markers)
354− })
355−}
356−
357332func hasRootMarkers(dir string, markers []string) bool {
358333 if len(markers) == 0 {
359334 return true
@@ -370,6 +345,11 @@ func hasRootMarkers(dir string, markers []string) bool {
370345 return false
371346}
372347
348+func handles(server *powernapconfig.ServerConfig, filePath, workDir string) bool {
349+ return handlesFiletype(server.Command, server.FileTypes, filePath) &&
350+ hasRootMarkers(workDir, server.RootMarkers)
351+}
352+
373353// KillAll force-kills all the LSP clients.
374354//
375355// This is generally faster than [Manager.StopAll] because it doesn't wait for
internal/lsp/util/edit.go+34−41
@@ -4,7 +4,6 @@ import (
44 "bytes"
55 "fmt"
66 "os"
7− "slices"
87 "sort"
98 "strings"
109
@@ -38,40 +37,28 @@ func applyTextEdits(uri protocol.DocumentURI, edits []protocol.TextEdit, encodin
3837 // Split into lines without the endings
3938 lines := strings.Split(string(content), lineEnding)
4039
41− // Sort edits once by ascending start position (end position as a stable
42− // tiebreaker). This lets us both detect overlaps in a single linear pass
43− // and apply edits from the bottom of the file upward.
40+ // Check for overlapping edits
41+ for i, edit1 := range edits {
42+ for j := i + 1; j < len(edits); j++ {
43+ if rangesOverlap(edit1.Range, edits[j].Range) {
44+ return fmt.Errorf("overlapping edits detected between edit %d and %d", i, j)
45+ }
46+ }
47+ }
48+
49+ // Sort edits in reverse order
4450 sortedEdits := make([]protocol.TextEdit, len(edits))
4551 copy(sortedEdits, edits)
4652 sort.Slice(sortedEdits, func(i, j int) bool {
47− a, b := sortedEdits[i].Range, sortedEdits[j].Range
48− if a.Start.Line != b.Start.Line {
49− return a.Start.Line < b.Start.Line
53+ if sortedEdits[i].Range.Start.Line != sortedEdits[j].Range.Start.Line {
54+ return sortedEdits[i].Range.Start.Line > sortedEdits[j].Range.Start.Line
5055 }
51− if a.Start.Character != b.Start.Character {
52− return a.Start.Character < b.Start.Character
53− }
54− if a.End.Line != b.End.Line {
55− return a.End.Line < b.End.Line
56− }
57− return a.End.Character < b.End.Character
56+ return sortedEdits[i].Range.Start.Character > sortedEdits[j].Range.Start.Character
5857 })
5958
60− // Detect overlapping edits in a single linear pass. Because the edits are
61− // sorted by ascending start, any overlap manifests between two adjacent
62− // edits, so checking neighbors is sufficient.
63− for i := 1; i < len(sortedEdits); i++ {
64− if rangesOverlap(sortedEdits[i-1].Range, sortedEdits[i].Range) {
65− return fmt.Errorf("overlapping edits detected between edit %d and %d", i-1, i)
66− }
67− }
68−
69− // Apply edits from the bottom of the file upward so that earlier edits'
70− // line/character offsets are not shifted by later ones. applyTextEdit
71− // splices the affected range in place instead of rebuilding the whole
72− // slice per edit.
73− for i := len(sortedEdits) - 1; i >= 0; i-- {
74− newLines, err := applyTextEdit(lines, sortedEdits[i], encoding)
59+ // Apply each edit
60+ for _, edit := range sortedEdits {
61+ newLines, err := applyTextEdit(lines, edit, encoding)
7562 if err != nil {
7663 return fmt.Errorf("failed to apply edit: %w", err)
7764 }
@@ -131,6 +118,12 @@ func applyTextEdit(lines []string, edit protocol.TextEdit, encoding powernap.Off
131118 endChar = utf32ToByteOffset(endLineContent, edit.Range.End.Character)
132119 }
133120
121+ // Create result slice with initial capacity
122+ result := make([]string, 0, len(lines))
123+
124+ // Copy lines before edit
125+ result = append(result, lines[:startLine]...)
126+
134127 // Get the prefix of the start line
135128 startLineContent := lines[startLine]
136129 if startChar < 0 || startChar > len(startLineContent) {
@@ -145,11 +138,10 @@ func applyTextEdit(lines []string, edit protocol.TextEdit, encoding powernap.Off
145138 }
146139 suffix := endLineContent[endChar:]
147140
148− // Build the replacement lines for the edited range [startLine, endLine].
149− var region []string
141+ // Handle the edit
150142 if edit.NewText == "" {
151143 if prefix+suffix != "" {
152− region = []string{prefix + suffix}
144+ result = append(result, prefix+suffix)
153145 }
154146 } else {
155147 // Split new text into lines, being careful not to add extra newlines
@@ -158,20 +150,21 @@ func applyTextEdit(lines []string, edit protocol.TextEdit, encoding powernap.Off
158150
159151 if len(newLines) == 1 {
160152 // Single line change
161− region = []string{prefix + newLines[0] + suffix}
153+ result = append(result, prefix+newLines[0]+suffix)
162154 } else {
163155 // Multi-line change
164− region = make([]string, 0, len(newLines))
165− region = append(region, prefix+newLines[0])
166− region = append(region, newLines[1:len(newLines)-1]...)
167− region = append(region, newLines[len(newLines)-1]+suffix)
156+ result = append(result, prefix+newLines[0])
157+ result = append(result, newLines[1:len(newLines)-1]...)
158+ result = append(result, newLines[len(newLines)-1]+suffix)
168159 }
169160 }
170161
171− // Splice the replacement in place instead of rebuilding the whole slice.
172− // Callers apply edits from the bottom of the file upward, so mutating the
173− // shared slice does not disturb not-yet-applied (earlier) edits.
174− return slices.Replace(lines, startLine, endLine+1, region...), nil
162+ // Add remaining lines
163+ if endLine+1 < len(lines) {
164+ result = append(result, lines[endLine+1:]...)
165+ }
166+
167+ return result, nil
175168}
176169
177170// applyDocumentChange applies a DocumentChange (create/rename/delete operations)
internal/message/content.go+23−69
@@ -171,18 +171,6 @@ type Message struct {
171171 CreatedAt int64
172172 UpdatedAt int64
173173 IsSummaryMessage bool
174−
175− // textBuf and reasoningBuf accumulate streaming deltas in amortized
176− // O(1) so building a long response is O(n) rather than O(n²) (the
177− // naive `existing + delta` reallocated and copied the whole
178− // accumulated string on every token). They are nil until the first
179− // delta, unexported (never persisted), and deliberately dropped by
180− // Clone: only the streaming message that owns them ever appends, so
181− // no other goroutine touches the builder. Snapshots observe an
182− // immutable prefix of the builder's append-only buffer, which is
183− // race-free to read concurrently.
184− textBuf *strings.Builder
185− reasoningBuf *strings.Builder
186174}
187175
188176func (m *Message) Content() TextContent {
@@ -233,21 +221,6 @@ func (m *Message) ToolCalls() []ToolCall {
233221 return toolCalls
234222}
235223
236−// toolCallStats returns the number of tool-call parts and how many of
237−// them are finished, in a single pass without allocating (unlike
238−// ToolCalls, which builds a slice). Used on the streaming hot path.
239−func (m *Message) toolCallStats() (count, finished int) {
240− for _, part := range m.Parts {
241− if c, ok := part.(ToolCall); ok {
242− count++
243− if c.Finished {
244− finished++
245− }
246− }
247− }
248− return count, finished
249−}
250−
251224func (m *Message) ToolResults() []ToolResult {
252225 toolResults := make([]ToolResult, 0)
253226 for _, part := range m.Parts {
@@ -293,51 +266,37 @@ func (m *Message) IsThinking() bool {
293266}
294267
295268func (m *Message) AppendContent(delta string) {
296− if m.textBuf == nil {
297− m.textBuf = &strings.Builder{}
298− // Seed from any pre-existing text part (e.g. a restored message);
299− // streaming messages start empty so this is normally a no-op.
300− for _, part := range m.Parts {
301− if c, ok := part.(TextContent); ok {
302− m.textBuf.WriteString(c.Text)
303− break
304− }
305− }
306− }
307− m.textBuf.WriteString(delta)
308− text := m.textBuf.String()
269+ found := false
309270 for i, part := range m.Parts {
310− if _, ok := part.(TextContent); ok {
311− m.Parts[i] = TextContent{Text: text}
312− return
271+ if c, ok := part.(TextContent); ok {
272+ m.Parts[i] = TextContent{Text: c.Text + delta}
273+ found = true
313274 }
314275 }
315− m.Parts = append(m.Parts, TextContent{Text: text})
276+ if !found {
277+ m.Parts = append(m.Parts, TextContent{Text: delta})
278+ }
316279}
317280
318281func (m *Message) AppendReasoningContent(delta string) {
319− if m.reasoningBuf == nil {
320− m.reasoningBuf = &strings.Builder{}
321− for _, part := range m.Parts {
322− if c, ok := part.(ReasoningContent); ok {
323− m.reasoningBuf.WriteString(c.Thinking)
324− break
325− }
326− }
327− }
328− m.reasoningBuf.WriteString(delta)
329− thinking := m.reasoningBuf.String()
282+ found := false
330283 for i, part := range m.Parts {
331284 if c, ok := part.(ReasoningContent); ok {
332− c.Thinking = thinking
333− m.Parts[i] = c
334− return
285+ m.Parts[i] = ReasoningContent{
286+ Thinking: c.Thinking + delta,
287+ Signature: c.Signature,
288+ StartedAt: c.StartedAt,
289+ FinishedAt: c.FinishedAt,
290+ }
291+ found = true
335292 }
336293 }
337− m.Parts = append(m.Parts, ReasoningContent{
338− Thinking: thinking,
339− StartedAt: time.Now().Unix(),
340− })
294+ if !found {
295+ m.Parts = append(m.Parts, ReasoningContent{
296+ Thinking: delta,
297+ StartedAt: time.Now().Unix(),
298+ })
299+ }
341300}
342301
343302func (m *Message) AppendThoughtSignature(signature string, toolCallID string) {
@@ -491,10 +450,6 @@ func (m *Message) Clone() Message {
491450 clone := *m
492451 clone.Parts = make([]ContentPart, len(m.Parts))
493452 copy(clone.Parts, m.Parts)
494− // Snapshots never stream; drop the accumulation builders so only the
495− // owning message ever appends to them (keeps them single-goroutine).
496− clone.textBuf = nil
497− clone.reasoningBuf = nil
498453 return clone
499454}
500455
@@ -547,9 +502,8 @@ func (m *Message) ToAIMessage() []fantasy.Message {
547502 case User:
548503 var parts []fantasy.MessagePart
549504 text := strings.TrimSpace(m.Content().Text)
550− binaryContent := m.BinaryContent()
551505 var textAttachments []Attachment
552− for _, content := range binaryContent {
506+ for _, content := range m.BinaryContent() {
553507 if !strings.HasPrefix(content.MIMEType, "text/") {
554508 continue
555509 }
@@ -572,7 +526,7 @@ func (m *Message) ToAIMessage() []fantasy.Message {
572526 if text != "" {
573527 parts = append(parts, fantasy.TextPart{Text: text})
574528 }
575− for _, content := range binaryContent {
529+ for _, content := range m.BinaryContent() {
576530 // skip text attachements
577531 if strings.HasPrefix(content.MIMEType, "text/") {
578532 continue
internal/message/message.go+56−102
@@ -49,7 +49,6 @@ type Service interface {
4949 Update(ctx context.Context, message Message) error
5050 Get(ctx context.Context, id string) (Message, error)
5151 List(ctx context.Context, sessionID string) ([]Message, error)
52− ListAfter(ctx context.Context, sessionID, afterMessageID string) ([]Message, error)
5352 ListUserMessages(ctx context.Context, sessionID string) ([]Message, error)
5453 ListAllUserMessages(ctx context.Context) ([]Message, error)
5554 Delete(ctx context.Context, id string) error
@@ -86,11 +85,6 @@ type pendingState struct {
8685 // but other flushers must back off.
8786 flushing bool
8887
89− // flushDone is non-nil exactly while flushing is true. It is closed
90− // (under service.mu) when the in-flight flush completes, so a sync
91− // caller can wait on it instead of polling.
92− flushDone chan struct{}
93−
9488 // timer is the active debounce timer, or nil if no flush is
9589 // scheduled. Stopped and reset when a terminal update preempts
9690 // the debounce window.
@@ -202,34 +196,18 @@ func (s *service) Create(ctx context.Context, sessionID string, params CreateMes
202196}
203197
204198func (s *service) DeleteSessionMessages(ctx context.Context, sessionID string) error {
205− // Fetch all messages first so we can publish per-message DeletedEvent
206− // for each (the UI uses them to remove individual chat entries).
207199 messages, err := s.List(ctx, sessionID)
208200 if err != nil {
209201 return err
210202 }
211−
212− // Single batch DELETE instead of N individual deletes.
213− if err := s.q.DeleteSessionMessages(ctx, sessionID); err != nil {
214− return err
215− }
216−
217− // Drop any pending coalesced state and publish events for the
218− // already-fetched messages — no redundant Get queries needed.
219− s.mu.Lock()
220− for _, msg := range messages {
221− if p, ok := s.pending[msg.ID]; ok {
222− if p.timer != nil {
223− p.timer.Stop()
203+ for _, message := range messages {
204+ if message.SessionID == sessionID {
205+ err = s.Delete(ctx, message.ID)
206+ if err != nil {
207+ return err
224208 }
225− delete(s.pending, msg.ID)
226209 }
227210 }
228− s.mu.Unlock()
229−
230− for _, msg := range messages {
231− s.Publish(pubsub.DeletedEvent, msg.Clone())
232− }
233211 return nil
234212}
235213
@@ -346,17 +324,9 @@ func (s *service) flushOne(ctx context.Context, id string, syncCaller bool) erro
346324 s.mu.Unlock()
347325 return nil
348326 }
349− // Wait for the in-flight flush to finish rather than
350− // polling. flushDone is closed under s.mu when flushing
351− // clears, and we capture it under the same lock, so the
352− // wakeup can never be missed.
353− done := p.flushDone
354327 s.mu.Unlock()
355− select {
356− case <-done:
357− case <-ctx.Done():
358− return ctx.Err()
359− }
328+ // Brief yield; in-flight write should land in <1ms typical.
329+ time.Sleep(time.Millisecond)
360330 continue
361331 }
362332 if !p.dirty {
@@ -379,7 +349,6 @@ func (s *service) flushOne(ctx context.Context, id string, syncCaller bool) erro
379349 }
380350 isTerminal := shouldFlushNow(prev, &snap)
381351 p.flushing = true
382− p.flushDone = make(chan struct{})
383352 p.dirty = false
384353 s.mu.Unlock()
385354
@@ -387,10 +356,6 @@ func (s *service) flushOne(ctx context.Context, id string, syncCaller bool) erro
387356
388357 s.mu.Lock()
389358 p.flushing = false
390− if p.flushDone != nil {
391− close(p.flushDone)
392− p.flushDone = nil
393− }
394359 if err == nil {
395360 p.lastFlushed = snap
396361 p.hasFlushed = true
@@ -455,20 +420,25 @@ func shouldFlushNow(prev, next *Message) bool {
455420 return true
456421 }
457422
458− var prevCount, prevFinished int
423+ var prevCalls []ToolCall
459424 var prevReasoningFinishedAt int64
460425 if prev != nil {
461− prevCount, prevFinished = prev.toolCallStats()
426+ prevCalls = prev.ToolCalls()
462427 prevReasoningFinishedAt = prev.ReasoningContent().FinishedAt
463428 }
464− nextCount, nextFinished := next.toolCallStats()
465− // The tool-call set grew, or a call transitioned to finished. Tool
466− // calls never un-finish and the count only grows, so comparing the
467− // finished counts is equivalent to the old per-index Finished check
468− // but allocation-free (a call's Input only matters once Finished).
469− if nextCount != prevCount || nextFinished != prevFinished {
429+ nextCalls := next.ToolCalls()
430+ if len(nextCalls) != len(prevCalls) {
470431 return true
471432 }
433+ for i := range nextCalls {
434+ // Bounds-safe: lengths are equal here.
435+ if nextCalls[i].Finished != prevCalls[i].Finished {
436+ return true
437+ }
438+ // A tool call's input only matters once it has landed (Finished
439+ // flips true). Earlier deltas to Input are debounced with the
440+ // rest of the streaming state.
441+ }
472442 if next.ReasoningContent().FinishedAt > 0 && prevReasoningFinishedAt == 0 {
473443 return true
474444 }
@@ -498,24 +468,6 @@ func (s *service) List(ctx context.Context, sessionID string) ([]Message, error)
498468 return messages, nil
499469}
500470
501−func (s *service) ListAfter(ctx context.Context, sessionID, afterMessageID string) ([]Message, error) {
502− dbMessages, err := s.q.ListMessagesBySessionAfter(ctx, db.ListMessagesBySessionAfterParams{
503− SessionID: sessionID,
504− ID: afterMessageID,
505− })
506− if err != nil {
507− return nil, err
508− }
509− messages := make([]Message, len(dbMessages))
510− for i, dbMessage := range dbMessages {
511− messages[i], err = s.fromDBItem(dbMessage)
512− if err != nil {
513− return nil, err
514− }
515− }
516− return messages, nil
517−}
518−
519471func (s *service) ListUserMessages(ctx context.Context, sessionID string) ([]Message, error) {
520472 dbMessages, err := s.q.ListUserMessagesBySession(ctx, sessionID)
521473 if err != nil {
@@ -618,74 +570,76 @@ func marshalParts(parts []ContentPart) ([]byte, error) {
618570}
619571
620572func unmarshalParts(data []byte) ([]ContentPart, error) {
621− // Single-pass unmarshal into a wrapper with Data as json.RawMessage,
622− // avoiding the intermediate []json.RawMessage + per-element re-decode.
623− var temp []struct {
624− Type partType `json:"type"`
625− Data json.RawMessage `json:"data"`
626− }
573+ temp := []json.RawMessage{}
627574
628575 if err := json.Unmarshal(data, &temp); err != nil {
629576 return nil, err
630577 }
631578
632− parts := make([]ContentPart, 0, len(temp))
579+ parts := make([]ContentPart, 0)
580+
581+ for _, rawPart := range temp {
582+ var wrapper struct {
583+ Type partType `json:"type"`
584+ Data json.RawMessage `json:"data"`
585+ }
586+
587+ if err := json.Unmarshal(rawPart, &wrapper); err != nil {
588+ return nil, err
589+ }
633590
634− for _, wrapper := range temp {
635− var part ContentPart
636591 switch wrapper.Type {
637592 case reasoningType:
638− p := ReasoningContent{}
639− if err := json.Unmarshal(wrapper.Data, &p); err != nil {
593+ part := ReasoningContent{}
594+ if err := json.Unmarshal(wrapper.Data, &part); err != nil {
640595 return nil, err
641596 }
642− part = p
597+ parts = append(parts, part)
643598 case textType:
644− p := TextContent{}
645− if err := json.Unmarshal(wrapper.Data, &p); err != nil {
599+ part := TextContent{}
600+ if err := json.Unmarshal(wrapper.Data, &part); err != nil {
646601 return nil, err
647602 }
648− part = p
603+ parts = append(parts, part)
649604 case imageURLType:
650− p := ImageURLContent{}
651− if err := json.Unmarshal(wrapper.Data, &p); err != nil {
605+ part := ImageURLContent{}
606+ if err := json.Unmarshal(wrapper.Data, &part); err != nil {
652607 return nil, err
653608 }
654− part = p
609+ parts = append(parts, part)
655610 case binaryType:
656− p := BinaryContent{}
657− if err := json.Unmarshal(wrapper.Data, &p); err != nil {
611+ part := BinaryContent{}
612+ if err := json.Unmarshal(wrapper.Data, &part); err != nil {
658613 return nil, err
659614 }
660− part = p
615+ parts = append(parts, part)
661616 case toolCallType:
662− p := ToolCall{}
663− if err := json.Unmarshal(wrapper.Data, &p); err != nil {
617+ part := ToolCall{}
618+ if err := json.Unmarshal(wrapper.Data, &part); err != nil {
664619 return nil, err
665620 }
666− part = p
621+ parts = append(parts, part)
667622 case toolResultType:
668− p := ToolResult{}
669− if err := json.Unmarshal(wrapper.Data, &p); err != nil {
623+ part := ToolResult{}
624+ if err := json.Unmarshal(wrapper.Data, &part); err != nil {
670625 return nil, err
671626 }
672− part = p
627+ parts = append(parts, part)
673628 case finishType:
674− p := Finish{}
675− if err := json.Unmarshal(wrapper.Data, &p); err != nil {
629+ part := Finish{}
630+ if err := json.Unmarshal(wrapper.Data, &part); err != nil {
676631 return nil, err
677632 }
678− part = p
633+ parts = append(parts, part)
679634 case shellCommandType:
680− p := ShellCommand{}
681− if err := json.Unmarshal(wrapper.Data, &p); err != nil {
635+ part := ShellCommand{}
636+ if err := json.Unmarshal(wrapper.Data, &part); err != nil {
682637 return nil, err
683638 }
684− part = p
639+ parts = append(parts, part)
685640 default:
686641 return nil, fmt.Errorf("unknown part type: %s", wrapper.Type)
687642 }
688− parts = append(parts, part)
689643 }
690644
691645 return parts, nil
internal/oauth/copilot/client.go+7−22
@@ -17,20 +17,14 @@ var assistantRolePattern = regexp.MustCompile(`"role"\s*:\s*"assistant"`)
1717// NewClient creates a new HTTP client with a custom transport that adds the
1818// X-Initiator header based on message history in the request body.
1919func NewClient(isSubAgent, debug bool) *http.Client {
20− t := &initiatorTransport{debug: debug, isSubAgent: isSubAgent}
21− if debug {
22− // Build the debug HTTP client once and reuse it on every RoundTrip.
23− t.debugClient = log.NewHTTPClient()
24− }
2520 return &http.Client{
26− Transport: t,
21+ Transport: &initiatorTransport{debug: debug, isSubAgent: isSubAgent},
2722 }
2823}
2924
3025type initiatorTransport struct {
31− debug bool
32− isSubAgent bool
33− debugClient *http.Client
26+ debug bool
27+ isSubAgent bool
3428}
3529
3630func (t *initiatorTransport) RoundTrip(req *http.Request) (*http.Response, error) {
@@ -43,17 +37,8 @@ func (t *initiatorTransport) RoundTrip(req *http.Request) (*http.Response, error
4337 if req == nil {
4438 return nil, fmt.Errorf("HTTP request is nil")
4539 }
46− // Sub-agents are always attributed to the agent initiator regardless of
47− // body content, so short-circuit before reading/cloning the body.
48− if t.isSubAgent {
49− req.Header.Set(xInitiatorHeader, agentInitiator)
50− slog.Debug("Setting X-Initiator header to agent (sub-agent)")
51− return t.roundTrip(req)
52− }
53− if req.Body == nil || req.Body == http.NoBody {
54− // No body to inspect; default to user. A nil Body is valid for
55− // bodyless requests (e.g. GET), and is distinct from http.NoBody,
56− // so both must be handled before reading below.
40+ if req.Body == http.NoBody {
41+ // No body to inspect; default to user.
5742 req.Header.Set(xInitiatorHeader, userInitiator)
5843 slog.Debug("Setting X-Initiator header to user (no request body)")
5944 return t.roundTrip(req)
@@ -75,7 +60,7 @@ func (t *initiatorTransport) RoundTrip(req *http.Request) (*http.Response, error
7560 // Check for assistant messages using regex to handle whitespace
7661 // variations in the JSON while avoiding full unmarshalling overhead.
7762 initiator := userInitiator
78− if assistantRolePattern.Match(bodyBytes) {
63+ if assistantRolePattern.Match(bodyBytes) || t.isSubAgent {
7964 slog.Debug("Setting X-Initiator header to agent (found assistant messages in history)")
8065 initiator = agentInitiator
8166 } else {
@@ -88,7 +73,7 @@ func (t *initiatorTransport) RoundTrip(req *http.Request) (*http.Response, error
8873
8974func (t *initiatorTransport) roundTrip(req *http.Request) (*http.Response, error) {
9075 if t.debug {
91− return t.debugClient.Transport.RoundTrip(req)
76+ return log.NewHTTPClient().Transport.RoundTrip(req)
9277 }
9378 return http.DefaultTransport.RoundTrip(req)
9479}
internal/oauth/copilot/client_test.godeleted−70
@@ -1,70 +0,0 @@
1−package copilot
2−
3−import (
4− "net/http"
5− "net/http/httptest"
6− "strings"
7− "testing"
8−
9− "github.com/stretchr/testify/require"
10−)
11−
12−func TestInitiatorTransportSetsHeader(t *testing.T) {
13− tests := []struct {
14− name string
15− // body builds the request body; nil means a bodyless request.
16− body func() (int, string)
17− want string
18− }{
19− {
20− name: "nil body defaults to user",
21− body: func() (int, string) { return 0, "" }, // req.Body == nil
22− want: "user",
23− },
24− {
25− name: "NoBody defaults to user",
26− body: func() (int, string) { return -1, "" }, // req.Body == http.NoBody
27− want: "user",
28− },
29− {
30− name: "user-only history stays user",
31− body: func() (int, string) { return 1, `{"messages":[{"role":"user"}]}` },
32− want: "user",
33− },
34− {
35− name: "assistant history becomes agent",
36− body: func() (int, string) { return 1, `{"messages":[{"role":"assistant"}]}` },
37− want: "agent",
38− },
39− }
40−
41− for _, tt := range tests {
42− t.Run(tt.name, func(t *testing.T) {
43− var got string
44− srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
45− got = r.Header.Get("X-Initiator")
46− }))
47− defer srv.Close()
48−
49− kind, payload := tt.body()
50− var req *http.Request
51− var err error
52− switch kind {
53− case 0:
54− req, err = http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL, nil)
55− case -1:
56− req, err = http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL, http.NoBody)
57− default:
58− req, err = http.NewRequestWithContext(t.Context(), http.MethodPost, srv.URL, strings.NewReader(payload))
59− }
60− require.NoError(t, err)
61−
62− client := &http.Client{Transport: &initiatorTransport{}}
63− resp, err := client.Do(req)
64− require.NoError(t, err)
65− resp.Body.Close()
66−
67− require.Equal(t, tt.want, got)
68− })
69− }
70−}
internal/permission/permission.go+47−55
@@ -104,10 +104,10 @@ type permissionService struct {
104104 skip atomic.Bool
105105 allowedTools []string
106106
107− // requestMu serializes the setup of a permission request (up to the
108− // point the pending entry is registered); it is released before the
109− // human wait so parallel requests don't queue behind one another.
110− requestMu sync.Mutex
107+ // used to make sure we only process one request at a time
108+ requestMu sync.Mutex
109+ activeRequest *PermissionRequest
110+ activeRequestMu sync.Mutex
111111}
112112
113113// resolve atomically removes the pending request entry for the given
@@ -146,6 +146,12 @@ func (s *permissionService) resolve(permission PermissionRequest, granted, denie
146146 // per request because Take removes the entry under the map lock,
147147 // so this send never blocks.
148148 respCh <- granted
149+
150+ s.activeRequestMu.Lock()
151+ if s.activeRequest != nil && s.activeRequest.ID == permission.ID {
152+ s.activeRequest = nil
153+ }
154+ s.activeRequestMu.Unlock()
149155 return true
150156}
151157
@@ -195,17 +201,26 @@ func (s *permissionService) Request(ctx context.Context, opts CreatePermissionRe
195201 return true, nil
196202 }
197203
198− // Set up the request under requestMu, then release it *before*
199− // blocking on the human response. Holding requestMu across the wait
200− // would serialize every permission prompt process-wide, so parallel
201− // sub-agents each needing approval would queue behind one another.
202− // Per-request resolution is scoped by the pendingRequests map (keyed
203− // by request ID), so the wait needs no global lock.
204− // Resolve the target path and build the request before taking
205− // requestMu. os.Stat is a blocking syscall and none of this setup
206− // touches shared state (workingDir is immutable, uuid.New is
207− // self-contained), so it does not need to serialize with other
208− // in-flight requests.
204+ s.requestMu.Lock()
205+ defer s.requestMu.Unlock()
206+
207+ // tell the UI that a permission was requested
208+ s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
209+ ToolCallID: opts.ToolCallID,
210+ })
211+
212+ s.autoApproveSessionsMu.RLock()
213+ autoApprove := s.autoApproveSessions[opts.SessionID]
214+ s.autoApproveSessionsMu.RUnlock()
215+
216+ if autoApprove {
217+ s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
218+ ToolCallID: opts.ToolCallID,
219+ Granted: true,
220+ })
221+ return true, nil
222+ }
223+
209224 fileInfo, err := os.Stat(opts.Path)
210225 dir := opts.Path
211226 if err == nil {
@@ -230,52 +245,29 @@ func (s *permissionService) Request(ctx context.Context, opts CreatePermissionRe
230245 Params: opts.Params,
231246 }
232247
233− respCh, requestID, resolved, granted := func() (chan bool, string, bool, bool) {
234− s.requestMu.Lock()
235− defer s.requestMu.Unlock()
236−
237− // tell the UI that a permission was requested
248+ if _, ok := s.sessionPermissions.Get(PermissionKey{
249+ SessionID: permission.SessionID,
250+ ToolName: permission.ToolName,
251+ Action: permission.Action,
252+ Path: permission.Path,
253+ }); ok {
238254 s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
239255 ToolCallID: opts.ToolCallID,
256+ Granted: true,
240257 })
258+ return true, nil
259+ }
241260
242− s.autoApproveSessionsMu.RLock()
243− autoApprove := s.autoApproveSessions[opts.SessionID]
244− s.autoApproveSessionsMu.RUnlock()
245−
246− if autoApprove {
247− s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
248− ToolCallID: opts.ToolCallID,
249− Granted: true,
250− })
251− return nil, "", true, true
252− }
253−
254− if _, ok := s.sessionPermissions.Get(PermissionKey{
255− SessionID: permission.SessionID,
256− ToolName: permission.ToolName,
257− Action: permission.Action,
258− Path: permission.Path,
259− }); ok {
260− s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
261− ToolCallID: opts.ToolCallID,
262− Granted: true,
263− })
264− return nil, "", true, true
265− }
266−
267− respCh := make(chan bool, 1)
268− s.pendingRequests.Set(permission.ID, respCh)
261+ s.activeRequestMu.Lock()
262+ s.activeRequest = &permission
263+ s.activeRequestMu.Unlock()
269264
270− // Publish the request
271− s.Publish(pubsub.CreatedEvent, permission)
272− return respCh, permission.ID, false, false
273− }()
265+ respCh := make(chan bool, 1)
266+ s.pendingRequests.Set(permission.ID, respCh)
267+ defer s.pendingRequests.Del(permission.ID)
274268
275− if resolved {
276− return granted, nil
277− }
278− defer s.pendingRequests.Del(requestID)
269+ // Publish the request
270+ s.Publish(pubsub.CreatedEvent, permission)
279271
280272 select {
281273 case <-ctx.Done():
internal/pubsub/broker.go+27−69
@@ -83,12 +83,6 @@ func (b *Broker[T]) SetMustDeliverTimeout(d time.Duration) {
8383}
8484
8585func (b *Broker[T]) Shutdown() {
86− b.mu.Lock()
87− defer b.mu.Unlock()
88−
89− // Close b.done under the lock so concurrent Shutdown calls (and the
90− // per-subscriber goroutines, which also close under the lock) cannot
91− // double-close it and panic.
9286 select {
9387 case <-b.done: // Already closed
9488 return
@@ -96,6 +90,9 @@ func (b *Broker[T]) Shutdown() {
9690 close(b.done)
9791 }
9892
93+ b.mu.Lock()
94+ defer b.mu.Unlock()
95+
9996 for ch := range b.subs {
10097 delete(b.subs, ch)
10198 close(ch)
@@ -121,20 +118,11 @@ func (b *Broker[T]) Subscribe(ctx context.Context) <-chan Event[T] {
121118 b.subCount++
122119
123120 go func() {
124− // Wake on either the subscriber's context ending or the broker
125− // shutting down. Without the b.done case, a subscriber with a
126− // long-lived context would park here forever after Shutdown,
127− // leaking the goroutine.
128− select {
129− case <-ctx.Done():
130− case <-b.done:
131− }
121+ <-ctx.Done()
132122
133123 b.mu.Lock()
134124 defer b.mu.Unlock()
135125
136− // If the broker shut down, it already closed this channel under
137− // the lock; don't close it again.
138126 select {
139127 case <-b.done:
140128 return
@@ -191,16 +179,10 @@ func (b *Broker[T]) Publish(t EventType, payload T) {
191179 case sub <- event:
192180 default:
193181 // Channel is full, subscriber is slow — skip this event.
194− // Lossy by design; counted so saturation is observable via
195− // DropCount. The warning is throttled to power-of-two drop
196− // counts (1, 2, 4, 8, …): saturation is exactly when this
197− // path runs hottest, and an unthrottled per-event log — while
198− // holding the RLock — would turn a slow subscriber into a
199− // log-storm that slows every publisher further.
200− if n := b.dropCount.Add(1); n&(n-1) == 0 {
201− slog.Warn("Pubsub buffer full; dropping events",
202− "type", t, "dropped_total", n)
203− }
182+ // Lossy by design; counted and logged so saturation is
183+ // observable.
184+ b.dropCount.Add(1)
185+ slog.Warn("Pubsub buffer full; dropping event", "type", t)
204186 }
205187 }
206188}
@@ -217,19 +199,6 @@ func (b *Broker[T]) Publish(t EventType, payload T) {
217199// after timeout — recovery is the subscriber's responsibility (e.g. a
218200// re-fetch on the next session-visible event).
219201func (b *Broker[T]) PublishMustDeliver(ctx context.Context, t EventType, payload T) {
220− // Hold the read lock across the bounded-blocking sends below. Both
221− // Shutdown and per-subscriber teardown close subscriber channels under
222− // the write lock, so holding RLock here is what guarantees a send can
223− // never race a close. The earlier lock-free snapshot released b.mu
224− // before sending, which let Shutdown/unsubscribe close a channel
225− // mid-send — a data race the race detector flags (the recover() that
226− // used to sit in mustDeliver only masked the resulting panic).
227− //
228− // The cost is bounded and paid only under saturation: a stalled
229− // subscriber can delay Subscribe/Shutdown by at most (number of
230− // subscribers × mustDeliverTimeout). With this app's handful of
231− // subscribers that ceiling is a few hundred milliseconds, and only
232− // when a subscriber's buffer is already full.
233202 b.mu.RLock()
234203 defer b.mu.RUnlock()
235204
@@ -241,38 +210,27 @@ func (b *Broker[T]) PublishMustDeliver(ctx context.Context, t EventType, payload
241210
242211 event := Event[T]{Type: t, Payload: payload}
243212 timeout := b.mustDeliverTimeout
213+
244214 for sub := range b.subs {
245− if b.mustDeliver(ctx, sub, event, timeout) {
246− // Context cancelled; stop delivering to the remaining subs.
247− return
215+ // Fast path: non-blocking send.
216+ select {
217+ case sub <- event:
218+ continue
219+ default:
248220 }
249− }
250−}
251−
252−// mustDeliver performs one bounded-blocking send. It reports whether the
253−// context was cancelled (so the caller stops delivering to remaining
254−// subscribers). The caller holds b.mu.RLock, and every subscriber-channel
255−// close happens under b.mu's write lock, so the channel cannot be closed
256−// concurrently with this send.
257−func (b *Broker[T]) mustDeliver(ctx context.Context, sub chan Event[T], event Event[T], timeout time.Duration) (ctxDone bool) {
258− // Fast path: non-blocking send.
259− select {
260− case sub <- event:
261− return false
262− default:
263− }
264221
265− // Slow path: bounded blocking send.
266− timer := time.NewTimer(timeout)
267− defer timer.Stop()
268− select {
269− case sub <- event:
270− case <-timer.C:
271− b.mustDeliverDropCount.Add(1)
272− slog.Error("PublishMustDeliver timed out delivering event",
273− "type", event.Type, "timeout", timeout)
274− case <-ctx.Done():
275− return true
222+ // Slow path: bounded blocking send.
223+ timer := time.NewTimer(timeout)
224+ select {
225+ case sub <- event:
226+ timer.Stop()
227+ case <-timer.C:
228+ b.mustDeliverDropCount.Add(1)
229+ slog.Error("PublishMustDeliver timed out delivering event",
230+ "type", t, "timeout", timeout)
231+ case <-ctx.Done():
232+ timer.Stop()
233+ return
234+ }
276235 }
277− return false
278236}
internal/pubsub/broker_test.godeleted−117
@@ -1,117 +0,0 @@
1−package pubsub
2−
3−import (
4− "context"
5− "sync"
6− "testing"
7− "time"
8−)
9−
10−// TestConcurrentShutdown ensures concurrent Shutdown calls don't panic on a
11−// double-close of the done channel.
12−func TestConcurrentShutdown(t *testing.T) {
13− t.Parallel()
14− b := NewBroker[int]()
15− ctx, cancel := context.WithCancel(context.Background())
16− defer cancel()
17− for range 8 {
18− b.Subscribe(ctx)
19− }
20−
21− var wg sync.WaitGroup
22− for range 16 {
23− wg.Add(1)
24− go func() {
25− defer wg.Done()
26− b.Shutdown()
27− }()
28− }
29− wg.Wait()
30−}
31−
32−// TestPublishMustDeliverDuringShutdown ensures sending outside the lock is
33−// safe when a subscriber channel is closed concurrently.
34−func TestPublishMustDeliverDuringShutdown(t *testing.T) {
35− t.Parallel()
36− b := NewBrokerWithOptions[int](1)
37− b.SetMustDeliverTimeout(time.Millisecond)
38−
39− ctx, cancel := context.WithCancel(context.Background())
40− defer cancel()
41− for range 8 {
42− b.Subscribe(ctx) // never drained, so buffers fill and sends block
43− }
44−
45− var wg sync.WaitGroup
46− wg.Add(2)
47− go func() {
48− defer wg.Done()
49− for range 50 {
50− b.PublishMustDeliver(context.Background(), CreatedEvent, 1)
51− }
52− }()
53− go func() {
54− defer wg.Done()
55− b.Shutdown()
56− }()
57− wg.Wait()
58−}
59−
60−// TestPublishMustDeliverDuringUnsubscribe ensures a send can't race a
61−// per-subscriber teardown: each subscriber is cancelled individually while
62−// PublishMustDeliver is delivering to it. Run under -race, this fails if the
63−// broker ever sends on a channel a teardown goroutine is concurrently closing.
64−func TestPublishMustDeliverDuringUnsubscribe(t *testing.T) {
65− t.Parallel()
66− b := NewBrokerWithOptions[int](1)
67− b.SetMustDeliverTimeout(time.Millisecond)
68− defer b.Shutdown()
69−
70− var wg sync.WaitGroup
71− wg.Add(1)
72− go func() {
73− defer wg.Done()
74− for range 200 {
75− b.PublishMustDeliver(context.Background(), CreatedEvent, 1)
76− }
77− }()
78−
79− for range 8 {
80− wg.Add(1)
81− go func() {
82− defer wg.Done()
83− // Per-subscriber context: cancelling it tears the subscriber
84− // down (removing + closing its channel) concurrently with the
85− // publisher above. Never drained, so its buffer saturates and
86− // sends block, widening the race window.
87− ctx, cancel := context.WithCancel(context.Background())
88− b.Subscribe(ctx)
89− cancel()
90− }()
91− }
92− wg.Wait()
93−}
94−
95−// TestSubscribeGoroutineExitsOnShutdown ensures the per-subscriber goroutine
96−// unblocks on Shutdown even when its context outlives the broker.
97−func TestSubscribeGoroutineExitsOnShutdown(t *testing.T) {
98− t.Parallel()
99− b := NewBroker[int]()
100− // Background context: without the b.done wakeup this goroutine would
101− // leak forever after Shutdown.
102− ch := b.Subscribe(context.Background())
103− b.Shutdown()
104−
105− select {
106− case _, ok := <-ch:
107− if ok {
108− t.Fatal("expected channel to be closed after Shutdown")
109− }
110− case <-time.After(time.Second):
111− t.Fatal("subscriber channel not closed after Shutdown")
112− }
113−
114− if got := b.GetSubscriberCount(); got != 0 {
115− t.Fatalf("subscriber count = %d, want 0", got)
116− }
117−}
internal/server/proto.go+3−13
@@ -3,6 +3,7 @@ package server
33import (
44 "encoding/json"
55 "errors"
6+ "fmt"
67 "net/http"
78
89 "github.com/charmbracelet/crush/internal/backend"
@@ -305,12 +306,7 @@ func (c *controllerV1) handleGetWorkspaceEvents(w http.ResponseWriter, r *http.R
305306 continue
306307 }
307308
308− // Write the SSE frame in three chunks to avoid the
309− // []byte->string copy that a "%s"-formatted write would
310− // incur on the (potentially large) marshaled payload.
311− _, _ = w.Write([]byte("data: "))
312− _, _ = w.Write(data)
313− _, _ = w.Write([]byte("\n\n"))
309+ fmt.Fprintf(w, "data: %s\n\n", data)
314310 flusher.Flush()
315311 }
316312 }
@@ -386,17 +382,11 @@ func (c *controllerV1) handleGetWorkspaceSessions(w http.ResponseWriter, r *http
386382 return
387383 }
388384 ws, _ := c.backend.GetWorkspace(id)
389− // Snapshot the client->session attachment counts once instead of
390− // re-scanning every client for each session (O(sessions x clients)).
391− var attachedCounts map[string]int
392− if ws != nil {
393− attachedCounts = ws.AttachedClientCountsBySession()
394− }
395385 result := make([]proto.Session, len(sessions))
396386 for i, s := range sessions {
397387 result[i] = sessionToProto(s)
398388 result[i].IsBusy = isSessionBusy(ws, s.ID)
399− result[i].AttachedClients = attachedCounts[s.ID]
389+ result[i].AttachedClients = attachedClients(ws, s.ID)
400390 }
401391 jsonEncode(w, result)
402392}
internal/session/session.go+18−13
@@ -27,7 +27,7 @@ const (
2727// HashID returns the XXH3 hash of a session ID (UUID) as a hex string.
2828func HashID(id string) string {
2929 h := xxh3.New()
30− _, _ = h.WriteString(id)
30+ h.WriteString(id)
3131 return fmt.Sprintf("%x", h.Sum(nil))
3232}
3333
@@ -223,35 +223,29 @@ func (s *service) Save(ctx context.Context, session Session) (Session, error) {
223223// UpdateTitleAndUsage updates only the title and usage fields atomically.
224224// This is safer than fetching, modifying, and saving the entire session.
225225func (s *service) UpdateTitleAndUsage(ctx context.Context, sessionID, title string, promptTokens, completionTokens int64, cost float64) error {
226− dbSession, err := s.q.UpdateSessionTitleAndUsage(ctx, db.UpdateSessionTitleAndUsageParams{
226+ if err := s.q.UpdateSessionTitleAndUsage(ctx, db.UpdateSessionTitleAndUsageParams{
227227 ID: sessionID,
228228 Title: title,
229229 PromptTokens: promptTokens,
230230 CompletionTokens: completionTokens,
231231 Cost: cost,
232− })
233− if err != nil {
232+ }); err != nil {
234233 return err
235234 }
236− session := s.fromDBItem(dbSession)
237− s.applyEstimatedUsageState(&session)
238− s.Publish(pubsub.UpdatedEvent, session)
235+ s.publishSessionUpdate(ctx, sessionID)
239236 return nil
240237}
241238
242239// Rename updates only the title of a session without touching updated_at or
243240// usage fields.
244241func (s *service) Rename(ctx context.Context, id string, title string) error {
245− dbSession, err := s.q.RenameSession(ctx, db.RenameSessionParams{
242+ if err := s.q.RenameSession(ctx, db.RenameSessionParams{
246243 ID: id,
247244 Title: title,
248− })
249− if err != nil {
245+ }); err != nil {
250246 return err
251247 }
252− session := s.fromDBItem(dbSession)
253− s.applyEstimatedUsageState(&session)
254− s.Publish(pubsub.UpdatedEvent, session)
248+ s.publishSessionUpdate(ctx, id)
255249 return nil
256250}
257251
@@ -268,6 +262,17 @@ func (s *service) List(ctx context.Context) ([]Session, error) {
268262 return sessions, nil
269263}
270264
265+// publishSessionUpdate re-fetches a session and publishes an UpdatedEvent so
266+// that UI subscribers reflect title or usage changes.
267+func (s *service) publishSessionUpdate(ctx context.Context, sessionID string) {
268+ session, err := s.Get(ctx, sessionID)
269+ if err != nil {
270+ slog.Error("Failed to re-fetch session for event publish", "error", err, "sessionID", sessionID)
271+ return
272+ }
273+ s.Publish(pubsub.UpdatedEvent, session)
274+}
275+
271276func (s *service) applyEstimatedUsageState(session *Session) {
272277 s.estimatedUsageMu.RLock()
273278 session.EstimatedUsage = s.estimatedUsage[session.ID]
internal/shell/background.go+1−1
@@ -185,7 +185,7 @@ func (m *BackgroundShellManager) Cleanup() int {
185185 }
186186
187187 for _, id := range toRemove {
188− _ = m.Remove(id)
188+ m.Remove(id)
189189 }
190190
191191 return len(toRemove)
internal/shell/persist_message.godeleted−44
@@ -1,44 +0,0 @@
1−package shell
2−
3−import (
4− "context"
5− "log/slog"
6− "strings"
7−
8− "github.com/charmbracelet/crush/internal/message"
9−)
10−
11−// PersistOutput stores a bang-mode shell command result as a user message.
12−// If the target session no longer exists (deleted before or during the
13−// command), persistence is skipped without surfacing an error.
14−func PersistOutput(
15− ctx context.Context,
16− messages message.Service,
17− sessionID, command, output string,
18− exitCode int,
19−) error {
20− if sessionID == "" {
21− return nil
22− }
23−
24− _, err := messages.Create(ctx, sessionID, message.CreateMessageParams{
25− Role: message.User,
26− Parts: []message.ContentPart{message.ShellCommand{
27− Command: command,
28− Output: output,
29− ExitCode: exitCode,
30− }},
31− })
32− // The messages table has a single foreign key (session_id), so an FK
33− // failure here can only mean the session is gone. We match on the error
34− // text because the codebase builds against two swappable SQLite drivers
35− // (modernc and ncruces) and this is the one signal stable across both.
36− if err != nil && strings.Contains(err.Error(), "FOREIGN KEY constraint failed") {
37− slog.Debug(
38− "Skipping shell command persistence: session no longer exists",
39− "session_id", sessionID,
40− )
41− return nil
42− }
43− return err
44−}
internal/shell/persist_message_test.godeleted−69
@@ -1,69 +0,0 @@
1−package shell
2−
3−import (
4− "testing"
5−
6− "github.com/charmbracelet/crush/internal/db"
7− "github.com/charmbracelet/crush/internal/message"
8− "github.com/charmbracelet/crush/internal/session"
9− "github.com/google/uuid"
10− "github.com/stretchr/testify/require"
11−)
12−
13−func TestPersistOutput_SkipsMissingSession(t *testing.T) {
14− t.Parallel()
15−
16− conn, err := db.Connect(t.Context(), t.TempDir())
17− require.NoError(t, err)
18− t.Cleanup(func() { conn.Close() })
19−
20− messages := message.NewService(db.New(conn))
21−
22− missingID := uuid.New().String()
23− err = PersistOutput(t.Context(), messages, missingID, "cat file.txt", "hello", 0)
24− require.NoError(t, err)
25−
26− stored, err := messages.List(t.Context(), missingID)
27− require.NoError(t, err)
28− require.Empty(t, stored)
29−}
30−
31−func TestPersistOutput_NoOpForEmptySessionID(t *testing.T) {
32− t.Parallel()
33−
34− conn, err := db.Connect(t.Context(), t.TempDir())
35− require.NoError(t, err)
36− t.Cleanup(func() { conn.Close() })
37−
38− messages := message.NewService(db.New(conn))
39−
40− require.NoError(t, PersistOutput(t.Context(), messages, "", "echo hi", "hi", 0))
41−}
42−
43−func TestPersistOutput_PersistsForExistingSession(t *testing.T) {
44− t.Parallel()
45−
46− conn, err := db.Connect(t.Context(), t.TempDir())
47− require.NoError(t, err)
48− t.Cleanup(func() { conn.Close() })
49−
50− q := db.New(conn)
51− sessions := session.NewService(q, conn)
52− messages := message.NewService(q)
53−
54− sess, err := sessions.Create(t.Context(), "shell test")
55− require.NoError(t, err)
56−
57− err = PersistOutput(t.Context(), messages, sess.ID, "cat file.txt", "hello", 0)
58− require.NoError(t, err)
59−
60− stored, err := messages.List(t.Context(), sess.ID)
61− require.NoError(t, err)
62− require.Len(t, stored, 1)
63− require.Equal(t, message.User, stored[0].Role)
64− shellParts := stored[0].ShellCommands()
65− require.Len(t, shellParts, 1)
66− require.Equal(t, "cat file.txt", shellParts[0].Command)
67− require.Equal(t, "hello", shellParts[0].Output)
68− require.Zero(t, shellParts[0].ExitCode)
69−}
internal/shell/run.go−30
@@ -255,36 +255,6 @@ func withNonInteractiveEnv(env []string) []string {
255255 return append(result, nonInteractiveEnvVars...)
256256}
257257
258−// herdrEnvVars are the environment variables herdr injects into panes
259−// so agents can report state over its Unix socket API. Subprocesses
260−// must not inherit these: a child process that calls herdr.Init()
261−// would attach to the parent's pane and, on exit, release its agent
262−// authority — making the status vanish. Stripping them here closes
263−// that gap for every command the bash tool runs.
264−var herdrEnvVars = []string{
265− "HERDR_ENV",
266− "HERDR_SOCKET_PATH",
267− "HERDR_PANE_ID",
268−}
269−
270−// withoutHerdrEnv returns env with all HERDR_* variables removed.
271−// The returned slice is a new allocation safe to use concurrently
272−// with the input.
273−func withoutHerdrEnv(env []string) []string {
274− strip := make(map[string]bool, len(herdrEnvVars))
275− for _, k := range herdrEnvVars {
276− strip[k] = true
277− }
278− result := make([]string, 0, len(env))
279− for _, e := range env {
280− if key, _, ok := strings.Cut(e, "="); ok && strip[key] {
281− continue
282− }
283− result = append(result, e)
284− }
285− return result
286−}
287−
288258// standardHandlers returns the exec-handler middleware chain used by both
289259// [Run] and [Shell]. Order matters:
290260// 1. builtins first (so Crush's in-process jq wins over any PATH binary);
internal/shell/run_test.go−43
@@ -309,49 +309,6 @@ func TestWithNonInteractiveEnv_SliceIndependence(t *testing.T) {
309309 }
310310}
311311
312−func TestWithoutHerdrEnv_StripsAllVars(t *testing.T) {
313− t.Parallel()
314− env := []string{
315− "HERDR_ENV=1",
316− "HERDR_SOCKET_PATH=/tmp/herdr.sock",
317− "HERDR_PANE_ID=wA:p1",
318− "PATH=/usr/bin",
319− "HOME=/home/user",
320− }
321− result := withoutHerdrEnv(env)
322− for _, e := range result {
323− if strings.HasPrefix(e, "HERDR_") {
324− t.Errorf("herdr var not stripped: %s", e)
325− }
326− }
327− if !slices.Contains(result, "PATH=/usr/bin") {
328− t.Error("non-herdr var PATH was incorrectly removed")
329− }
330− if !slices.Contains(result, "HOME=/home/user") {
331− t.Error("non-herdr var HOME was incorrectly removed")
332− }
333−}
334−
335−func TestWithoutHerdrEnv_EmptyInput(t *testing.T) {
336− t.Parallel()
337− result := withoutHerdrEnv(nil)
338− if len(result) != 0 {
339− t.Errorf("expected empty result for nil input, got %v", result)
340− }
341−}
342−
343−func TestWithoutHerdrEnv_SliceIndependence(t *testing.T) {
344− t.Parallel()
345− env := []string{"HERDR_ENV=1", "FOO=bar"}
346− result := withoutHerdrEnv(env)
347− env[1] = "FOO=baz"
348− for _, e := range result {
349− if e == "FOO=baz" {
350− t.Error("result shares backing array with input")
351− }
352− }
353−}
354−
355312func TestRun_DiscardsNilWriters(t *testing.T) {
356313 // No panic when Stdout/Stderr are nil.
357314 err := Run(t.Context(), RunOptions{
internal/shell/shell.go−5
@@ -94,11 +94,6 @@ func NewShell(opts *Options) *Shell {
9494 env = os.Environ()
9595 }
9696
97− // Strip herdr pane-ownership vars so subprocesses (including test
98− // binaries and nested crush instances) can't attach to or release
99− // the parent pane's agent authority.
100− env = withoutHerdrEnv(env)
101−
10297 // Allow tools to detect execution by Crush.
10398 env = append(env, CrushEnvMarkers()...)
10499
internal/skills/embed.go+1−1
@@ -32,7 +32,7 @@ func DiscoverBuiltinWithStates() ([]*Skill, []*SkillState) {
3232 var discovered []*Skill
3333 var states []*SkillState
3434
35− _ = fs.WalkDir(builtinFS, "builtin", func(path string, d fs.DirEntry, err error) error {
35+ fs.WalkDir(builtinFS, "builtin", func(path string, d fs.DirEntry, err error) error {
3636 if err != nil {
3737 return nil
3838 }
internal/skills/skills.go+1−30
@@ -34,26 +34,6 @@ var (
3434 latestStatesMu sync.RWMutex
3535)
3636
37−// ignoredWalkDirs is a set of directory names that skill discovery never
38−// descends into. Pruning these avoids walking large dependency/VCS trees that
39−// cannot contain user skills.
40−var ignoredWalkDirs = map[string]bool{
41− "node_modules": true,
42− ".git": true,
43− ".svn": true,
44− ".hg": true,
45− "vendor": true,
46− "dist": true,
47− "build": true,
48− "target": true,
49− ".idea": true,
50− ".vscode": true,
51− "__pycache__": true,
52− ".cache": true,
53− "bin": true,
54− "obj": true,
55−}
56−
5737// Skill represents a parsed SKILL.md file.
5838type Skill struct {
5939 Name string `yaml:"name" json:"name"`
@@ -269,16 +249,7 @@ func DiscoverWithStates(paths []string) ([]*Skill, []*SkillState) {
269249 addState("", path, StateError, err)
270250 return nil
271251 }
272− if d.IsDir() {
273− // Prune large/irrelevant directory trees so discovery does
274− // not descend into dependency and VCS directories. Never
275− // prune the walk root itself.
276− if path != base && ignoredWalkDirs[d.Name()] {
277− return filepath.SkipDir
278− }
279− return nil
280− }
281− if d.Name() != SkillFileName {
252+ if d.IsDir() || d.Name() != SkillFileName {
282253 return nil
283254 }
284255 mu.Lock()
internal/ui/attachments/attachments.go+2−2
@@ -132,8 +132,8 @@ func (r *Renderer) Render(attachments []message.Attachment, deleting bool, width
132132 )
133133 }
134134
135− if i == fits && len(attachments) > i+1 {
136− chips = append(chips, lipgloss.NewStyle().Width(maxItemWidth).Render(fmt.Sprintf("%d more…", len(attachments)-fits-1)))
135+ if i == fits && len(attachments) > i {
136+ chips = append(chips, lipgloss.NewStyle().Width(maxItemWidth).Render(fmt.Sprintf("%d more…", len(attachments)-fits)))
137137 break
138138 }
139139 }
internal/ui/chat/agent.go+26−61
@@ -2,7 +2,6 @@ package chat
22
33import (
44 "encoding/json"
5− "fmt"
65 "strings"
76
87 tea "charm.land/bubbletea/v2"
@@ -25,18 +24,6 @@ type NestedToolContainer interface {
2524 AddNestedTool(tool ToolMessageItem)
2625}
2726
28−// collapsedNestedHint is the affordance shown under a collapsed sub-agent
29−// card, indicating its nested tool calls can be revealed with space.
30−const collapsedNestedHint = " ▸ space to expand"
31−
32−// toolCountLabel renders a "N tools" summary for a sub-agent header line.
33−func toolCountLabel(n int) string {
34− if n == 1 {
35− return "1 tool"
36− }
37− return fmt.Sprintf("%d tools", n)
38−}
39−
4027// AgentToolMessageItem is a message item that represents an agent tool call.
4128type AgentToolMessageItem struct {
4229 *baseToolMessageItem
@@ -57,7 +44,7 @@ func NewAgentToolMessageItem(
5744 canceled bool,
5845) *AgentToolMessageItem {
5946 t := &AgentToolMessageItem{}
60− t.baseToolMessageItem = newBaseToolMessageItem(sty, toolCall, result, &AgentToolRenderContext{agent: t}, canceled, "")
47+ t.baseToolMessageItem = newBaseToolMessageItem(sty, toolCall, result, &AgentToolRenderContext{agent: t}, canceled)
6148 // For the agent tool we keep spinning until the tool call is finished.
6249 t.spinningFunc = func(state SpinningState) bool {
6350 return !state.HasResult() && !state.IsCanceled()
@@ -147,15 +134,9 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
147134 _ = json.Unmarshal([]byte(opts.ToolCall.Input), &params)
148135
149136 prompt := params.Prompt
150− if !opts.ExpandedContent {
151− prompt = strings.ReplaceAll(prompt, "\n", " ")
152− }
137+ prompt = strings.ReplaceAll(prompt, "\n", " ")
153138
154− header := toolHeader(sty, opts.Status, "Agent", cappedWidth, opts)
155− nestedCount := len(r.agent.nestedTools)
156− if nestedCount > 0 {
157− header += " " + sty.Tool.ParamMain.Render(toolCountLabel(nestedCount))
158− }
139+ header := toolHeader(sty, opts.Status, "Agent", cappedWidth, opts.Compact)
159140 if opts.Compact {
160141 return header
161142 }
@@ -181,23 +162,18 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
181162 ),
182163 )
183164
184− // Nested tool calls are collapsed by default to keep the conversation
185− // readable; press space to expand the full call tree.
186− var parts []string
187− if opts.ExpandedContent {
188− childTools := tree.Root(header)
189− for _, nestedTool := range r.agent.nestedTools {
190− childView := nestedTool.Render(remainingWidth)
191− childTools.Child(childView)
192− }
193− parts = append(parts, childTools.Enumerator(roundedEnumerator(2, taskTagWidth-5)).String())
194− } else {
195− parts = append(parts, header)
196− if nestedCount > 0 {
197− parts = append(parts, sty.Tool.AgentPrompt.Render(collapsedNestedHint))
198− }
165+ // Build tree with nested tool calls.
166+ childTools := tree.Root(header)
167+
168+ for _, nestedTool := range r.agent.nestedTools {
169+ childView := nestedTool.Render(remainingWidth)
170+ childTools.Child(childView)
199171 }
200172
173+ // Build parts.
174+ var parts []string
175+ parts = append(parts, childTools.Enumerator(roundedEnumerator(2, taskTagWidth-5)).String())
176+
201177 // Show animation if still running.
202178 if !opts.HasResult() && !opts.IsCanceled() {
203179 parts = append(parts, "", opts.Anim.Render())
@@ -238,7 +214,7 @@ func NewAgenticFetchToolMessageItem(
238214 canceled bool,
239215) *AgenticFetchToolMessageItem {
240216 t := &AgenticFetchToolMessageItem{}
241− t.baseToolMessageItem = newBaseToolMessageItem(sty, toolCall, result, &AgenticFetchToolRenderContext{fetch: t}, canceled, "")
217+ t.baseToolMessageItem = newBaseToolMessageItem(sty, toolCall, result, &AgenticFetchToolRenderContext{fetch: t}, canceled)
242218 // For the agentic fetch tool we keep spinning until the tool call is finished.
243219 t.spinningFunc = func(state SpinningState) bool {
244220 return !state.HasResult() && !state.IsCanceled()
@@ -319,9 +295,7 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
319295 _ = json.Unmarshal([]byte(opts.ToolCall.Input), &params)
320296
321297 prompt := params.Prompt
322− if !opts.ExpandedContent {
323− prompt = strings.ReplaceAll(prompt, "\n", " ")
324− }
298+ prompt = strings.ReplaceAll(prompt, "\n", " ")
325299
326300 // Build header with optional URL param.
327301 var toolParams []string
@@ -329,11 +303,7 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
329303 toolParams = append(toolParams, params.URL)
330304 }
331305
332− header := toolHeader(sty, opts.Status, "Agentic Fetch", cappedWidth, opts, toolParams...)
333− nestedCount := len(r.fetch.nestedTools)
334− if nestedCount > 0 {
335− header += " " + sty.Tool.ParamMain.Render(toolCountLabel(nestedCount))
336− }
306+ header := toolHeader(sty, opts.Status, "Agentic Fetch", cappedWidth, opts.Compact, toolParams...)
337307 if opts.Compact {
338308 return header
339309 }
@@ -359,23 +329,18 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
359329 ),
360330 )
361331
362− // Nested tool calls are collapsed by default to keep the conversation
363− // readable; press space to expand the full call tree.
364− var parts []string
365− if opts.ExpandedContent {
366− childTools := tree.Root(header)
367− for _, nestedTool := range r.fetch.nestedTools {
368− childView := nestedTool.Render(remainingWidth)
369− childTools.Child(childView)
370− }
371− parts = append(parts, childTools.Enumerator(roundedEnumerator(2, promptTagWidth-5)).String())
372− } else {
373− parts = append(parts, header)
374− if nestedCount > 0 {
375− parts = append(parts, sty.Tool.AgentPrompt.Render(collapsedNestedHint))
376− }
332+ // Build tree with nested tool calls.
333+ childTools := tree.Root(header)
334+
335+ for _, nestedTool := range r.fetch.nestedTools {
336+ childView := nestedTool.Render(remainingWidth)
337+ childTools.Child(childView)
377338 }
378339
340+ // Build parts.
341+ var parts []string
342+ parts = append(parts, childTools.Enumerator(roundedEnumerator(2, promptTagWidth-5)).String())
343+
379344 // Show animation if still running.
380345 if !opts.HasResult() && !opts.IsCanceled() {
381346 parts = append(parts, "", opts.Anim.Render())
internal/ui/chat/assistant.go+85−313
@@ -1,7 +1,9 @@
11package chat
22
33import (
4+ "encoding/binary"
45 "fmt"
6+ "hash/fnv"
57 "strings"
68
79 tea "charm.land/bubbletea/v2"
@@ -23,7 +25,7 @@ const assistantMessageTruncateFormat = "… (%d lines hidden) [click or space to
2325// promote the view to a full expansion. The promotion is wired through
2426// the existing ToggleExpanded path (click / space) — F5 deliberately
2527// does not add a new keybinding.
26−const assistantMessageTailWindowFormat = "… (%d earlier lines hidden) [click or space to expand]"
28+const assistantMessageTailWindowFormat = "… %d earlier lines hidden [click or space for full view]"
2729
2830// maxCollapsedThinkingHeight defines the maximum height of the thinking
2931const maxCollapsedThinkingHeight = 10
@@ -91,47 +93,11 @@ func (s *assistantSection) reset() {
9193 *s = assistantSection{}
9294}
9395
94−// FNV-64a constants. We fold bytes into a plain uint64 accumulator
95−// rather than allocating a fnv.New64a per call: the returned
96−// hash.Hash64 escapes to the heap, which is wasteful on the per-render
97−// hashing hot path. The algorithm (offset basis, prime, xor-then-multiply)
98−// is identical to hash/fnv, so hashes are byte-for-byte the same.
99−const (
100− fnvOffset64 = 14695981039346656037
101− fnvPrime64 = 1099511628211
102−)
103−
104−// fnvAddString folds s into an in-progress FNV-64a hash.
105−func fnvAddString(h uint64, s string) uint64 {
106− for i := 0; i < len(s); i++ {
107− h ^= uint64(s[i])
108− h *= fnvPrime64
109− }
110− return h
111−}
112−
113−// fnvAddBytes folds b into an in-progress FNV-64a hash.
114−func fnvAddBytes(h uint64, b []byte) uint64 {
115− for _, c := range b {
116− h ^= uint64(c)
117− h *= fnvPrime64
118− }
119− return h
120−}
121−
122−// fnvAddU64 folds v's 8 little-endian bytes into an in-progress hash,
123−// matching binary.LittleEndian.PutUint64 followed by a Write.
124−func fnvAddU64(h, v uint64) uint64 {
125− for i := 0; i < 8; i++ {
126− h ^= uint64(byte(v >> (8 * i)))
127− h *= fnvPrime64
128− }
129− return h
130−}
131−
132−// fnv64 hashes a single string with FNV-64a.
96+// fnv64 hashes a single string with FNV-64.
13397func fnv64(s string) uint64 {
134− return fnvAddString(fnvOffset64, s)
98+ h := fnv.New64a()
99+ _, _ = h.Write([]byte(s))
100+ return h.Sum64()
135101}
136102
137103// fnvFields hashes a list of byte fields with length-prefix framing
@@ -140,41 +106,14 @@ func fnv64(s string) uint64 {
140106// boundary between two fields). Each field is preceded by its
141107// length encoded as 8 bytes little-endian.
142108func fnvFields(fields ...[]byte) uint64 {
143− h := uint64(fnvOffset64)
109+ h := fnv.New64a()
110+ var lenBuf [8]byte
144111 for _, f := range fields {
145− h = fnvAddU64(h, uint64(len(f)))
146− h = fnvAddBytes(h, f)
147− }
148− return h
149−}
150−
151−// assistantRenderKeys bundles the per-render cache keys and tool-call
152−// count computed once at the top of Render/RawRender and threaded
153−// through the render path, so each section's source text is hashed only
154−// once and ToolCalls() is materialized only once per render.
155−type assistantRenderKeys struct {
156− thinkSrc, thinkExtra uint64
157− contentSrc, contentExtra uint64
158− errSrc, errExtra uint64
159− toolCallCount int
160−}
161−
162−// renderKeys computes the per-render keys once. It calls ToolCalls()
163−// exactly once and reuses its length everywhere the render path needs it.
164−func (a *AssistantMessageItem) renderKeys() assistantRenderKeys {
165− toolCallCount := len(a.message.ToolCalls())
166− thinkSrc, thinkExtra := a.thinkingKey(toolCallCount)
167− contentSrc, contentExtra := a.contentKey()
168− errSrc, errExtra := a.errorKey()
169− return assistantRenderKeys{
170− thinkSrc: thinkSrc,
171− thinkExtra: thinkExtra,
172− contentSrc: contentSrc,
173− contentExtra: contentExtra,
174− errSrc: errSrc,
175− errExtra: errExtra,
176− toolCallCount: toolCallCount,
112+ binary.LittleEndian.PutUint64(lenBuf[:], uint64(len(f)))
113+ _, _ = h.Write(lenBuf[:])
114+ _, _ = h.Write(f)
177115 }
116+ return h.Sum64()
178117}
179118
180119// AssistantMessageItem represents an assistant message in the chat UI.
@@ -269,36 +208,23 @@ func (a *AssistantMessageItem) ID() string {
269208
270209// RawRender implements [MessageItem].
271210func (a *AssistantMessageItem) RawRender(width int) string {
272− return a.rawRender(width, a.renderKeys())
273−}
274−
275−// rawRenderBody renders the assistant message body (thinking / content /
276−// error sections) WITHOUT the spinner suffix. The body is stable across
277−// animation ticks — only the spinner changes — so Render caches its
278−// prefixed form and reuses it while re-rendering just the spinner.
279−func (a *AssistantMessageItem) rawRenderBody(width int, keys assistantRenderKeys) string {
280211 cappedWidth := cappedMessageWidth(width)
281− content, height := a.renderMessageContent(cappedWidth, keys)
282− return a.renderHighlighted(content, cappedWidth, height)
283−}
284−
285−// rawRender renders the item body plus, when spinning, the spinner suffix.
286−// It is the reference full render; Render reproduces its output byte-for-byte
287−// via a cached body + freshly-prefixed spinner (validated in
288−// assistant_render_test.go).
289−func (a *AssistantMessageItem) rawRender(width int, keys assistantRenderKeys) string {
290− body := a.rawRenderBody(width, keys)
291− if !a.isSpinningWithToolCalls(keys.toolCallCount) {
292− return body
293− }
294− spinner := a.renderSpinning()
295− if spinner == "" {
296− return body
212+
213+ var spinner string
214+ if a.isSpinning() {
215+ spinner = a.renderSpinning()
297216 }
298− if body != "" {
299− return body + "\n\n" + spinner
217+
218+ content, height := a.renderMessageContent(cappedWidth)
219+ highlightedContent := a.renderHighlighted(content, cappedWidth, height)
220+ if spinner != "" {
221+ if highlightedContent != "" {
222+ highlightedContent += "\n\n"
223+ }
224+ return highlightedContent + spinner
300225 }
301− return spinner
226+
227+ return highlightedContent
302228}
303229
304230// Render implements MessageItem.
@@ -317,82 +243,30 @@ func (a *AssistantMessageItem) Render(width int) string {
317243 // drop. Bypass the cache while spinning (RawRender's spinner
318244 // suffix changes every animation frame) or while a highlight
319245 // range is active (selection drag).
320− keys := a.renderKeys()
321− spinning := a.isSpinningWithToolCalls(keys.toolCallCount)
246+ useCache := !a.isSpinning() && !a.isHighlighted()
322247 cappedWidth := cappedMessageWidth(width)
323− key := a.prefixCacheKey(cappedWidth, keys)
324−
325− // Only render the style actually used; rendering both wastes work
326− // (lipgloss.Render is not free) when one is discarded.
327− var prefix string
328− if a.focused {
329− prefix = a.sty.Messages.AssistantFocused.Render()
330− } else {
331− prefix = a.sty.Messages.AssistantBlurred.Render()
332− }
333−
334− // Resolve the prefixed body. Its cache key excludes the spinner frame,
335− // so it stays valid across animation ticks — the whole point of the
336− // decoupling: prefix the (potentially long) thinking body once per
337− // content change instead of on every 20fps spinner tick. Highlighting
338− // mutates the body itself, so it bypasses the cache.
339− useCache := !a.isHighlighted()
340− prefixedBody, ok := "", false
248+ key := a.prefixCacheKey(cappedWidth)
341249 if useCache {
342− prefixedBody, ok = a.getCachedPrefixedRender(width, key)
343− }
344− bodyEmpty := false
345− if !ok {
346− body := a.rawRenderBody(width, keys)
347− bodyEmpty = body == ""
348− prefixedBody = prefixLines(prefix, body)
349− // Cache only a non-empty body: an empty body prefixes to the bare
350− // prefix and is cheap to recompute, and the cache treats "" as a
351− // miss, so caching it would be indistinguishable from no entry.
352− if useCache && !bodyEmpty {
353− a.setCachedPrefixedRender(prefixedBody, width, key)
250+ if cached, ok := a.getCachedPrefixedRender(width, key); ok {
251+ return cached
354252 }
355253 }
356− // A cache hit implies a non-empty body (empty bodies are never cached),
357− // so bodyEmpty stays false on the hit path.
358−
359− if !spinning {
360− return prefixedBody
361− }
362− spinner := a.renderSpinning()
363− if spinner == "" {
364− return prefixedBody
365− }
366− return assembleSpinningRender(prefix, prefixedBody, bodyEmpty, prefixLines(prefix, spinner))
367−}
368−
369−// prefixLines prepends prefix to every line of s, matching the per-line
370−// focus/selection prefix that Render applies (done manually rather than via
371−// lipgloss.Render, whose wrapping logic is costly for long messages).
372−func prefixLines(prefix, s string) string {
373− lines := strings.Split(s, "\n")
374− var sb strings.Builder
254+ focused := a.sty.Messages.AssistantFocused.Render()
255+ blurred := a.sty.Messages.AssistantBlurred.Render()
256+ rendered := a.RawRender(width)
257+ lines := strings.Split(rendered, "\n")
375258 for i, line := range lines {
376− if i > 0 {
377− sb.WriteString("\n")
259+ if a.focused {
260+ lines[i] = focused + line
261+ } else {
262+ lines[i] = blurred + line
378263 }
379− sb.WriteString(prefix)
380− sb.WriteString(line)
381264 }
382− return sb.String()
383−}
384−
385−// assembleSpinningRender reconstructs the prefixed body+spinner output
386−// byte-for-byte identically to prefixLines(prefix, body+"\n\n"+spinner),
387−// but from the separately-prefixed (and body-cached) parts. When the body is
388−// empty the reference render is the spinner alone; otherwise the "\n\n"
389−// separator becomes one prefixed empty line between body and spinner. This
390−// exact identity is validated in assistant_render_test.go.
391−func assembleSpinningRender(prefix, prefixedBody string, bodyEmpty bool, prefixedSpinner string) string {
392− if bodyEmpty {
393− return prefixedSpinner
265+ out := strings.Join(lines, "\n")
266+ if useCache {
267+ a.setCachedPrefixedRender(out, width, key)
394268 }
395− return prefixedBody + "\n" + prefix + "\n" + prefixedSpinner
269+ return out
396270}
397271
398272// prefixCacheKey builds the F3 prefixed-render cache key. We pack the
@@ -404,17 +278,27 @@ func assembleSpinningRender(prefix, prefixedBody string, bodyEmpty bool, prefixe
404278// folded in too because it controls the composition of
405279// renderMessageContent (e.g. appending the constant "Canceled"
406280// string) — that decision lives outside any section's own hash.
407−func (a *AssistantMessageItem) prefixCacheKey(cappedWidth int, keys assistantRenderKeys) uint64 {
408− h := uint64(fnvOffset64)
409− h = fnvAddU64(h, uint64(cappedWidth))
410− h = fnvAddU64(h, keys.thinkSrc)
411− h = fnvAddU64(h, keys.thinkExtra)
412− h = fnvAddU64(h, keys.contentSrc)
413− h = fnvAddU64(h, keys.contentExtra)
414− h = fnvAddU64(h, keys.errSrc)
415− h = fnvAddU64(h, keys.errExtra)
416− h = fnvAddU64(h, a.compositionKey())
417− fingerprint := h
281+func (a *AssistantMessageItem) prefixCacheKey(cappedWidth int) uint64 {
282+ thinkSrc, thinkExtra := a.thinkingKey()
283+ contentSrc, contentExtra := a.contentKey()
284+ errSrc, errExtra := a.errorKey()
285+ h := fnv.New64a()
286+ var buf [8]byte
287+ writeU64 := func(v uint64) {
288+ for i := range 8 {
289+ buf[i] = byte(v >> (8 * i))
290+ }
291+ _, _ = h.Write(buf[:])
292+ }
293+ writeU64(uint64(cappedWidth))
294+ writeU64(thinkSrc)
295+ writeU64(thinkExtra)
296+ writeU64(contentSrc)
297+ writeU64(contentExtra)
298+ writeU64(errSrc)
299+ writeU64(errExtra)
300+ writeU64(a.compositionKey())
301+ fingerprint := h.Sum64()
418302 var focusBit uint64
419303 if a.focused {
420304 focusBit = 1
@@ -443,20 +327,20 @@ func (a *AssistantMessageItem) compositionKey() uint64 {
443327// content, and finish reason. Each section is served from its own cache;
444328// only the section whose source text or extras changed since the last
445329// render is recomputed.
446−func (a *AssistantMessageItem) renderMessageContent(width int, keys assistantRenderKeys) (string, int) {
330+func (a *AssistantMessageItem) renderMessageContent(width int) (string, int) {
447331 var messageParts []string
448332 thinking := strings.TrimSpace(a.message.ReasoningContent().Thinking)
449333 content := strings.TrimSpace(a.message.Content().Text)
450334
451335 if thinking != "" {
452− messageParts = append(messageParts, a.cachedThinking(width, keys))
336+ messageParts = append(messageParts, a.cachedThinking(width))
453337 }
454338
455339 if content != "" {
456340 if thinking != "" {
457341 messageParts = append(messageParts, "")
458342 }
459− messageParts = append(messageParts, a.cachedContent(width, keys))
343+ messageParts = append(messageParts, a.cachedContent(width))
460344 }
461345
462346 if a.message.IsFinished() {
@@ -464,7 +348,7 @@ func (a *AssistantMessageItem) renderMessageContent(width int, keys assistantRen
464348 case message.FinishReasonCanceled:
465349 messageParts = append(messageParts, a.sty.Messages.AssistantCanceled.Render("Canceled"))
466350 case message.FinishReasonError:
467− messageParts = append(messageParts, a.cachedError(width, keys))
351+ messageParts = append(messageParts, a.cachedError(width))
468352 }
469353 }
470354
@@ -477,11 +361,11 @@ func (a *AssistantMessageItem) renderMessageContent(width int, keys assistantRen
477361// thinking text that affects the rendered output: the view mode
478362// (collapsed / tail-window / full) and the footer state (which
479363// depends on IsThinking, ToolCalls, and ThinkingDuration).
480−func (a *AssistantMessageItem) thinkingKey(toolCallCount int) (uint64, uint64) {
364+func (a *AssistantMessageItem) thinkingKey() (uint64, uint64) {
481365 thinking := a.message.ReasoningContent().Thinking
482366 srcHash := fnv64(thinking)
483367
484− showFooter := !a.message.IsThinking() || toolCallCount > 0
368+ showFooter := !a.message.IsThinking() || len(a.message.ToolCalls()) > 0
485369 var durationStr string
486370 if showFooter {
487371 duration := a.message.ThinkingDuration()
@@ -528,20 +412,20 @@ func (a *AssistantMessageItem) errorKey() (uint64, uint64) {
528412// caching it on miss. The thinking-box height (used for click target
529413// detection) is preserved across hits via assistantSection.aux so the
530414// cached path never desyncs click detection.
531−func (a *AssistantMessageItem) cachedThinking(width int, keys assistantRenderKeys) string {
532− srcHash, extra := keys.thinkSrc, keys.thinkExtra
415+func (a *AssistantMessageItem) cachedThinking(width int) string {
416+ srcHash, extra := a.thinkingKey()
533417 if a.thinkingSec.hit(width, srcHash, extra) {
534418 a.thinkingBoxHeight = a.thinkingSec.aux
535419 return a.thinkingSec.out
536420 }
537− out := a.renderThinking(a.message.ReasoningContent().Thinking, width, keys.toolCallCount)
421+ out := a.renderThinking(a.message.ReasoningContent().Thinking, width)
538422 a.thinkingSec.store(width, srcHash, extra, out, a.thinkingBoxHeight)
539423 return out
540424}
541425
542426// cachedContent returns the rendered content section.
543−func (a *AssistantMessageItem) cachedContent(width int, keys assistantRenderKeys) string {
544− srcHash, extra := keys.contentSrc, keys.contentExtra
427+func (a *AssistantMessageItem) cachedContent(width int) string {
428+ srcHash, extra := a.contentKey()
545429 if a.contentSec.hit(width, srcHash, extra) {
546430 return a.contentSec.out
547431 }
@@ -551,8 +435,8 @@ func (a *AssistantMessageItem) cachedContent(width int, keys assistantRenderKeys
551435}
552436
553437// cachedError returns the rendered error section.
554−func (a *AssistantMessageItem) cachedError(width int, keys assistantRenderKeys) string {
555− srcHash, extra := keys.errSrc, keys.errExtra
438+func (a *AssistantMessageItem) cachedError(width int) string {
439+ srcHash, extra := a.errorKey()
556440 if a.errorSec.hit(width, srcHash, extra) {
557441 return a.errorSec.out
558442 }
@@ -568,35 +452,18 @@ func (a *AssistantMessageItem) cachedError(width int, keys assistantRenderKeys)
568452// boundary problem §4.4 of the design note flags. The bordered
569453// ThinkingBox style is applied on top of the (already-windowed)
570454// lines so the visual box matches what the user sees today.
571−func (a *AssistantMessageItem) renderThinking(thinking string, width int, toolCallCount int) string {
455+func (a *AssistantMessageItem) renderThinking(thinking string, width int) string {
572456 renderer := common.QuietMarkdownRenderer(a.sty, width)
573−
574− // Cap the raw text handed to glamour while the block is windowed.
575− // The collapsed and tail-window views only ever DISPLAY the last
576− // N rendered lines, so rendering the entire (unbounded) reasoning
577− // trace on every ~33ms streaming flush is wasted work that grows
578− // with the trace — O(n) per flush, O(n²) over the stream — and is
579− // the source of the "long thinking makes the UI lag" problem.
580− // Full expansion is an explicit request to see everything and is
581− // never bounded. droppedLines keeps the "N lines hidden" count
582− // roughly truthful and never affects what is displayed.
583− windowed, droppedLines := boundThinkingInput(thinking, a.thinkingViewMode, width)
584− rendered := a.streamingThinking.Render(windowed, width, renderer)
457+ rendered := a.streamingThinking.Render(thinking, width, renderer)
585458 rendered = strings.TrimSpace(rendered)
586459
587460 lines := strings.Split(rendered, "\n")
588− renderedLines := len(lines)
589− // totalLines counts everything the trace would render to, so the
590− // affordance's hidden-line count stays honest even though only
591− // the windowed tail was actually rendered.
592− totalLines := renderedLines + droppedLines
461+ totalLines := len(lines)
593462
594463 switch a.thinkingViewMode {
595464 case thinkingCollapsed:
596465 if totalLines > maxCollapsedThinkingHeight {
597− if renderedLines > maxCollapsedThinkingHeight {
598− lines = lines[renderedLines-maxCollapsedThinkingHeight:]
599− }
466+ lines = lines[totalLines-maxCollapsedThinkingHeight:]
600467 hint := a.sty.Messages.ThinkingTruncationHint.Render(
601468 fmt.Sprintf(assistantMessageTruncateFormat, totalLines-maxCollapsedThinkingHeight),
602469 )
@@ -604,9 +471,7 @@ func (a *AssistantMessageItem) renderThinking(thinking string, width int, toolCa
604471 }
605472 case thinkingTailWindow:
606473 if totalLines > maxExpandedThinkingTailLines {
607− if renderedLines > maxExpandedThinkingTailLines {
608− lines = lines[renderedLines-maxExpandedThinkingTailLines:]
609− }
474+ lines = lines[totalLines-maxExpandedThinkingTailLines:]
610475 hint := a.sty.Messages.ThinkingTruncationHint.Render(
611476 fmt.Sprintf(assistantMessageTailWindowFormat, totalLines-maxExpandedThinkingTailLines),
612477 )
@@ -620,7 +485,7 @@ func (a *AssistantMessageItem) renderThinking(thinking string, width int, toolCa
620485
621486 var footer string
622487 // if thinking is done add the thought for footer
623− if !a.message.IsThinking() || toolCallCount > 0 {
488+ if !a.message.IsThinking() || len(a.message.ToolCalls()) > 0 {
624489 duration := a.message.ThinkingDuration()
625490 if duration.String() != "0s" {
626491 footer = a.sty.Messages.ThinkingFooterTitle.Render("Thought for ") +
@@ -635,94 +500,6 @@ func (a *AssistantMessageItem) renderThinking(thinking string, width int, toolCa
635500 return result
636501}
637502
638−// boundThinkingInput caps the raw reasoning text fed to glamour so a
639−// streaming flush costs O(window) instead of O(entire trace). It
640−// returns the (possibly shortened) text to render plus the number of
641−// source lines dropped ahead of the window.
642−//
643−// A full render is preserved for [thinkingFullExpanded] (the user
644−// asked to see everything) and whenever the trace already fits the
645−// budget, so short blocks and the non-streaming steady state behave
646−// exactly as before.
647−//
648−// The window is snapped forward to a line boundary so a source line
649−// is never cut mid-way, and a code fence left open by the cut is
650−// re-opened at the window head so the tail keeps rendering as code
651−// (matching reality) instead of flipping to prose. Any other
652−// construct a raw cut might tear — a list, table, or blockquote at
653−// the very top of the window — only corrupts lines that sit above
654−// the displayed tail slice and are discarded before display.
655−func boundThinkingInput(thinking string, mode thinkingViewMode, width int) (windowed string, droppedLines int) {
656− budget := thinkingWindowBytes(mode, width)
657− if budget <= 0 || len(thinking) <= budget {
658− return thinking, 0
659− }
660− // The streamingMarkdown incremental cache already keeps a flush
661− // cheap when a safe boundary sits within `budget` of the end: it
662− // re-renders only the small trailing segment. Windowing would
663− // break that fast path (a sliding window is never a prefix
664− // extension, forcing a cache reset every flush), so only window
665− // when the tail since the last boundary is itself larger than the
666− // budget — the "wall of text with no paragraph break" case the
667− // cache cannot handle.
668− if b := findSafeMarkdownBoundary(thinking); b >= 0 && len(thinking)-b <= budget {
669− return thinking, 0
670− }
671− cut := len(thinking) - budget
672− // Snap forward to the start of the next line so the window never
673− // begins in the middle of a source line.
674− if nl := strings.IndexByte(thinking[cut:], '\n'); nl >= 0 {
675− cut += nl + 1
676− }
677− dropped := thinking[:cut]
678− windowed = thinking[cut:]
679− // Re-open a code fence left dangling by the cut so the window's
680− // code renders as code rather than prose.
681− if fenceLineCount(dropped)%2 != 0 {
682− windowed = "```\n" + windowed
683− }
684− return windowed, strings.Count(dropped, "\n")
685−}
686−
687−// thinkingWindowBytes returns the maximum bytes of raw reasoning text
688−// to render for a view mode, or -1 for "no bound". The budget scales
689−// with the wrap width so the window still comfortably overfills the
690−// visible line cap after wrapping, with a floor for narrow widths.
691−func thinkingWindowBytes(mode thinkingViewMode, width int) int {
692− if width < 1 {
693− width = 1
694− }
695− switch mode {
696− case thinkingCollapsed:
697− return max(4096, maxCollapsedThinkingHeight*width*8)
698− case thinkingTailWindow:
699− return max(32768, maxExpandedThinkingTailLines*width*4)
700− default:
701− return -1
702− }
703−}
704−
705−// fenceLineCount counts fenced-code-block delimiter lines in s. An
706−// odd count means a fence is still open at the end of s. Reuses the
707−// same [isFenceLine] rule as the streaming-markdown boundary scanner.
708−func fenceLineCount(s string) int {
709− n := 0
710− start := 0
711− for i := 0; i < len(s); i++ {
712− if s[i] != '\n' {
713− continue
714− }
715− if isFenceLine(s[start:i]) {
716− n++
717− }
718− start = i + 1
719− }
720− if start < len(s) && isFenceLine(s[start:]) {
721− n++
722− }
723− return n
724−}
725−
726503// renderMarkdown renders content as markdown. F8 routes the call
727504// through streamingContent, which caches the glamour render of a
728505// "stable prefix" so each streaming flush only re-renders the
@@ -758,16 +535,11 @@ func (a *AssistantMessageItem) renderError(width int) string {
758535
759536// isSpinning returns true if the assistant message is still generating.
760537func (a *AssistantMessageItem) isSpinning() bool {
761− return a.isSpinningWithToolCalls(len(a.message.ToolCalls()))
762−}
763−
764−// isSpinningWithToolCalls is isSpinning given an already-computed
765−// tool-call count, so the render path avoids re-materializing ToolCalls().
766−func (a *AssistantMessageItem) isSpinningWithToolCalls(toolCallCount int) bool {
767538 isThinking := a.message.IsThinking()
768539 isFinished := a.message.IsFinished()
769540 hasContent := strings.TrimSpace(a.message.Content().Text) != ""
770− return (isThinking || !isFinished) && !hasContent && toolCallCount == 0
541+ hasToolCalls := len(a.message.ToolCalls()) > 0
542+ return (isThinking || !isFinished) && !hasContent && !hasToolCalls
771543}
772544
773545// SetMessage is used to update the underlying message. Only the
internal/ui/chat/assistant_render_test.godeleted−78
@@ -1,78 +0,0 @@
1−package chat
2−
3−import (
4− "strings"
5− "testing"
6−)
7−
8−// combineBodySpinner replicates rawRender's spinning combine: an empty body
9−// yields the spinner alone, otherwise the two are joined by a blank line.
10−func combineBodySpinner(body, spinner string) string {
11− if body != "" {
12− return body + "\n\n" + spinner
13− }
14− return spinner
15−}
16−
17−// TestAssembleSpinningRender_ByteIdentical proves the decoupled Render path
18−// (cache the prefixed body, prefix the spinner separately, reassemble) is
19−// byte-for-byte identical to the old path of prefixing the full
20−// body+spinner string in one pass. If this ever diverges, the spinner
21−// decoupling would visibly corrupt assistant rendering.
22−func TestAssembleSpinningRender_ByteIdentical(t *testing.T) {
23− t.Parallel()
24− prefixes := []string{"", "P", "▏ ", "\x1b[38;5;1m│\x1b[0m "}
25− bodies := []string{
26− "",
27− "single line",
28− "line1\nline2\nline3",
29− "has\n\ninternal blank",
30− "trailing\n",
31− }
32− spinners := []string{
33− "spin",
34− "spin1\nspin2",
35− "⠋ Thinking",
36− }
37− for _, prefix := range prefixes {
38− for _, body := range bodies {
39− for _, spinner := range spinners {
40− want := prefixLines(prefix, combineBodySpinner(body, spinner))
41− got := assembleSpinningRender(
42− prefix,
43− prefixLines(prefix, body),
44− body == "",
45− prefixLines(prefix, spinner),
46− )
47− if got != want {
48− t.Errorf("prefix=%q body=%q spinner=%q\n got=%q\nwant=%q",
49− prefix, body, spinner, got, want)
50− }
51− }
52− }
53− }
54−}
55−
56−// TestPrefixLines matches the original inline per-line prefix loop.
57−func TestPrefixLines(t *testing.T) {
58− t.Parallel()
59− cases := []struct{ prefix, in, want string }{
60− {"P", "", "P"},
61− {"P", "a", "Pa"},
62− {"P", "a\nb", "Pa\nPb"},
63− {"P", "a\n\nb", "Pa\nP\nPb"},
64− {"", "a\nb", "a\nb"},
65− {"P", "trailing\n", "Ptrailing\nP"},
66− }
67− for _, c := range cases {
68− if got := prefixLines(c.prefix, c.in); got != c.want {
69− t.Errorf("prefixLines(%q,%q)=%q want %q", c.prefix, c.in, got, c.want)
70− }
71− }
72− // Cross-check against strings.Split semantics for a random-ish sample.
73− s := "x1\nx2\n\nx3"
74− want := strings.ReplaceAll("Q"+strings.ReplaceAll(s, "\n", "\nQ"), "\r", "")
75− if got := prefixLines("Q", s); got != want {
76− t.Errorf("prefixLines cross-check: got %q want %q", got, want)
77− }
78−}
internal/ui/chat/assistant_section_cache_test.go+3−3
@@ -149,8 +149,8 @@ func TestAssistantSectionCache_HashKeyDiscrimination(t *testing.T) {
149149 itemA := NewAssistantMessageItem(&sty, msgA).(*AssistantMessageItem)
150150 itemB := NewAssistantMessageItem(&sty, msgB).(*AssistantMessageItem)
151151
152− thinkSrcA, _ := itemA.thinkingKey(0)
153− thinkSrcB, _ := itemB.thinkingKey(0)
152+ thinkSrcA, _ := itemA.thinkingKey()
153+ thinkSrcB, _ := itemB.thinkingKey()
154154 require.NotEqual(t, thinkSrcA, thinkSrcB,
155155 "distinct thinking text must produce distinct FNV-64 source hashes")
156156
@@ -162,7 +162,7 @@ func TestAssistantSectionCache_HashKeyDiscrimination(t *testing.T) {
162162 // Identical source text on a fresh item must produce the same
163163 // hashes — keying invariant for cache hits.
164164 itemAClone := NewAssistantMessageItem(&sty, thinkingMessage("a3", "thinking A", "content A")).(*AssistantMessageItem)
165− thinkSrcAClone, _ := itemAClone.thinkingKey(0)
165+ thinkSrcAClone, _ := itemAClone.thinkingKey()
166166 contentSrcAClone, _ := itemAClone.contentKey()
167167 require.Equal(t, thinkSrcA, thinkSrcAClone)
168168 require.Equal(t, contentSrcA, contentSrcAClone)
internal/ui/chat/assistant_thinking_bench_test.godeleted−69
@@ -1,69 +0,0 @@
1−package chat
2−
3−import (
4− "fmt"
5− "strings"
6− "testing"
7−
8− "github.com/charmbracelet/crush/internal/message"
9− "github.com/charmbracelet/crush/internal/ui/styles"
10−)
11−
12−// benchThinkingStream simulates a reasoning stream: it grows `body`
13−// to full length in `steps` increments and drives the real
14−// AssistantMessageItem render path (SetMessage + RawRender) on every
15−// increment, exactly as the live TUI does on each ~33ms flush while
16−// the block sits collapsed. It guards against a regression of the
17−// O(n²) full-trace re-render that made long thinking lag the UI:
18−// with boundThinkingInput the per-flush cost is bounded, so the wall
19−// (no-blank-line) case stays close to the blank-line case instead of
20−// blowing up quadratically.
21−func benchThinkingStream(b *testing.B, body string, steps int) {
22− sty := styles.CharmtonePantera()
23− const width = 80
24− b.ReportAllocs()
25− b.ResetTimer()
26− for n := 0; n < b.N; n++ {
27− item := NewAssistantMessageItem(&sty, thinkingMsg("")).(*AssistantMessageItem)
28− for i := 1; i <= steps; i++ {
29− cut := len(body) * i / steps
30− item.SetMessage(thinkingMsg(body[:cut]))
31− _ = item.RawRender(width)
32− }
33− }
34−}
35−
36−func thinkingMsg(thinking string) *message.Message {
37− return &message.Message{
38− ID: "bench",
39− Role: message.Assistant,
40− Parts: []message.ContentPart{
41− message.ReasoningContent{Thinking: thinking, StartedAt: testStartedAt},
42− },
43− }
44−}
45−
46−func benchParagraphs(n int) string {
47− var sb strings.Builder
48− for i := 0; i < n; i++ {
49− fmt.Fprintf(&sb, "Reasoning paragraph %d with a few sentences of prose that a model might emit while working through a problem step by step.\n\n", i)
50− }
51− return sb.String()
52−}
53−
54−// benchWall is one giant paragraph with NO blank line — the case
55−// where no safe markdown boundary ever exists and, before
56−// boundThinkingInput, every flush re-rendered the whole growing
57−// trace.
58−func benchWall(n int) string {
59− var sb strings.Builder
60− for i := 0; i < n; i++ {
61− fmt.Fprintf(&sb, "sentence %d that keeps going with no paragraph break at all. ", i)
62− }
63− return sb.String()
64−}
65−
66−func BenchmarkThinkingStream_BlankLines(b *testing.B) {
67− benchThinkingStream(b, benchParagraphs(200), 100)
68−}
69−func BenchmarkThinkingStream_Wall(b *testing.B) { benchThinkingStream(b, benchWall(1500), 100) }
internal/ui/chat/assistant_thinking_bound_test.godeleted−105
@@ -1,105 +0,0 @@
1−package chat
2−
3−import (
4− "strings"
5− "testing"
6−
7− "github.com/stretchr/testify/require"
8−)
9−
10−// TestBoundThinkingInput_ShortNotWindowed: content within the budget
11−// is returned verbatim so short blocks and the non-streaming steady
12−// state are untouched.
13−func TestBoundThinkingInput_ShortNotWindowed(t *testing.T) {
14− t.Parallel()
15− in := "a short thought\n\nwith two paragraphs"
16− out, dropped := boundThinkingInput(in, thinkingCollapsed, 80)
17− require.Equal(t, in, out)
18− require.Zero(t, dropped)
19−}
20−
21−// TestBoundThinkingInput_FullExpandedNeverWindowed: full expansion is
22−// an explicit request to see everything, so it is never bounded even
23−// for a huge trace.
24−func TestBoundThinkingInput_FullExpandedNeverWindowed(t *testing.T) {
25− t.Parallel()
26− in := strings.Repeat("wall of text with no breaks ", 100000)
27− out, dropped := boundThinkingInput(in, thinkingFullExpanded, 80)
28− require.Equal(t, in, out)
29− require.Zero(t, dropped)
30−}
31−
32−// TestBoundThinkingInput_BoundariedNotWindowed: a large trace whose
33−// tail has a recent safe boundary is NOT windowed — the incremental
34−// cache renders only the small trailing segment, so windowing (which
35−// would reset that cache every flush) must be skipped.
36−func TestBoundThinkingInput_BoundariedNotWindowed(t *testing.T) {
37− t.Parallel()
38− var b strings.Builder
39− for i := 0; i < 5000; i++ {
40− b.WriteString("paragraph with a few words\n\n")
41− }
42− in := b.String()
43− require.Greater(t, len(in), thinkingWindowBytes(thinkingCollapsed, 80),
44− "fixture must exceed the budget so only the boundary gate can spare it")
45− out, dropped := boundThinkingInput(in, thinkingCollapsed, 80)
46− require.Equal(t, in, out, "boundaried content must not be windowed")
47− require.Zero(t, dropped)
48−}
49−
50−// TestBoundThinkingInput_WallWindowed: a long unbroken wall (no
51−// paragraph break the incremental cache can use) IS windowed to a
52−// bounded tail.
53−func TestBoundThinkingInput_WallWindowed(t *testing.T) {
54− t.Parallel()
55− budget := thinkingWindowBytes(thinkingCollapsed, 80)
56− in := strings.Repeat("word ", budget) // one giant line, no '\n'
57− out, _ := boundThinkingInput(in, thinkingCollapsed, 80)
58− require.Less(t, len(out), len(in), "a wall past budget must be shortened")
59− require.LessOrEqual(t, len(out), budget+8, "window must respect the budget (+fence slack)")
60− require.True(t, strings.HasSuffix(in, out), "window must be a tail of the source")
61−}
62−
63−// TestBoundThinkingInput_SnapsToLineBoundary: when a newline exists
64−// inside the trailing budget the window begins at the start of a
65−// line, never mid-line.
66−func TestBoundThinkingInput_SnapsToLineBoundary(t *testing.T) {
67− t.Parallel()
68− budget := thinkingWindowBytes(thinkingCollapsed, 80)
69− // A long single line (forces windowing) followed by clean lines
70− // within the budget window.
71− head := strings.Repeat("x", budget)
72− tail := "\nclean line one\nclean line two\n"
73− out, dropped := boundThinkingInput(head+tail, thinkingCollapsed, 80)
74− require.Greater(t, dropped, 0)
75− require.False(t, strings.HasPrefix(out, "x"),
76− "window must snap past the truncated head line")
77− require.True(t, strings.HasPrefix(out, "clean line"))
78−}
79−
80−// TestBoundThinkingInput_ReopensDanglingFence: when the dropped
81−// prefix leaves a code fence open, the window is prefixed with a
82−// fence opener so the tail renders as code (matching reality) instead
83−// of flipping to prose.
84−func TestBoundThinkingInput_ReopensDanglingFence(t *testing.T) {
85− t.Parallel()
86− budget := thinkingWindowBytes(thinkingCollapsed, 80)
87− // Open a fence, then emit a long body (no closing fence) that
88− // pushes the opener out of the window.
89− in := "```go\n" + strings.Repeat("code line here\n", budget/15+500)
90− out, dropped := boundThinkingInput(in, thinkingCollapsed, 80)
91− require.Greater(t, dropped, 0, "fixture must actually be windowed")
92− require.True(t, strings.HasPrefix(out, "```\n"),
93− "a dangling open fence must be re-opened at the window head")
94−}
95−
96−// TestFenceLineCount checks parity accounting used to detect a
97−// dangling open fence.
98−func TestFenceLineCount(t *testing.T) {
99− t.Parallel()
100− require.Equal(t, 0, fenceLineCount("no fences here\njust prose\n"))
101− require.Equal(t, 2, fenceLineCount("```go\ncode\n```\n"))
102− require.Equal(t, 1, fenceLineCount("```\nopen but never closed\n"))
103− // A final line with no trailing newline still counts.
104− require.Equal(t, 1, fenceLineCount("prose\n```"))
105−}
internal/ui/chat/bash.go+7−10
@@ -31,7 +31,7 @@ func NewBashToolMessageItem(
3131 result *message.ToolResult,
3232 canceled bool,
3333) ToolMessageItem {
34− return newBaseToolMessageItem(sty, toolCall, result, &BashToolRenderContext{}, canceled, "")
34+ return newBaseToolMessageItem(sty, toolCall, result, &BashToolRenderContext{}, canceled)
3535}
3636
3737// BashToolRenderContext renders bash tool messages.
@@ -62,17 +62,14 @@ func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
6262 }
6363
6464 // Regular bash command.
65− cmd := params.Command
66− if !opts.ExpandedContent {
67− cmd = strings.ReplaceAll(cmd, "\n", " ")
68− }
65+ cmd := strings.ReplaceAll(params.Command, "\n", " ")
6966 cmd = strings.ReplaceAll(cmd, "\t", " ")
7067 toolParams := []string{cmd}
7168 if params.RunInBackground {
7269 toolParams = append(toolParams, "background", "true")
7370 }
7471
75− header := toolHeader(sty, opts.Status, "Bash", cappedWidth, opts, toolParams...)
72+ header := toolHeader(sty, opts.Status, "Bash", cappedWidth, opts.Compact, toolParams...)
7673 if opts.Compact {
7774 return header
7875 }
@@ -116,7 +113,7 @@ func NewJobOutputToolMessageItem(
116113 result *message.ToolResult,
117114 canceled bool,
118115) ToolMessageItem {
119− return newBaseToolMessageItem(sty, toolCall, result, &JobOutputToolRenderContext{}, canceled, "")
116+ return newBaseToolMessageItem(sty, toolCall, result, &JobOutputToolRenderContext{}, canceled)
120117}
121118
122119// JobOutputToolRenderContext renders job_output tool messages.
@@ -131,7 +128,7 @@ func (j *JobOutputToolRenderContext) RenderTool(sty *styles.Styles, width int, o
131128
132129 var params tools.JobOutputParams
133130 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
134− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
131+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
135132 }
136133
137134 var description string
@@ -167,7 +164,7 @@ func NewJobKillToolMessageItem(
167164 result *message.ToolResult,
168165 canceled bool,
169166) ToolMessageItem {
170− return newBaseToolMessageItem(sty, toolCall, result, &JobKillToolRenderContext{}, canceled, "")
167+ return newBaseToolMessageItem(sty, toolCall, result, &JobKillToolRenderContext{}, canceled)
171168}
172169
173170// JobKillToolRenderContext renders job_kill tool messages.
@@ -182,7 +179,7 @@ func (j *JobKillToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
182179
183180 var params tools.JobKillParams
184181 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
185− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
182+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
186183 }
187184
188185 var description string
internal/ui/chat/diagnostics.go+2−2
@@ -27,7 +27,7 @@ func NewDiagnosticsToolMessageItem(
2727 result *message.ToolResult,
2828 canceled bool,
2929) ToolMessageItem {
30− return newBaseToolMessageItem(sty, toolCall, result, &DiagnosticsToolRenderContext{}, canceled, "")
30+ return newBaseToolMessageItem(sty, toolCall, result, &DiagnosticsToolRenderContext{}, canceled)
3131}
3232
3333// DiagnosticsToolRenderContext renders diagnostics tool messages.
@@ -49,7 +49,7 @@ func (d *DiagnosticsToolRenderContext) RenderTool(sty *styles.Styles, width int,
4949 mainParam = fsext.PrettyPath(params.FilePath)
5050 }
5151
52− header := toolHeader(sty, opts.Status, "Diagnostics", cappedWidth, opts, mainParam)
52+ header := toolHeader(sty, opts.Status, "Diagnostics", cappedWidth, opts.Compact, mainParam)
5353 if opts.Compact {
5454 return header
5555 }
internal/ui/chat/docker_mcp.go+6−6
@@ -28,7 +28,7 @@ func NewDockerMCPToolMessageItem(
2828 result *message.ToolResult,
2929 canceled bool,
3030) ToolMessageItem {
31− return newBaseToolMessageItem(sty, toolCall, result, &DockerMCPToolRenderContext{}, canceled, "")
31+ return newBaseToolMessageItem(sty, toolCall, result, &DockerMCPToolRenderContext{}, canceled)
3232}
3333
3434// DockerMCPToolRenderContext renders Docker MCP tool messages.
@@ -138,7 +138,7 @@ func (d *DockerMCPToolRenderContext) RenderTool(sty *styles.Styles, width int, o
138138
139139 // Handle text content.
140140 if opts.Result.Content != "" {
141− body := renderToolResultTextContent(sty, opts.Result.Content, toolResultContentWidths{Body: bodyWidth, Diff: cappedWidth}, opts.ExpandedContent, opts.DiffTool)
141+ body := renderToolResultTextContent(sty, opts.Result.Content, toolResultContentWidths{Body: bodyWidth, Diff: cappedWidth}, opts.ExpandedContent)
142142 parts = append(parts, body)
143143 }
144144
@@ -220,12 +220,12 @@ func (d *DockerMCPToolRenderContext) makeHeader(sty *styles.Styles, tool string,
220220 icon = sty.Tool.IconPending.Render()
221221 }
222222 prefix := fmt.Sprintf("%s %s ", icon, d.formatToolName(sty, tool))
223− return prefix + toolParamList(sty, params, width-lipgloss.Width(prefix), opts)
223+ return prefix + toolParamList(sty, params, width-lipgloss.Width(prefix))
224224}
225225
226226func (d *DockerMCPToolRenderContext) formatToolName(sty *styles.Styles, tool string) string {
227227 mainTool := "Docker MCP"
228− var action string
228+ action := tool
229229 actionStyle := sty.Tool.MCPToolName
230230 switch tool {
231231 case "mcp-exec":
@@ -254,7 +254,7 @@ func (d *DockerMCPToolRenderContext) formatToolName(sty *styles.Styles, tool str
254254}
255255
256256func (d *DockerMCPToolRenderContext) makeCompactHeader(sty *styles.Styles, tool string, width int, params ...string) string {
257− var action string
257+ action := tool
258258 switch tool {
259259 case "mcp-exec":
260260 action = "exec"
@@ -274,7 +274,7 @@ func (d *DockerMCPToolRenderContext) makeCompactHeader(sty *styles.Styles, tool
274274 }
275275
276276 name := fmt.Sprintf("Docker MCP: %s", action)
277− return toolHeader(sty, ToolStatusSuccess, name, width, &ToolRenderOpts{Compact: true}, params...)
277+ return toolHeader(sty, ToolStatusSuccess, name, width, true, params...)
278278}
279279
280280// IsDockerMCPTool returns true if the tool name is a Docker MCP tool.
internal/ui/chat/fetch.go+10−10
@@ -26,7 +26,7 @@ func NewFetchToolMessageItem(
2626 result *message.ToolResult,
2727 canceled bool,
2828) ToolMessageItem {
29− return newBaseToolMessageItem(sty, toolCall, result, &FetchToolRenderContext{}, canceled, "")
29+ return newBaseToolMessageItem(sty, toolCall, result, &FetchToolRenderContext{}, canceled)
3030}
3131
3232// FetchToolRenderContext renders fetch tool messages.
@@ -41,7 +41,7 @@ func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
4141
4242 var params tools.FetchParams
4343 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
44− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
44+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
4545 }
4646
4747 toolParams := []string{params.URL}
@@ -52,7 +52,7 @@ func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
5252 toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
5353 }
5454
55− header := toolHeader(sty, opts.Status, "Fetch", cappedWidth, opts, toolParams...)
55+ header := toolHeader(sty, opts.Status, "Fetch", cappedWidth, opts.Compact, toolParams...)
5656 if opts.Compact {
5757 return header
5858 }
@@ -101,7 +101,7 @@ func NewWebFetchToolMessageItem(
101101 result *message.ToolResult,
102102 canceled bool,
103103) ToolMessageItem {
104− return newBaseToolMessageItem(sty, toolCall, result, &WebFetchToolRenderContext{}, canceled, "")
104+ return newBaseToolMessageItem(sty, toolCall, result, &WebFetchToolRenderContext{}, canceled)
105105}
106106
107107// WebFetchToolRenderContext renders web_fetch tool messages.
@@ -111,16 +111,16 @@ type WebFetchToolRenderContext struct{}
111111func (w *WebFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
112112 cappedWidth := cappedMessageWidth(width)
113113 if opts.IsPending() {
114− return pendingTool(sty, "Web Fetch", opts.Anim, opts.Compact)
114+ return pendingTool(sty, "Fetch", opts.Anim, opts.Compact)
115115 }
116116
117117 var params tools.WebFetchParams
118118 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
119− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
119+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
120120 }
121121
122122 toolParams := []string{params.URL}
123− header := toolHeader(sty, opts.Status, "Web Fetch", cappedWidth, opts, toolParams...)
123+ header := toolHeader(sty, opts.Status, "Fetch", cappedWidth, opts.Compact, toolParams...)
124124 if opts.Compact {
125125 return header
126126 }
@@ -155,7 +155,7 @@ func NewWebSearchToolMessageItem(
155155 result *message.ToolResult,
156156 canceled bool,
157157) ToolMessageItem {
158− return newBaseToolMessageItem(sty, toolCall, result, &WebSearchToolRenderContext{}, canceled, "")
158+ return newBaseToolMessageItem(sty, toolCall, result, &WebSearchToolRenderContext{}, canceled)
159159}
160160
161161// WebSearchToolRenderContext renders web_search tool messages.
@@ -170,11 +170,11 @@ func (w *WebSearchToolRenderContext) RenderTool(sty *styles.Styles, width int, o
170170
171171 var params tools.WebSearchParams
172172 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
173− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
173+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
174174 }
175175
176176 toolParams := []string{params.Query}
177− header := toolHeader(sty, opts.Status, "Search", cappedWidth, opts, toolParams...)
177+ header := toolHeader(sty, opts.Status, "Search", cappedWidth, opts.Compact, toolParams...)
178178 if opts.Compact {
179179 return header
180180 }
internal/ui/chat/file.go+22−22
@@ -29,7 +29,7 @@ func NewViewToolMessageItem(
2929 result *message.ToolResult,
3030 canceled bool,
3131) ToolMessageItem {
32− return newBaseToolMessageItem(sty, toolCall, result, &ViewToolRenderContext{}, canceled, "")
32+ return newBaseToolMessageItem(sty, toolCall, result, &ViewToolRenderContext{}, canceled)
3333}
3434
3535// ViewToolRenderContext renders view tool messages.
@@ -44,7 +44,7 @@ func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
4444
4545 var params tools.ViewParams
4646 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
47− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
47+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
4848 }
4949
5050 file := fsext.PrettyPath(params.FilePath)
@@ -56,7 +56,7 @@ func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
5656 toolParams = append(toolParams, "offset", fmt.Sprintf("%d", params.Offset))
5757 }
5858
59− header := toolHeader(sty, opts.Status, "View", cappedWidth, opts, toolParams...)
59+ header := toolHeader(sty, opts.Status, "View", cappedWidth, opts.Compact, toolParams...)
6060 if opts.Compact {
6161 return header
6262 }
@@ -115,7 +115,7 @@ func NewWriteToolMessageItem(
115115 result *message.ToolResult,
116116 canceled bool,
117117) ToolMessageItem {
118− return newBaseToolMessageItem(sty, toolCall, result, &WriteToolRenderContext{}, canceled, "")
118+ return newBaseToolMessageItem(sty, toolCall, result, &WriteToolRenderContext{}, canceled)
119119}
120120
121121// WriteToolRenderContext renders write tool messages.
@@ -130,11 +130,11 @@ func (w *WriteToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
130130
131131 var params tools.WriteParams
132132 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
133− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
133+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
134134 }
135135
136136 file := fsext.PrettyPath(params.FilePath)
137− header := toolHeader(sty, opts.Status, "Write", cappedWidth, opts, file)
137+ header := toolHeader(sty, opts.Status, "Write", cappedWidth, opts.Compact, file)
138138 if opts.Compact {
139139 return header
140140 }
@@ -150,11 +150,11 @@ func (w *WriteToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
150150 if opts.Result.IsError {
151151 var meta tools.WriteResponseMetadata
152152 if err := json.Unmarshal([]byte(opts.Result.Metadata), &meta); err == nil && meta.Diff != "" {
153− errLine := toolErrorContent(sty, opts.Result, cappedWidth, opts.ExpandedContent)
154− diff := toolOutputDiffContentFromUnified(sty, meta.Diff, cappedWidth, opts.ExpandedContent, opts.DiffTool)
153+ errLine := toolErrorContent(sty, opts.Result, cappedWidth)
154+ diff := toolOutputDiffContentFromUnified(sty, meta.Diff, cappedWidth, opts.ExpandedContent)
155155 return strings.Join([]string{header, "", errLine, "", diff}, "\n")
156156 }
157− return joinToolParts(header, toolErrorContent(sty, opts.Result, cappedWidth, opts.ExpandedContent))
157+ return joinToolParts(header, toolErrorContent(sty, opts.Result, cappedWidth))
158158 }
159159
160160 // Render code content with syntax highlighting.
@@ -184,7 +184,7 @@ func NewEditToolMessageItem(
184184 result *message.ToolResult,
185185 canceled bool,
186186) ToolMessageItem {
187− return newBaseToolMessageItem(sty, toolCall, result, &EditToolRenderContext{}, canceled, "")
187+ return newBaseToolMessageItem(sty, toolCall, result, &EditToolRenderContext{}, canceled)
188188}
189189
190190// EditToolRenderContext renders edit tool messages.
@@ -199,11 +199,11 @@ func (e *EditToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
199199
200200 var params tools.EditParams
201201 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
202− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width, false)
202+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
203203 }
204204
205205 file := fsext.PrettyPath(params.FilePath)
206− header := toolHeader(sty, opts.Status, "Edit", width, opts, file)
206+ header := toolHeader(sty, opts.Status, "Edit", width, opts.Compact, file)
207207 if opts.Compact {
208208 return header
209209 }
@@ -223,11 +223,11 @@ func (e *EditToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
223223 return joinToolParts(header, body)
224224 }
225225
226− diff := toolOutputDiffContent(sty, file, meta.OldContent, meta.NewContent, width, opts.ExpandedContent, opts.DiffTool)
226+ diff := toolOutputDiffContent(sty, file, meta.OldContent, meta.NewContent, width, opts.ExpandedContent)
227227
228228 // On error (e.g. denied permission), show error above the diff.
229229 if opts.Result.IsError {
230− errLine := toolErrorContent(sty, opts.Result, width, opts.ExpandedContent)
230+ errLine := toolErrorContent(sty, opts.Result, width)
231231 return strings.Join([]string{header, "", errLine, "", diff}, "\n")
232232 }
233233
@@ -252,7 +252,7 @@ func NewMultiEditToolMessageItem(
252252 result *message.ToolResult,
253253 canceled bool,
254254) ToolMessageItem {
255− return newBaseToolMessageItem(sty, toolCall, result, &MultiEditToolRenderContext{}, canceled, "")
255+ return newBaseToolMessageItem(sty, toolCall, result, &MultiEditToolRenderContext{}, canceled)
256256}
257257
258258// MultiEditToolRenderContext renders multi-edit tool messages.
@@ -267,7 +267,7 @@ func (m *MultiEditToolRenderContext) RenderTool(sty *styles.Styles, width int, o
267267
268268 var params tools.MultiEditParams
269269 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
270− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width, false)
270+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
271271 }
272272
273273 file := fsext.PrettyPath(params.FilePath)
@@ -276,7 +276,7 @@ func (m *MultiEditToolRenderContext) RenderTool(sty *styles.Styles, width int, o
276276 toolParams = append(toolParams, "edits", fmt.Sprintf("%d", len(params.Edits)))
277277 }
278278
279− header := toolHeader(sty, opts.Status, "Multi-Edit", width, opts, toolParams...)
279+ header := toolHeader(sty, opts.Status, "Multi-Edit", width, opts.Compact, toolParams...)
280280 if opts.Compact {
281281 return header
282282 }
@@ -297,11 +297,11 @@ func (m *MultiEditToolRenderContext) RenderTool(sty *styles.Styles, width int, o
297297 }
298298
299299 // Render diff with optional failed edits note.
300− diff := toolOutputMultiEditDiffContent(sty, file, meta, len(params.Edits), width, opts.ExpandedContent, opts.DiffTool)
300+ diff := toolOutputMultiEditDiffContent(sty, file, meta, len(params.Edits), width, opts.ExpandedContent)
301301
302302 // On error (e.g. denied permission), show error above the diff.
303303 if opts.Result.IsError {
304− errLine := toolErrorContent(sty, opts.Result, width, opts.ExpandedContent)
304+ errLine := toolErrorContent(sty, opts.Result, width)
305305 return strings.Join([]string{header, "", errLine, "", diff}, "\n")
306306 }
307307
@@ -326,7 +326,7 @@ func NewDownloadToolMessageItem(
326326 result *message.ToolResult,
327327 canceled bool,
328328) ToolMessageItem {
329− return newBaseToolMessageItem(sty, toolCall, result, &DownloadToolRenderContext{}, canceled, "")
329+ return newBaseToolMessageItem(sty, toolCall, result, &DownloadToolRenderContext{}, canceled)
330330}
331331
332332// DownloadToolRenderContext renders download tool messages.
@@ -341,7 +341,7 @@ func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, op
341341
342342 var params tools.DownloadParams
343343 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
344− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
344+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
345345 }
346346
347347 toolParams := []string{params.URL}
@@ -352,7 +352,7 @@ func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, op
352352 toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
353353 }
354354
355− header := toolHeader(sty, opts.Status, "Download", cappedWidth, opts, toolParams...)
355+ header := toolHeader(sty, opts.Status, "Download", cappedWidth, opts.Compact, toolParams...)
356356 if opts.Compact {
357357 return header
358358 }
internal/ui/chat/generic.go+4−4
@@ -22,7 +22,7 @@ func NewGenericToolMessageItem(
2222 result *message.ToolResult,
2323 canceled bool,
2424) ToolMessageItem {
25− return newBaseToolMessageItem(sty, toolCall, result, &GenericToolRenderContext{}, canceled, "")
25+ return newBaseToolMessageItem(sty, toolCall, result, &GenericToolRenderContext{}, canceled)
2626}
2727
2828// GenericToolRenderContext renders unknown/generic tool messages.
@@ -39,7 +39,7 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
3939
4040 var params map[string]any
4141 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
42− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
42+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
4343 }
4444
4545 var toolParams []string
@@ -48,7 +48,7 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
4848 toolParams = append(toolParams, string(parsed))
4949 }
5050
51− header := toolHeader(sty, opts.Status, name, cappedWidth, opts, toolParams...)
51+ header := toolHeader(sty, opts.Status, name, cappedWidth, opts.Compact, toolParams...)
5252 if opts.Compact {
5353 return header
5454 }
@@ -68,6 +68,6 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
6868 return joinToolParts(header, body)
6969 }
7070
71− body := renderToolResultTextContent(sty, opts.Result.Content, toolResultContentWidths{Body: bodyWidth, Diff: cappedWidth}, opts.ExpandedContent, opts.DiffTool)
71+ body := renderToolResultTextContent(sty, opts.Result.Content, toolResultContentWidths{Body: bodyWidth, Diff: cappedWidth}, opts.ExpandedContent)
7272 return joinToolParts(header, body)
7373}
internal/ui/chat/incremental_glamour_test.go+16−144
@@ -1,7 +1,6 @@
11package chat
22
33import (
4− "fmt"
54 "strings"
65 "testing"
76
@@ -224,46 +223,12 @@ func TestFindSafeMarkdownBoundary_TableDriven(t *testing.T) {
224223 {
225224 name: "closed list then paragraph",
226225 content: "- one\n- two\n\nPara.",
227− // blank line after the list, then "Para." at
228− // indent 0 (< listBaseIndent+2). The closure-
229− // aware tracker sees the list close at "Para."
230− // and accepts the boundary at its start — the
231− // prefix contains a complete list.
232− want: len("- one\n- two\n\n"),
233− },
234− {
235− name: "closed ordered list then paragraph",
236− content: "1. first\n2. second\n\nAfter list.",
237− // Same closure rule for ordered lists. Boundary at
238− // the start of "After list." — the list closed on
239− // the dedented non-marker line.
240− want: len("1. first\n2. second\n\n"),
241− },
242− {
243− name: "list then multiple paragraphs",
244− content: "- item\n\nPara one.\n\nPara two.",
245− // The list closes at "Para one." and a boundary is
246− // accepted there. A later boundary at "Para two."
247− // should also be found (no list open anymore).
248− // Latest boundary wins.
249− want: len("- item\n\nPara one.\n\n"),
250− },
251− {
252− name: "loose list continuation keeps list open",
253− content: "- item one\n\n continuation\n\nPara after.",
254− // " continuation" is indented 2 (>=
255− // listBaseIndent+2) so the list stays open
256− // through it. The list only closes at "Para
257− // after." Boundary at the start of "Para after.".
258− want: len("- item one\n\n continuation\n\n"),
259− },
260− {
261− name: "nested list then closure",
262− content: "- outer\n - inner\n\nBack to top level.",
263− // Inner list opens at indent 2. "Back to top
264− // level." at indent 0 closes everything.
265− // Boundary at the start of "Back to top level.".
266− want: len("- outer\n - inner\n\n"),
226+ // blank line after the list. Last non-blank line
227+ // of prefix is "- two" — a list item — so the
228+ // candidate is REJECTED. (Conservative: we don't
229+ // know the list is "closed" without looking at
230+ // what follows.)
231+ want: -1,
267232 },
268233 {
269234 name: "table at end",
@@ -673,69 +638,9 @@ func TestStreamingMarkdown_NoSafeBoundaryDoesNotCrash(t *testing.T) {
673638 }
674639}
675640
676−// TestStreamingMarkdown_LongThinkingWithLists is the regression
677−// test for the lag bug: a long thinking trace that contains a list
678−// early on, followed by many paragraphs. Before the closure-aware
679−// list tracker, the list marker forced findSafeMarkdownBoundary to
680−// return -1 for the ENTIRE trace, so every streaming delta did a
681−// full glamour re-render of the growing text — O(n²) overall and
682−// visible lag on long reasoning.
683−//
684−// With the closure-aware tracker the list closes at the first
685−// dedented paragraph, the stable-prefix cache advances past it,
686−// and subsequent deltas only re-render the trailing partial. This
687−// test asserts the cache actually advances (stablePrefix grows
688−// beyond the list) and the final output is visually equivalent to
689−// a fresh full render.
690−func TestStreamingMarkdown_LongThinkingWithLists(t *testing.T) {
691− t.Parallel()
692−
693− // Build a thinking trace: intro, a short list, then many
694− // paragraphs that simulate a long reasoning section.
695− var doc strings.Builder
696− doc.WriteString("Let me analyze this step by step.\n\n")
697− doc.WriteString("1. First consideration.\n\n")
698− doc.WriteString("2. Second consideration.\n\n")
699− doc.WriteString("3. Third consideration.\n\n")
700− for i := range 200 {
701− fmt.Fprintf(&doc, "Paragraph %d of the reasoning trace. It has enough text to be a real paragraph.\n\n", i)
702− }
703−
704− full := doc.String()
705− listEnd := strings.Index(full, "3. Third consideration.\n\n") + len("3. Third consideration.\n\n")
706− require.Greater(t, listEnd, 0, "test setup: list end offset")
707−
708− const width = 80
709− r := newTestRenderer(t, width)
710− var sm streamingMarkdown
711−
712− // Stream the document in ~50 progressive prefixes. After the
713− // list closes, the cache should advance past listEnd.
714− steps := 50
715− prefixes := progressivePrefixes(full, steps)
716− var lastOut string
717− advanced := false
718− for i, p := range prefixes {
719− if p == "" {
720− continue
721− }
722− lastOut = sm.Render(p, width, r)
723− // Once the prefix extends well past the list, the
724− // stable prefix should have advanced past listEnd.
725− if len(p) > listEnd+200 && len(sm.stablePrefix) > listEnd {
726− advanced = true
727− }
728− _ = i
729− }
730− require.True(t, advanced,
731− "stable prefix must advance past the closed list — if it didn't, "+
732− "every delta is doing a full re-render (the lag bug)")
733−
734− // Final output must be visually equivalent to a fresh render.
735− fresh := freshRender(t, full, width)
736− require.Equal(t, normalizeRender(fresh), normalizeRender(lastOut),
737− "final streaming output must match a fresh full render visually")
738−}
641+// -----------------------------------------------------------------------
642+// Integration assertions on the wired-in path.
643+// -----------------------------------------------------------------------
739644
740645// -----------------------------------------------------------------------
741646// T5 / T6 / T7: anywhere-in-prefix hazards (B1 / B2 / B3 from the
@@ -821,11 +726,8 @@ func runProgressiveBoundaryRespectTest(t *testing.T, doc string, hazardLineOffse
821726// trailing paragraph creates a candidate boundary between the list
822727// item and its continuation; the trailing non-blank line of that
823728// candidate prefix is the continuation paragraph (not a list
824−// marker), so the line-only check would accept it. The closure-
825−// aware list tracker keeps the list open through the continuation
826−// (indent >= listBaseIndent+2) and only closes it at the dedented
827−// "Trailing paragraph" line, so boundaries INSIDE the list are
828−// rejected while the boundary at the closing line is accepted.
729+// marker), so the line-only check would accept it. The
730+// anywhere-in-prefix list-marker check rejects it.
829731func TestStreamingMarkdown_LooseListContinuation(t *testing.T) {
830732 t.Parallel()
831733
@@ -842,42 +744,12 @@ func TestStreamingMarkdown_LooseListContinuation(t *testing.T) {
842744 }, "\n")
843745
844746 // The first list marker line begins after "Intro paragraph.\n\n".
845− // The closure boundary is the start of "Trailing paragraph after
846− // the list." — the first safe boundary after the list closes.
847− listStart := strings.Index(doc, "- item one")
848− require.Greater(t, listStart, 0, "test setup")
849− closureStart := strings.Index(doc, "Trailing paragraph")
850− require.Greater(t, closureStart, listStart, "test setup")
851−
852− const width = 80
853− const steps = 25
854− renderer := newTestRenderer(t, width)
855− var sm streamingMarkdown
856−
857− prefixes := progressivePrefixes(doc, steps)
858− var lastOut string
859− for i, p := range prefixes {
860− if p == "" {
861− continue
862− }
863− lastOut = sm.Render(p, width, renderer)
864− require.NotEmptyf(t, lastOut, "step %d: empty render", i)
865− // The stable prefix must NEVER be inside the list —
866− // either it's at/before the intro boundary (before the
867− // list opens) or at/after the closure boundary (after
868− // the list closes). A value strictly between listStart
869− // and closureStart would mean a boundary inside the
870− // list was accepted, which would split the loose list.
871− if len(sm.stablePrefix) > listStart && len(sm.stablePrefix) < closureStart {
872− t.Fatalf("step %d: cached stable prefix landed inside the loose list "+
873− "(len=%d, list starts at %d, closure at %d)\nsm.stablePrefix=%q",
874− i, len(sm.stablePrefix), listStart, closureStart, sm.stablePrefix)
875− }
876− }
747+ // The cached stable prefix may include that boundary (BEFORE
748+ // the list opens) but must never advance into the list.
749+ hazardOffset := strings.Index(doc, "- item one")
750+ require.Greater(t, hazardOffset, 0, "test setup")
877751
878− fresh := freshRender(t, doc, width)
879− require.Equal(t, nonBlankLines(fresh), nonBlankLines(lastOut),
880− "final streaming output must contain the same non-blank lines as a fresh full render")
752+ runProgressiveBoundaryRespectTest(t, doc, hazardOffset)
881753}
882754
883755// TestStreamingMarkdown_HTMLBlock locks in the B2 fix. A raw HTML
internal/ui/chat/lsp_restart.go+2−2
@@ -22,7 +22,7 @@ func NewLSPRestartToolMessageItem(
2222 result *message.ToolResult,
2323 canceled bool,
2424) ToolMessageItem {
25− return newBaseToolMessageItem(sty, toolCall, result, &LSPRestartToolRenderContext{}, canceled, "")
25+ return newBaseToolMessageItem(sty, toolCall, result, &LSPRestartToolRenderContext{}, canceled)
2626}
2727
2828// LSPRestartToolRenderContext renders lsprestart tool messages.
@@ -43,7 +43,7 @@ func (r *LSPRestartToolRenderContext) RenderTool(sty *styles.Styles, width int,
4343 toolParams = append(toolParams, params.Name)
4444 }
4545
46− header := toolHeader(sty, opts.Status, "Restart LSP", cappedWidth, opts, toolParams...)
46+ header := toolHeader(sty, opts.Status, "Restart LSP", cappedWidth, opts.Compact, toolParams...)
4747 if opts.Compact {
4848 return header
4949 }
internal/ui/chat/mcp.go+5−5
@@ -23,7 +23,7 @@ func NewMCPToolMessageItem(
2323 result *message.ToolResult,
2424 canceled bool,
2525) ToolMessageItem {
26− return newBaseToolMessageItem(sty, toolCall, result, &MCPToolRenderContext{}, canceled, "")
26+ return newBaseToolMessageItem(sty, toolCall, result, &MCPToolRenderContext{}, canceled)
2727}
2828
2929// MCPToolRenderContext renders bash tool messages.
@@ -34,7 +34,7 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
3434 cappedWidth := cappedMessageWidth(width)
3535 toolNameParts := strings.SplitN(opts.ToolCall.Name, "_", 3)
3636 if len(toolNameParts) != 3 {
37− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid tool name"}, cappedWidth, false)
37+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid tool name"}, cappedWidth)
3838 }
3939 mcpName := humanizedToolName(toolNameParts[1])
4040 toolName := humanizedToolName(toolNameParts[2])
@@ -50,7 +50,7 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
5050
5151 var params map[string]any
5252 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
53− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
53+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
5454 }
5555
5656 var toolParams []string
@@ -59,7 +59,7 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
5959 toolParams = append(toolParams, string(parsed))
6060 }
6161
62− header := toolHeader(sty, opts.Status, name, cappedWidth, opts, toolParams...)
62+ header := toolHeader(sty, opts.Status, name, cappedWidth, opts.Compact, toolParams...)
6363 if opts.Compact {
6464 return header
6565 }
@@ -73,6 +73,6 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
7373 }
7474
7575 bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
76− body := renderToolResultTextContent(sty, opts.Result.Content, toolResultContentWidths{Body: bodyWidth, Diff: cappedWidth}, opts.ExpandedContent, opts.DiffTool)
76+ body := renderToolResultTextContent(sty, opts.Result.Content, toolResultContentWidths{Body: bodyWidth, Diff: cappedWidth}, opts.ExpandedContent)
7777 return joinToolParts(header, body)
7878}
internal/ui/chat/messages.go+1−2
@@ -364,7 +364,7 @@ func cappedMessageWidth(availableWidth int) int {
364364//
365365// For assistant messages with tool calls, pass a toolResults map to link results.
366366// Use BuildToolResultMap to create this map from all messages in a session.
367−func ExtractMessageItems(sty *styles.Styles, msg *message.Message, toolResults map[string]message.ToolResult, diffTool string) []MessageItem {
367+func ExtractMessageItems(sty *styles.Styles, msg *message.Message, toolResults map[string]message.ToolResult) []MessageItem {
368368 switch msg.Role {
369369 case message.User:
370370 // Reconstruct shell command items from ShellCommand parts.
@@ -401,7 +401,6 @@ func ExtractMessageItems(sty *styles.Styles, msg *message.Message, toolResults m
401401 tc,
402402 result,
403403 msg.FinishReason() == message.FinishReasonCanceled,
404− diffTool,
405404 ))
406405 }
407406 return items
internal/ui/chat/references.go+2−2
@@ -23,7 +23,7 @@ func NewReferencesToolMessageItem(
2323 result *message.ToolResult,
2424 canceled bool,
2525) ToolMessageItem {
26− return newBaseToolMessageItem(sty, toolCall, result, &ReferencesToolRenderContext{}, canceled, "")
26+ return newBaseToolMessageItem(sty, toolCall, result, &ReferencesToolRenderContext{}, canceled)
2727}
2828
2929// ReferencesToolRenderContext renders references tool messages.
@@ -44,7 +44,7 @@ func (r *ReferencesToolRenderContext) RenderTool(sty *styles.Styles, width int,
4444 toolParams = append(toolParams, "path", fsext.PrettyPath(params.Path))
4545 }
4646
47− header := toolHeader(sty, opts.Status, "Find References", cappedWidth, opts, toolParams...)
47+ header := toolHeader(sty, opts.Status, "Find References", cappedWidth, opts.Compact, toolParams...)
4848 if opts.Compact {
4949 return header
5050 }
internal/ui/chat/search.go+12−12
@@ -27,7 +27,7 @@ func NewGlobToolMessageItem(
2727 result *message.ToolResult,
2828 canceled bool,
2929) ToolMessageItem {
30− return newBaseToolMessageItem(sty, toolCall, result, &GlobToolRenderContext{}, canceled, "")
30+ return newBaseToolMessageItem(sty, toolCall, result, &GlobToolRenderContext{}, canceled)
3131}
3232
3333// GlobToolRenderContext renders glob tool messages.
@@ -42,7 +42,7 @@ func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
4242
4343 var params tools.GlobParams
4444 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
45− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
45+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
4646 }
4747
4848 toolParams := []string{params.Pattern}
@@ -50,7 +50,7 @@ func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
5050 toolParams = append(toolParams, "path", params.Path)
5151 }
5252
53− header := toolHeader(sty, opts.Status, "Glob", cappedWidth, opts, toolParams...)
53+ header := toolHeader(sty, opts.Status, "Glob", cappedWidth, opts.Compact, toolParams...)
5454 if opts.Compact {
5555 return header
5656 }
@@ -86,7 +86,7 @@ func NewGrepToolMessageItem(
8686 result *message.ToolResult,
8787 canceled bool,
8888) ToolMessageItem {
89− return newBaseToolMessageItem(sty, toolCall, result, &GrepToolRenderContext{}, canceled, "")
89+ return newBaseToolMessageItem(sty, toolCall, result, &GrepToolRenderContext{}, canceled)
9090}
9191
9292// GrepToolRenderContext renders grep tool messages.
@@ -101,7 +101,7 @@ func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
101101
102102 var params tools.GrepParams
103103 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
104− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
104+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
105105 }
106106
107107 toolParams := []string{params.Pattern}
@@ -115,7 +115,7 @@ func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
115115 toolParams = append(toolParams, "literal", "true")
116116 }
117117
118− header := toolHeader(sty, opts.Status, "Grep", cappedWidth, opts, toolParams...)
118+ header := toolHeader(sty, opts.Status, "Grep", cappedWidth, opts.Compact, toolParams...)
119119 if opts.Compact {
120120 return header
121121 }
@@ -151,7 +151,7 @@ func NewLSToolMessageItem(
151151 result *message.ToolResult,
152152 canceled bool,
153153) ToolMessageItem {
154− return newBaseToolMessageItem(sty, toolCall, result, &LSToolRenderContext{}, canceled, "")
154+ return newBaseToolMessageItem(sty, toolCall, result, &LSToolRenderContext{}, canceled)
155155}
156156
157157// LSToolRenderContext renders ls tool messages.
@@ -166,7 +166,7 @@ func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *To
166166
167167 var params tools.LSParams
168168 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
169− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
169+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
170170 }
171171
172172 path := params.Path
@@ -175,7 +175,7 @@ func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *To
175175 }
176176 path = fsext.PrettyPath(path)
177177
178− header := toolHeader(sty, opts.Status, "List", cappedWidth, opts, path)
178+ header := toolHeader(sty, opts.Status, "List", cappedWidth, opts.Compact, path)
179179 if opts.Compact {
180180 return header
181181 }
@@ -211,7 +211,7 @@ func NewSourcegraphToolMessageItem(
211211 result *message.ToolResult,
212212 canceled bool,
213213) ToolMessageItem {
214− return newBaseToolMessageItem(sty, toolCall, result, &SourcegraphToolRenderContext{}, canceled, "")
214+ return newBaseToolMessageItem(sty, toolCall, result, &SourcegraphToolRenderContext{}, canceled)
215215}
216216
217217// SourcegraphToolRenderContext renders sourcegraph tool messages.
@@ -226,7 +226,7 @@ func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int,
226226
227227 var params tools.SourcegraphParams
228228 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
229− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth, false)
229+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
230230 }
231231
232232 toolParams := []string{params.Query}
@@ -237,7 +237,7 @@ func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int,
237237 toolParams = append(toolParams, "context", formatNonZero(params.ContextWindow))
238238 }
239239
240− header := toolHeader(sty, opts.Status, "Sourcegraph", cappedWidth, opts, toolParams...)
240+ header := toolHeader(sty, opts.Status, "Sourcegraph", cappedWidth, opts.Compact, toolParams...)
241241 if opts.Compact {
242242 return header
243243 }
internal/ui/chat/shell.go+32−85
@@ -42,21 +42,6 @@ type ShellItem struct {
4242 sty *styles.Styles
4343 pending bool
4444 anim *anim.Anim
45−
46− // xOffset-independent render cache. The expensive part of RawRender
47− // (ANSI-reset trim, RemapANSI16, split, per-line width) does not
48− // depend on the horizontal scroll offset, yet hscroll/highlight
49− // Bump() forces a full re-render on every keystroke. Cache the
50− // prepared display lines keyed by the inputs that actually affect
51− // them, and reapply only the xOffset windowing per render.
52− shellCacheValid bool
53− shellCacheOutput string
54− shellCacheExpanded bool
55− shellCacheWidth int
56− shellCachePending bool
57− shellCacheLines []string
58− shellCacheTruncated int
59− shellCacheMaxWidth int
6045}
6146
6247var (
@@ -196,14 +181,6 @@ func (s *ShellItem) ScrollHorizontal(delta int) {
196181 s.Bump()
197182}
198183
199−// clearCache drops the base render cache and the xOffset-independent
200−// output cache. The latter embeds the current theme's remapped ANSI
201−// colors (via RemapANSI16), so a style change must re-run the remap.
202−func (s *ShellItem) clearCache() {
203− s.cachedMessageItem.clearCache()
204− s.shellCacheValid = false
205−}
206−
207184// ToggleExpanded toggles the expanded state and invalidates the cache.
208185func (s *ShellItem) ToggleExpanded() bool {
209186 s.expandedContent = !s.expandedContent
@@ -211,19 +188,33 @@ func (s *ShellItem) ToggleExpanded() bool {
211188 return s.expandedContent
212189}
213190
214−// prepareOutput computes the xOffset-independent render intermediate:
215−// the trimmed, ANSI-remapped, split-and-truncated display lines plus the
216−// max-line-width clamp value. The result is cached and only recomputed
217−// when the output, expansion, capped width, or pending state change, so a
218−// horizontal-scroll keystroke (which Bumps the list cache) no longer
219−// re-runs the ANSI trim, RemapANSI16, split, and per-line width scan.
220−func (s *ShellItem) prepareOutput(cappedWidth int) (displayLines []string, truncatedCount, maxLineWidth int) {
221− if s.shellCacheValid &&
222− s.shellCacheOutput == s.output &&
223− s.shellCacheExpanded == s.expandedContent &&
224− s.shellCacheWidth == cappedWidth &&
225− s.shellCachePending == s.pending {
226− return s.shellCacheLines, s.shellCacheTruncated, s.shellCacheMaxWidth
191+func (s *ShellItem) RawRender(width int) string {
192+ cappedWidth := cappedMessageWidth(width)
193+
194+ cmd := strings.ReplaceAll(s.command, "\n", " ")
195+ cmd = strings.ReplaceAll(cmd, "\t", " ")
196+
197+ var prompt string
198+ if s.focused {
199+ prompt = s.sty.Messages.ShellPrompt.Render("$")
200+ } else {
201+ prompt = s.sty.Messages.ShellPromptBlurred.Render("$")
202+ }
203+
204+ highlighted := s.sty.Messages.ShellCommand.Render(cmd)
205+ header := prompt + " " + highlighted
206+
207+ if s.pending {
208+ if s.output == "" {
209+ // Nothing streamed yet: show the spinner under the header.
210+ return header + "\n" + s.anim.Render()
211+ }
212+ } else if s.exitCode != 0 {
213+ header += " " + s.sty.Messages.ShellExitCode.Render(fmt.Sprintf("(exit %d)", s.exitCode))
214+ }
215+
216+ if s.output == "" {
217+ return header
227218 }
228219
229220 // Remap raw ANSI 16-color codes onto legible Charmtone colors so
@@ -252,8 +243,8 @@ func (s *ShellItem) prepareOutput(cappedWidth int) (displayLines []string, trunc
252243 maxLines = len(lines)
253244 }
254245
255− displayLines = lines
256− truncatedCount = 0
246+ displayLines := lines
247+ truncatedCount := 0
257248 if len(lines) > maxLines {
258249 if s.pending {
259250 // Show the most recent lines while still running.
@@ -272,51 +263,7 @@ func (s *ShellItem) prepareOutput(cappedWidth int) (displayLines []string, trunc
272263 maxW = w
273264 }
274265 }
275− maxLineWidth = max(0, maxW-cappedWidth)
276−
277− s.shellCacheValid = true
278− s.shellCacheOutput = s.output
279− s.shellCacheExpanded = s.expandedContent
280− s.shellCacheWidth = cappedWidth
281− s.shellCachePending = s.pending
282− s.shellCacheLines = displayLines
283− s.shellCacheTruncated = truncatedCount
284− s.shellCacheMaxWidth = maxLineWidth
285−
286− return displayLines, truncatedCount, maxLineWidth
287−}
288−
289−func (s *ShellItem) RawRender(width int) string {
290− cappedWidth := cappedMessageWidth(width)
291−
292− cmd := strings.ReplaceAll(s.command, "\n", " ")
293− cmd = strings.ReplaceAll(cmd, "\t", " ")
294−
295− var prompt string
296− if s.focused {
297− prompt = s.sty.Messages.ShellPrompt.Render("$")
298− } else {
299− prompt = s.sty.Messages.ShellPromptBlurred.Render("$")
300− }
301−
302− highlighted := s.sty.Messages.ShellCommand.Render(cmd)
303− header := prompt + " " + highlighted
304−
305− if s.pending {
306− if s.output == "" {
307− // Nothing streamed yet: show the spinner under the header.
308− return header + "\n" + s.anim.Render()
309− }
310− } else if s.exitCode != 0 {
311− header += " " + s.sty.Messages.ShellExitCode.Render(fmt.Sprintf("(exit %d)", s.exitCode))
312− }
313−
314− if s.output == "" {
315− return header
316− }
317−
318− displayLines, truncatedCount, maxLineWidth := s.prepareOutput(cappedWidth)
319− s.maxLineWidth = maxLineWidth
266+ s.maxLineWidth = max(0, maxW-cappedWidth)
320267
321268 var body strings.Builder
322269
@@ -324,7 +271,7 @@ func (s *ShellItem) RawRender(width int) string {
324271 // the "more lines" notice before the output.
325272 if truncatedCount > 0 && s.pending {
326273 body.WriteString(s.sty.Messages.ShellTruncation.Render(
327− fmt.Sprintf(assistantMessageTailWindowFormat, truncatedCount),
274+ fmt.Sprintf("… %d earlier lines", truncatedCount),
328275 ))
329276 body.WriteString("\n")
330277 }
@@ -342,7 +289,7 @@ func (s *ShellItem) RawRender(width int) string {
342289 // When finished, hidden lines are below, so show the notice after.
343290 if truncatedCount > 0 && !s.pending && !s.expandedContent {
344291 body.WriteString(s.sty.Messages.ShellTruncation.Render(
345− fmt.Sprintf(assistantMessageTruncateFormat, truncatedCount),
292+ fmt.Sprintf("… %d more lines", truncatedCount),
346293 ))
347294 return header + "\n" + body.String()
348295 }
internal/ui/chat/streaming_markdown.go+251−232
@@ -201,268 +201,245 @@ func trimGlamourMargins(s string) string {
201201// we have the slightest doubt we return -1 and let the caller fall
202202// back to a full render.
203203//
204−// The implementation is a single O(n) forward scan
205−// ([scanSafeBoundaries]) that tracks block state across the whole
206−// document: fenced-code toggle, list open/close depth, HTML-block
207−// opener, and link-reference-definition presence. State is updated
208−// per line, so every blank-line candidate is evaluated against
209−// already-computed state in O(1) — no re-scanning of the prefix.
204+// Decision tree, in order of preference (latest boundary wins):
210205//
211−// Closure-aware list tracking (B1) replaces the prior "any list
212−// marker anywhere → reject" rule. A list is considered open from
213−// the first marker line until a blank line is followed by a
214−// non-continuation, non-marker line at the same or shallower
215−// indent. Once closed, prefixes ending after the close are
216−// accepted, so the incremental cache resumes working for the
217−// paragraphs that follow a list — the common case in LLM reasoning
218−// traces. On any ambiguity the list is kept open (conservative —
219−// falls back to -1).
220−//
221−// B2 (HTML block) and B3 (link reference definition) remain
222−// anywhere-in-prefix rejects: the typical assistant output
223−// contains neither, so the perf cost is zero in the common case.
206+// 1. Walk backward through every "blank line" position p such that
207+// content[:p] ends with "\n\n" (or "\n[ \t]*\n").
208+// 2. For each candidate, check that content[:p] has an even
209+// number of triple-backtick fence lines (no open fenced
210+// block). Any odd count means we'd be cutting inside a fence
211+// and mis-syntax-highlighting the trailing partial.
212+// 2b. Reject if any line in content[:p] (outside fenced blocks)
213+// is a list-marker line, an HTML-block opener, or a link
214+// reference definition. See [prefixHasOpenHazard] for the
215+// reasoning behind these "anywhere in prefix" rejects.
216+// 3. Reject if the last non-blank line of content[:p] is:
217+// - a list item marker line ("^\s*([-*+]|\d+\.)\s")
218+// - a table line (contains "|")
219+// - a block quote ("^\s*>")
220+// - a setext header underline ("^=+\s*$" or "^-+\s*$")
221+// - an indented code line (4+ leading spaces or a tab)
222+// 4. Reject if the line immediately AFTER the boundary (skipping
223+// leading blank lines) looks like a setext underline (a line
224+// of '=' or '-' only). Rendering the prefix as a paragraph
225+// would change once the underline arrived; that's exactly the
226+// "splitting changes the prefix render" hazard §4.4 calls out.
224227//
225228// Returns the byte offset of the first character AFTER the blank
226229// line, i.e. the start of the trailing segment.
227230func findSafeMarkdownBoundary(content string) int {
228− boundaries := scanSafeBoundaries(content)
229− if len(boundaries) == 0 {
231+ if len(content) == 0 {
230232 return -1
231233 }
232− return boundaries[len(boundaries)-1]
234+
235+ // Iterate every blank-line position from latest to earliest.
236+ for p := blankLineBefore(content, len(content)); p > 0; p = blankLineBefore(content, p-1) {
237+ if !isSafeBoundaryAt(content, p) {
238+ continue
239+ }
240+ return p
241+ }
242+ return -1
233243}
234244
235−// scanSafeBoundaries walks content once, O(n), tracking block
236−// state, and returns the byte offsets of every safe stable-prefix
237−// boundary in increasing order. A boundary is the byte offset
238−// immediately after a blank-line separator at which content[:p]
239−// can be rendered independently of content[p:].
245+// blankLineBefore returns the byte offset of the first character
246+// AFTER the latest blank-line separator that ends strictly before
247+// `until`. A blank-line separator is a sequence "\n([ \t]*\n)+"
248+// — one newline, then one or more lines containing only spaces or
249+// tabs and terminated by another newline. The returned offset is
250+// the start of the first non-blank line that follows the
251+// separator (or the position immediately after the final newline,
252+// if no further content remains).
240253//
241−// State carried across the scan:
242−//
243−// - inFence: toggled by each fence line. While true, no boundary
244−// is safe (we're inside a fenced code block) and list/html/ref
245−// detection is skipped so code-block contents don't false-
246−// trigger the hazards.
247−// - listDepth: current open-list nesting. Incremented on a
248−// marker line, decremented on closure (see below). A boundary
249−// is only safe when listDepth == 0 (no list open).
250−// - sawHTMLBlock / sawLinkRef: anywhere-in-prefix flags for B2
251−// and B3. Once set they never clear — the prefix is
252−// permanently unsafe for splitting.
253−// - prevNonBlank: the last non-blank line seen before the
254−// current candidate, for the last-line check
255−// ([lineOpensConstruct]).
256−//
257−// Each blank-line candidate is evaluated in O(1) against the
258−// already-computed state, so the full scan is O(lines) regardless
259−// of how many candidates exist. This replaces the prior O(n²)
260−// backward search that re-scanned the prefix for every candidate.
261−//
262−// List closure detection (B1 fix): a list opens on any marker line
263−// when no list is open. A list closes when, after a blank line, a
264−// non-marker line appears at indent < listBaseIndent+2 (not a
265−// continuation paragraph). On any ambiguity the list is kept open
266−// (conservative — falls back to -1). This replaces the prior "any
267−// list marker anywhere → reject" rule, so boundaries after a
268−// closed list followed by paragraphs are accepted — the common
269−// case in LLM reasoning traces.
270−//
271−// Crucially, boundary evaluation happens BEFORE the state update
272−// for the current line: the boundary at the start of line L has
273−// prefix = content[:L], so the state must reflect everything
274−// BEFORE L, not including L's effects. For example, the boundary
275−// at the start of a fence-opening line is safe (the fence hasn't
276−// opened in the prefix yet); the boundary at the start of a list-
277−// marker line is safe (the list hasn't opened in the prefix yet).
278−func scanSafeBoundaries(content string) []int {
279− if len(content) == 0 {
280− return nil
254+// Returns -1 when no blank-line separator exists before `until`.
255+func blankLineBefore(content string, until int) int {
256+ if until <= 0 {
257+ return -1
281258 }
282−
283− var boundaries []int
284− var prevNonBlank string
285− inFence := false
286− listDepth := 0
287− sawHTMLBlock := false
288− sawLinkRef := false
289−
290− // listBaseIndent is the indent column of the innermost open
291− // list's markers. Used for closure detection: a non-marker
292− // line at indent < listBaseIndent+2 after a blank line closes
293− // the list.
294− listBaseIndent := 0
295−
296− // blankPending tracks whether the line immediately before the
297− // current line was blank. A boundary candidate exists at the
298− // start of the first non-blank line after a blank line.
299− blankPending := false
300−
301− // processLine handles one line (without its newline
302− // terminator) starting at byte offset off. It evaluates any
303− // pending boundary candidate, then updates block state for
304− // the line.
305− //
306− // Boundary evaluation happens BEFORE the state update for the
307− // current line: the boundary at the start of line L has
308− // prefix = content[:off], so the state must reflect everything
309− // BEFORE L. However, when an open list CLOSES at line L (L is
310− // a non-marker, dedented line after a blank line), the prefix
311− // contains a COMPLETE list — the boundary is safe even though
312− // listDepth is still > 0 in the pre-L state. We compute
313− // listClosesHere before evaluation and pass it as an override.
314− processLine := func(line string, off int) {
315− blank := strings.TrimRight(line, " \t") == ""
316−
317− if blank {
318− blankPending = true
319− return
259+ // Walk backward looking for a newline followed (after optional
260+ // blank-line content) by another newline. We track the latest
261+ // newline we've seen; if the next earlier newline has only
262+ // blank chars between them, we have a blank-line separator
263+ // and the boundary sits immediately after the latest newline.
264+ end := until
265+ for end > 0 {
266+ nl := strings.LastIndexByte(content[:end], '\n')
267+ if nl < 0 {
268+ return -1
320269 }
321−
322− trimmed := strings.TrimLeft(line, " \t")
323− indent := len(line) - len(trimmed)
324− isMarker := isListItemMarker(trimmed)
325−
326− // Compute whether this line closes the open list.
327− // A list closes when a blank line is followed by a
328− // non-marker line at indent < listBaseIndent+2 (not
329− // a continuation paragraph).
330− listClosesHere := listDepth > 0 && blankPending && !isMarker && indent < listBaseIndent+2
331−
332− // Non-blank line. If a blank line preceded, this is a
333− // boundary candidate at offset `off`. Evaluate BEFORE
334− // updating state — the prefix is content[:off].
335− if blankPending {
336− if isSafeBoundaryState(inFence, listDepth, listClosesHere, sawHTMLBlock, sawLinkRef, prevNonBlank, trimmed) {
337− boundaries = append(boundaries, off)
270+ // Look for an earlier newline whose gap to nl is empty
271+ // or whitespace only.
272+ prev := strings.LastIndexByte(content[:nl], '\n')
273+ for prev >= 0 {
274+ gap := content[prev+1 : nl]
275+ if isBlankOrSpaces(gap) {
276+ return nl + 1
338277 }
339− blankPending = false
340− }
341−
342− // Now update state for this line.
343− if isFenceLine(line) {
344− inFence = !inFence
345− prevNonBlank = line
346− return
347− }
348−
349− if inFence {
350− prevNonBlank = line
351− return
278+ // Gap had non-whitespace; nl is not a blank-line
279+ // separator. Move up: try with the earlier newline as
280+ // the new "nl" candidate.
281+ break
352282 }
353−
354− // Update list depth (closure-aware B1).
355− switch {
356− case listDepth == 0 && isMarker:
357− listDepth = 1
358− listBaseIndent = indent
359− case listDepth > 0 && isMarker:
360− switch {
361− case indent > listBaseIndent:
362− // Nested list opens.
363− listDepth++
364− listBaseIndent = indent
365− case indent < listBaseIndent:
366− // Marker at shallower indent: close
367− // current list. If an outer list
368− // existed we'd need its base indent;
369− // we don't track the stack, so be
370− // conservative and close everything,
371− // then open a new list at this indent.
372− listDepth = 1
373− listBaseIndent = indent
374− }
375− // Same indent: another item, depth unchanged.
376− case listDepth > 0 && !isMarker && blankPending:
377− // Blank line then non-marker line: check for
378− // closure. A continuation paragraph is indented
379− // 2+ relative to the marker.
380− if indent < listBaseIndent+2 {
381− // Dedent: list closed. Be conservative
382− // and close all nesting levels.
383− listDepth = 0
384− }
385− // Otherwise: continuation, list stays open.
386− }
387− // listDepth > 0 && !isMarker && !blankPending:
388− // A non-marker line directly after another line
389− // (no blank line between). This is either a lazy
390− // continuation or part of a tight list's item.
391− // Keep the list open (conservative).
392−
393− // B2: HTML block opener (anywhere in prefix).
394− if !sawHTMLBlock && isHTMLBlockOpener(line) {
395− sawHTMLBlock = true
396− }
397− // B3: link reference definition (anywhere in prefix).
398− if !sawLinkRef && isLinkRefDefinition(line) {
399− sawLinkRef = true
400− }
401−
402− prevNonBlank = line
283+ end = nl
403284 }
285+ return -1
286+}
404287
405− start := 0
406− for i := 0; i < len(content); i++ {
407− if content[i] != '\n' {
408− continue
288+// isBlankOrSpaces reports whether s consists entirely of spaces
289+// and tabs (or is empty).
290+func isBlankOrSpaces(s string) bool {
291+ for i := range len(s) {
292+ if s[i] != ' ' && s[i] != '\t' {
293+ return false
409294 }
410− processLine(content[start:i], start)
411− start = i + 1
412− }
413− // Process the trailing segment (content after the last '\n').
414− // A non-blank trailing line after a blank line is a valid
415− // boundary candidate. A blank trailing line is irrelevant.
416− if start < len(content) {
417− processLine(content[start:], start)
418295 }
419−
420− return boundaries
296+ return true
421297}
422298
423−// isSafeBoundaryState evaluates the safety of a boundary candidate
424−// given the block state computed by the forward scan (reflecting
425−// everything BEFORE the boundary line). All state is passed in
426−// explicitly so this helper stays pure and testable.
299+// isSafeBoundaryAt reports whether content[:p] is a safe stable
300+// prefix. p must be a blank-line boundary (start of a line, with a
301+// blank line immediately preceding).
427302//
428−// nextLineTrimmed is the first non-blank line of the suffix
429−// (already left-trimmed), used for the setext-underline check
430−// (rule 4). listClosesHere reports whether the next line closes
431−// the currently-open list; when true, the prefix contains a
432−// complete list and the boundary is safe even though listDepth
433−// is non-zero in the pre-line state.
434−func isSafeBoundaryState(inFence bool, listDepth int, listClosesHere, sawHTMLBlock, sawLinkRef bool, prevNonBlank, nextLineTrimmed string) bool {
435− // (2) Not inside a fence.
436− if inFence {
303+// Beyond the last-line checks, three "anywhere in the prefix"
304+// hazards force a reject because they cannot be reliably reasoned
305+// about by inspecting the trailing line alone. For each of these
306+// the simplest, safest rule was chosen — see prefixHasOpenHazard.
307+func isSafeBoundaryAt(content string, p int) bool {
308+ prefix := content[:p]
309+
310+ // (2) Even number of triple-backtick fence lines.
311+ if countFenceLines(prefix)%2 != 0 {
437312 return false
438313 }
439− // (B1) No open list, unless the next line closes it.
440− if listDepth != 0 && !listClosesHere {
314+
315+ // (2b) Anywhere-in-prefix hazards: open list (B1), HTML block
316+ // opener (B2), reference link definition (B3). Any of these
317+ // anywhere in the prefix forces a fallback.
318+ if prefixHasOpenHazard(prefix) {
441319 return false
442320 }
443− // (B2) No HTML block opener anywhere in the prefix.
444− if sawHTMLBlock {
321+
322+ // (3) Inspect the last non-blank line of the prefix.
323+ lastLine := lastNonBlankLine(prefix)
324+ if lastLine != "" && lineOpensConstruct(lastLine) {
445325 return false
446326 }
447− // (B3) No link reference definition anywhere in the prefix.
448− if sawLinkRef {
449− return false
327+
328+ // (4) If anything follows, make sure it doesn't look like a
329+ // setext underline that would retroactively turn the last
330+ // paragraph of the prefix into a header.
331+ if rest := content[p:]; rest != "" {
332+ first := firstNonBlankLine(rest)
333+ if isSetextUnderlineCandidate(first) {
334+ return false
335+ }
450336 }
451− // (3) The last non-blank line of the prefix must not keep a
452− // construct open. When the list closes at the next line, the
453− // prefix's last non-blank line is a list marker — that's
454− // expected and safe because the list is complete, so skip
455− // the check in that case.
456− if !listClosesHere && prevNonBlank != "" && lineOpensConstruct(prevNonBlank) {
457− return false
337+
338+ return true
339+}
340+
341+// prefixHasOpenHazard reports whether prefix contains any of three
342+// constructs that cannot be safely cut at a blank-line boundary
343+// even when the immediately preceding line looks fine. Each check
344+// uses the SIMPLEST viable conservative rule per the F8 round-2
345+// review:
346+//
347+// B1 (loose lists). A loose list has a blank line between an item
348+// and a continuation paragraph that begins with indentation
349+// but no list marker. If a candidate boundary lands on that
350+// blank line, the prefix's trailing non-blank line is the
351+// continuation paragraph, NOT a list marker, so the last-line
352+// check would accept it even though the list is still open.
353+//
354+// Rule chosen: any list-marker line ANYWHERE in the prefix
355+// forces -1. This is overly conservative — it forfeits
356+// boundary advancement past a closed list — but it eliminates
357+// the entire bug class with zero parsing of CommonMark's
358+// loose-list closure semantics. We retain the most useful
359+// boundary in practice: the one BEFORE the list opens (no
360+// marker has appeared in the prefix yet).
361+//
362+// B2 (HTML blocks). CommonMark defines seven HTML-block opener
363+// patterns (script/pre/style/textarea, comments, processing
364+// instructions, CDATA, declarations, recognised tag names).
365+// If the prefix opens an HTML block that the suffix closes,
366+// splitting renders the prefix as raw HTML and the suffix as
367+// prose.
368+//
369+// Rule chosen: any HTML-block opener anywhere in the prefix
370+// forces -1. Same trade-off as B1 — the typical assistant
371+// output contains no raw HTML, so the perf cost is zero in
372+// the common case.
373+//
374+// B3 (reference link definitions). A line of the form
375+// "[label]: <url>" defines a link reference that the suffix
376+// may later use as "[text][label]". Splitting the document
377+// loses the definition because each half is rendered as an
378+// independent glamour document.
379+//
380+// Rule chosen: any reference link definition line anywhere in
381+// the prefix forces -1. Suffix-side reference detection is
382+// fragile (three syntaxes: [text][label], [label][], [label]),
383+// so the prefix-side check is the simpler safe choice.
384+//
385+// All three rules accept the perf hit of "no boundary after a
386+// list / HTML block / link def" in exchange for guaranteed
387+// soundness. If profiling shows this kills the F8 win on real
388+// streaming traces, the next iteration can promote each rule to
389+// its less-conservative variant (closure-aware list tracking,
390+// per-tag HTML close detection, suffix-aware ref tracking).
391+func prefixHasOpenHazard(prefix string) bool {
392+ inFence := false
393+ for line := range splitLines(prefix) {
394+ // Track fenced state so list/html/ref patterns inside a
395+ // fenced code block do not falsely trigger the hazards.
396+ if isFenceLine(line) {
397+ inFence = !inFence
398+ continue
399+ }
400+ if inFence {
401+ continue
402+ }
403+ trimmed := strings.TrimLeft(line, " \t")
404+ if trimmed == "" {
405+ continue
406+ }
407+ // B1: any list-item marker.
408+ if isListItemMarker(trimmed) {
409+ return true
410+ }
411+ // B2: HTML block opener.
412+ if isHTMLBlockOpener(line) {
413+ return true
414+ }
415+ // B3: link reference definition.
416+ if isLinkRefDefinition(line) {
417+ return true
418+ }
458419 }
459− // (4) The first non-blank line of the suffix must not look
460− // like a setext underline that would retroactively turn the
461− // last paragraph of the prefix into a header.
462− if isSetextUnderlineCandidate(nextLineTrimmed) {
463− return false
420+ return false
421+}
422+
423+// countFenceLines counts lines that begin a fenced code block in
424+// the CommonMark sense: a line whose first non-whitespace run is
425+// at least three consecutive backticks (or tildes). Each such
426+// line toggles the fenced state, so an even count means every
427+// opened fence has been closed.
428+//
429+// We accept up to three leading spaces of indentation (CommonMark
430+// rule) and require the fence characters to be the FIRST
431+// non-whitespace content of the line. We deliberately do NOT
432+// attempt to parse info-strings or differentiate opener from
433+// closer beyond toggling — a closing fence is just any line
434+// whose first non-whitespace run is >=3 of the same fence char.
435+func countFenceLines(s string) int {
436+ n := 0
437+ for line := range splitLines(s) {
438+ if isFenceLine(line) {
439+ n++
440+ }
464441 }
465− return true
442+ return n
466443}
467444
468445// isFenceLine reports whether line opens or closes a fenced code
@@ -488,6 +465,48 @@ func isFenceLine(line string) bool {
488465 return run >= 3
489466}
490467
468+// lastNonBlankLine returns the last non-blank line of s, or ""
469+// when every line is blank.
470+func lastNonBlankLine(s string) string {
471+ last := ""
472+ for line := range splitLines(s) {
473+ if strings.TrimSpace(line) != "" {
474+ last = line
475+ }
476+ }
477+ return last
478+}
479+
480+// firstNonBlankLine returns the first non-blank line of s, or ""
481+// when every line is blank.
482+func firstNonBlankLine(s string) string {
483+ for line := range splitLines(s) {
484+ if strings.TrimSpace(line) != "" {
485+ return line
486+ }
487+ }
488+ return ""
489+}
490+
491+// splitLines yields the lines of s without their terminators. The
492+// final segment is yielded even if not newline-terminated.
493+func splitLines(s string) func(yield func(string) bool) {
494+ return func(yield func(string) bool) {
495+ start := 0
496+ for i := 0; i < len(s); i++ {
497+ if s[i] == '\n' {
498+ if !yield(s[start:i]) {
499+ return
500+ }
501+ start = i + 1
502+ }
503+ }
504+ if start <= len(s)-1 {
505+ yield(s[start:])
506+ }
507+ }
508+}
509+
491510// lineOpensConstruct reports whether line keeps a markdown
492511// construct open across the boundary. We err conservatively —
493512// any case that smells like list/table/quote/setext/indented-code
internal/ui/chat/todos.go+2−2
@@ -31,7 +31,7 @@ func NewTodosToolMessageItem(
3131 result *message.ToolResult,
3232 canceled bool,
3333) ToolMessageItem {
34− return newBaseToolMessageItem(sty, toolCall, result, &TodosToolRenderContext{}, canceled, "")
34+ return newBaseToolMessageItem(sty, toolCall, result, &TodosToolRenderContext{}, canceled)
3535}
3636
3737// TodosToolRenderContext renders todos tool messages.
@@ -119,7 +119,7 @@ func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
119119 }
120120
121121 toolParams := []string{headerText}
122− header := toolHeader(sty, opts.Status, "To-Do", cappedWidth, opts, toolParams...)
122+ header := toolHeader(sty, opts.Status, "To-Do", cappedWidth, opts.Compact, toolParams...)
123123 if opts.Compact {
124124 return header
125125 }
internal/ui/chat/tool_result_content.go+14−15
@@ -20,20 +20,19 @@ func humanizedToolName(name string) string {
2020 return stringext.Capitalize(name)
2121}
2222
23−var markdownPatterns = []string{
24− "# ",
25− "## ",
26− "**",
27− "```",
28− "- ",
29− "1. ",
30− "> ",
31− "---",
32− "***",
33−}
34−
3523func looksLikeMarkdown(content string) bool {
36− for _, p := range markdownPatterns {
24+ patterns := []string{
25+ "# ",
26+ "## ",
27+ "**",
28+ "```",
29+ "- ",
30+ "1. ",
31+ "> ",
32+ "---",
33+ "***",
34+ }
35+ for _, p := range patterns {
3736 if strings.Contains(content, p) {
3837 return true
3938 }
@@ -41,7 +40,7 @@ func looksLikeMarkdown(content string) bool {
4140 return false
4241}
4342
44−func renderToolResultTextContent(sty *styles.Styles, content string, widths toolResultContentWidths, expanded bool, diffTool string) string {
43+func renderToolResultTextContent(sty *styles.Styles, content string, widths toolResultContentWidths, expanded bool) string {
4544 var result json.RawMessage
4645 if err := json.Unmarshal([]byte(content), &result); err == nil {
4746 prettyResult, err := json.MarshalIndent(result, "", " ")
@@ -51,7 +50,7 @@ func renderToolResultTextContent(sty *styles.Styles, content string, widths tool
5150 return sty.Tool.Body.Render(toolOutputPlainContent(sty, content, widths.Body, expanded))
5251 }
5352 if diffdetect.IsUnifiedDiff(content) {
54− return toolOutputDiffContentFromUnified(sty, content, widths.Diff, expanded, diffTool)
53+ return toolOutputDiffContentFromUnified(sty, content, widths.Diff, expanded)
5554 }
5655 if looksLikeMarkdown(content) {
5756 return sty.Tool.Body.Render(toolOutputCodeContent(sty, "result.md", content, 0, widths.Body, expanded))
internal/ui/chat/tool_result_content_test.go+5−5
@@ -72,7 +72,7 @@ func TestRenderToolResultTextContent(t *testing.T) {
7272 t.Fatalf("json.MarshalIndent() error = %v", err)
7373 }
7474 expected := styPtr.Tool.Body.Render(toolOutputCodeContent(styPtr, "result.json", string(prettyResult), 0, widths.Body, false))
75− got := renderToolResultTextContent(styPtr, content, widths, false, "")
75+ got := renderToolResultTextContent(styPtr, content, widths, false)
7676 if got != expected {
7777 t.Fatal("renderToolResultTextContent() did not choose JSON rendering")
7878 }
@@ -87,8 +87,8 @@ func TestRenderToolResultTextContent(t *testing.T) {
8787-# Old
8888+# New
8989`
90− expected := toolOutputDiffContentFromUnified(styPtr, content, widths.Diff, false, "")
91− got := renderToolResultTextContent(styPtr, content, widths, false, "")
90+ expected := toolOutputDiffContentFromUnified(styPtr, content, widths.Diff, false)
91+ got := renderToolResultTextContent(styPtr, content, widths, false)
9292 if got != expected {
9393 t.Fatal("renderToolResultTextContent() did not choose diff rendering")
9494 }
@@ -98,7 +98,7 @@ func TestRenderToolResultTextContent(t *testing.T) {
9898 t.Parallel()
9999 content := "# Title\n\nBody"
100100 expected := styPtr.Tool.Body.Render(toolOutputCodeContent(styPtr, "result.md", content, 0, widths.Body, false))
101− got := renderToolResultTextContent(styPtr, content, widths, false, "")
101+ got := renderToolResultTextContent(styPtr, content, widths, false)
102102 if got != expected {
103103 t.Fatal("renderToolResultTextContent() did not choose markdown rendering")
104104 }
@@ -108,7 +108,7 @@ func TestRenderToolResultTextContent(t *testing.T) {
108108 t.Parallel()
109109 content := "plain text"
110110 expected := styPtr.Tool.Body.Render(toolOutputPlainContent(styPtr, content, widths.Body, false))
111− got := renderToolResultTextContent(styPtr, content, widths, false, "")
111+ got := renderToolResultTextContent(styPtr, content, widths, false)
112112 if got != expected {
113113 t.Fatal("renderToolResultTextContent() did not choose plain rendering")
114114 }
internal/ui/chat/tools.go+64−152
@@ -19,7 +19,6 @@ import (
1919 "github.com/charmbracelet/crush/internal/stringext"
2020 "github.com/charmbracelet/crush/internal/ui/anim"
2121 "github.com/charmbracelet/crush/internal/ui/common"
22− "github.com/charmbracelet/crush/internal/ui/difftastic"
2322 "github.com/charmbracelet/crush/internal/ui/list"
2423 "github.com/charmbracelet/crush/internal/ui/styles"
2524 "github.com/charmbracelet/x/ansi"
@@ -28,11 +27,6 @@ import (
2827// responseContextHeight limits the number of lines displayed in tool output.
2928const responseContextHeight = 10
3029
31−// diffContextHeight limits the number of lines displayed in code-edit
32−// diffs before truncation. Diffs get a larger default cap than plain
33−// tool output so most of a change is visible without expanding.
34−const diffContextHeight = 30
35−
3630// toolBodyLeftPaddingTotal represents the padding that should be applied to each tool body
3731const toolBodyLeftPaddingTotal = 2
3832
@@ -58,7 +52,6 @@ type ToolMessageItem interface {
5852 SetMessageID(id string)
5953 SetStatus(status ToolStatus)
6054 Status() ToolStatus
61− SetDiffTool(tool string)
6255}
6356
6457// Compactable is an interface for tool items that can render in a compacted mode.
@@ -93,7 +86,7 @@ type DefaultToolRenderContext struct{}
9386
9487// RenderTool implements the [ToolRenderer] interface.
9588func (d *DefaultToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
96− return sty.Messages.AssistantBlurred.Render(fmt.Sprintf("Unknown tool: %s", opts.ToolCall.Name))
89+ return "TODO: Implement Tool Renderer For: " + opts.ToolCall.Name
9790}
9891
9992// ToolRenderOpts contains the data needed to render a tool call.
@@ -105,10 +98,6 @@ type ToolRenderOpts struct {
10598 Compact bool
10699 IsSpinning bool
107100 Status ToolStatus
108− // DiffTool selects the external diff renderer for edit/multi-edit diffs.
109− // "builtin" (the default) uses the in-process renderer; "difftastic"
110− // shells out to the difft binary.
111− DiffTool string
112101}
113102
114103// IsPending returns true if the tool call is still pending (not finished and
@@ -169,8 +158,6 @@ type baseToolMessageItem struct {
169158 sty *styles.Styles
170159 anim *anim.Anim
171160 expandedContent bool
172− // diffTool selects the external diff renderer for edit/multi-edit diffs.
173− diffTool string
174161}
175162
176163var _ Expandable = (*baseToolMessageItem)(nil)
@@ -182,7 +169,6 @@ func newBaseToolMessageItem(
182169 result *message.ToolResult,
183170 toolRenderer ToolRenderer,
184171 canceled bool,
185− diffTool string,
186172) *baseToolMessageItem {
187173 // we only do full width for diffs (as far as I know)
188174 hasCappedWidth := toolCall.Name != tools.EditToolName && toolCall.Name != tools.MultiEditToolName
@@ -204,7 +190,6 @@ func newBaseToolMessageItem(
204190 result: result,
205191 status: status,
206192 hasCappedWidth: hasCappedWidth,
207− diffTool: diffTool,
208193 }
209194 t.anim = anim.New(anim.Settings{
210195 ID: toolCall.ID,
@@ -229,7 +214,6 @@ func NewToolMessageItem(
229214 toolCall message.ToolCall,
230215 result *message.ToolResult,
231216 canceled bool,
232− diffTool string,
233217) ToolMessageItem {
234218 var item ToolMessageItem
235219 switch toolCall.Name {
@@ -284,7 +268,6 @@ func NewToolMessageItem(
284268 item = NewGenericToolMessageItem(sty, toolCall, result, canceled)
285269 }
286270 }
287− item.SetDiffTool(diffTool)
288271 item.SetMessageID(messageID)
289272 return item
290273}
@@ -299,16 +282,6 @@ func (t *baseToolMessageItem) SetCompact(compact bool) {
299282 t.Bump()
300283}
301284
302−// SetDiffTool sets the external diff tool preference and invalidates the cache.
303−func (t *baseToolMessageItem) SetDiffTool(tool string) {
304− if t.diffTool == tool {
305− return
306− }
307− t.diffTool = tool
308− t.clearCache()
309− t.Bump()
310−}
311−
312285// ID returns the unique identifier for this tool message item.
313286func (t *baseToolMessageItem) ID() string {
314287 return t.toolCall.ID
@@ -360,7 +333,6 @@ func (t *baseToolMessageItem) RawRender(width int) string {
360333 Compact: t.isCompact,
361334 IsSpinning: t.isSpinning(),
362335 Status: t.computeStatus(),
363− DiffTool: t.diffTool,
364336 })
365337
366338 // Prepend hook indicator if hooks ran for this tool call.
@@ -407,15 +379,10 @@ func (t *baseToolMessageItem) Render(width int) string {
407379 prefix = t.sty.Messages.ToolCallBlurred.Render()
408380 }
409381 lines := strings.Split(t.RawRender(width), "\n")
410− var sb strings.Builder
411382 for i, ln := range lines {
412− if i > 0 {
413− sb.WriteString("\n")
414− }
415− sb.WriteString(prefix)
416− sb.WriteString(ln)
383+ lines[i] = prefix + ln
417384 }
418− out := sb.String()
385+ out := strings.Join(lines, "\n")
419386 if useCache {
420387 t.setCachedPrefixedRender(out, width, key)
421388 }
@@ -556,7 +523,7 @@ func toolEarlyStateContent(sty *styles.Styles, opts *ToolRenderOpts, width int)
556523 var msg string
557524 switch opts.Status {
558525 case ToolStatusError:
559− msg = toolErrorContent(sty, opts.Result, width, opts.ExpandedContent)
526+ msg = toolErrorContent(sty, opts.Result, width)
560527 case ToolStatusCanceled:
561528 msg = sty.Tool.StateCancelled.Render("Canceled.")
562529 case ToolStatusAwaitingPermission:
@@ -570,42 +537,21 @@ func toolEarlyStateContent(sty *styles.Styles, opts *ToolRenderOpts, width int)
570537}
571538
572539// toolErrorContent formats an error message with an ERROR or WARN tag.
573−//
574−// When collapsed it shows a single truncated line plus a hidden-line
575−// notice; when expanded it renders the full message wrapped to width,
576−// preserving newlines, so multi-line failures (stack traces, compiler
577−// output, edit-failure messages) stay actionable. This mirrors the
578−// assistant-level error renderer's wrapped Details.
579−func toolErrorContent(sty *styles.Styles, result *message.ToolResult, width int, expanded bool) string {
540+func toolErrorContent(sty *styles.Styles, result *message.ToolResult, width int) string {
580541 if result == nil {
581542 return ""
582543 }
583− content := strings.TrimRight(result.Content, "\n")
584−
585− tag := sty.Tool.ErrorTag.Render("ERROR")
586− msgStyle := sty.Tool.ErrorMessage
587− if strings.Contains(content, "User denied permission") {
588− tag = sty.Tool.WarnTag.Render("WARN")
589− msgStyle = sty.Tool.WarnMessage
590− }
591− tagWidth := lipgloss.Width(tag)
592− msgWidth := max(1, width-tagWidth-1)
593−
594− if !expanded {
595− oneLine := ansi.Truncate(strings.ReplaceAll(content, "\n", " "), msgWidth, "…")
596− out := fmt.Sprintf("%s %s", tag, msgStyle.Render(oneLine))
597− if hidden := strings.Count(content, "\n"); hidden > 0 {
598− out += "\n" + sty.Tool.ContentTruncation.
599− Width(width).
600− Render(fmt.Sprintf(assistantMessageTruncateFormat, hidden))
601− }
602− return out
544+ errContent := strings.ReplaceAll(result.Content, "\n", " ")
545+ if strings.Contains(errContent, "User denied permission") {
546+ deniedTag := sty.Tool.WarnTag.Render("WARN")
547+ deniedTagWidth := lipgloss.Width(deniedTag)
548+ errContent = ansi.Truncate(errContent, width-deniedTagWidth-3, "…")
549+ return fmt.Sprintf("%s %s", deniedTag, sty.Tool.WarnMessage.Render(errContent))
603550 }
604−
605− // Expanded: wrap the full message to width, preserving newlines,
606− // and align continuation lines under the message column.
607− wrapped := msgStyle.Width(msgWidth).Render(content)
608− return lipgloss.JoinHorizontal(lipgloss.Top, tag+" ", wrapped)
551+ errTag := sty.Tool.ErrorTag.Render("ERROR")
552+ tagWidth := lipgloss.Width(errTag)
553+ errContent = ansi.Truncate(errContent, width-tagWidth-3, "…")
554+ return fmt.Sprintf("%s %s", errTag, sty.Tool.ErrorMessage.Render(errContent))
609555}
610556
611557// toolIcon returns the status icon for a tool call.
@@ -623,9 +569,9 @@ func toolIcon(sty *styles.Styles, status ToolStatus) string {
623569 }
624570}
625571
572+// toolParamList formats parameters as "main (key=value, ...)" with truncation.
626573// toolParamList formats tool parameters as "main (key=value, ...)" with truncation.
627−// When opts.ExpandedContent is true, the output wraps instead of truncating.
628−func toolParamList(sty *styles.Styles, params []string, width int, opts *ToolRenderOpts) string {
574+func toolParamList(sty *styles.Styles, params []string, width int) string {
629575 // minSpaceForMainParam is the min space required for the main param
630576 // if this is less that the value set we will only show the main param nothing else
631577 const minSpaceForMainParam = 30
@@ -652,18 +598,14 @@ func toolParamList(sty *styles.Styles, params []string, width int, opts *ToolRen
652598 }
653599 }
654600
655− if width >= 0 && (opts == nil || !opts.ExpandedContent) {
601+ if width >= 0 {
656602 output = ansi.Truncate(output, width, "…")
657− } else if opts != nil && opts.ExpandedContent && width > 0 && lipgloss.Width(output) > width {
658− output = ansi.Hardwrap(output, width, false)
659603 }
660604 return sty.Tool.ParamMain.Render(output)
661605}
662606
663607// toolHeader builds the tool header line: "● ToolName params..."
664−// When opts.ExpandedContent is true, long parameters wrap instead of truncating.
665−func toolHeader(sty *styles.Styles, status ToolStatus, name string, width int, opts *ToolRenderOpts, params ...string) string {
666− nested := opts != nil && opts.Compact
608+func toolHeader(sty *styles.Styles, status ToolStatus, name string, width int, nested bool, params ...string) string {
667609 icon := toolIcon(sty, status)
668610 nameStyle := sty.Tool.NameNormal
669611 if nested {
@@ -673,18 +615,7 @@ func toolHeader(sty *styles.Styles, status ToolStatus, name string, width int, o
673615 prefix := fmt.Sprintf("%s %s ", icon, toolName)
674616 prefixWidth := lipgloss.Width(prefix)
675617 remainingWidth := width - prefixWidth
676− paramsStr := toolParamList(sty, params, remainingWidth, opts)
677−
678− // When expanded, toolParamList may return multiple lines. Indent
679− // continuation lines to align with the first line's param text.
680− if strings.Contains(paramsStr, "\n") {
681− lines := strings.Split(paramsStr, "\n")
682− indent := strings.Repeat(" ", prefixWidth)
683− for i := 1; i < len(lines); i++ {
684− lines[i] = indent + lines[i]
685− }
686− return prefix + strings.Join(lines, "\n")
687− }
618+ paramsStr := toolParamList(sty, params, remainingWidth)
688619 return prefix + paramsStr
689620}
690621
@@ -698,7 +629,6 @@ func toolOutputPlainContent(sty *styles.Styles, content string, width int, expan
698629 maxLines = len(lines) // Show all
699630 }
700631
701− contentLineStyle := sty.Tool.ContentLine.Width(width)
702632 var out []string
703633 for i, ln := range lines {
704634 if i >= maxLines {
@@ -708,7 +638,7 @@ func toolOutputPlainContent(sty *styles.Styles, content string, width int, expan
708638 if lipgloss.Width(ln) > width {
709639 ln = ansi.Truncate(ln, width, "…")
710640 }
711− out = append(out, contentLineStyle.Render(ln))
641+ out = append(out, sty.Tool.ContentLine.Width(width).Render(ln))
712642 }
713643
714644 wasTruncated := len(lines) > responseContextHeight
@@ -750,16 +680,16 @@ func toolOutputCodeContent(sty *styles.Styles, path, content string, offset, wid
750680 bodyWidth := width - toolBodyLeftPaddingTotal
751681 codeWidth := bodyWidth - maxDigits
752682
753− codeLineStyle := sty.Tool.ContentCodeLine.Width(codeWidth)
754− codeLinePadding := sty.Tool.ContentCodeLine.GetHorizontalPadding()
755683 var out []string
756684 for i, ln := range highlightedLines {
757685 lineNum := sty.Tool.ContentLineNumber.Render(fmt.Sprintf(numFmt, i+1+offset))
758686
759687 // Truncate accounting for padding that will be added.
760− ln = ansi.Truncate(ln, codeWidth-codeLinePadding, "…")
688+ ln = ansi.Truncate(ln, codeWidth-sty.Tool.ContentCodeLine.GetHorizontalPadding(), "…")
761689
762− codeLine := codeLineStyle.Render(ln)
690+ codeLine := sty.Tool.ContentCodeLine.
691+ Width(codeWidth).
692+ Render(ln)
763693
764694 out = append(out, lipgloss.JoinHorizontal(lipgloss.Left, lineNum, codeLine))
765695 }
@@ -991,33 +921,18 @@ func formatSize(bytes int) string {
991921 )
992922 switch {
993923 case bytes >= mb:
994− result := fmt.Sprintf("%.1f MB", float64(bytes)/float64(mb))
995− if strings.HasSuffix(result, ".0 MB") {
996− result = strings.Replace(result, ".0 MB", " MB", 1)
997− }
998− return result
924+ return fmt.Sprintf("%.1f MB", float64(bytes)/float64(mb))
999925 case bytes >= kb:
1000− result := fmt.Sprintf("%.1f KB", float64(bytes)/float64(kb))
1001− if strings.HasSuffix(result, ".0 KB") {
1002− result = strings.Replace(result, ".0 KB", " KB", 1)
1003− }
1004− return result
926+ return fmt.Sprintf("%.1f KB", float64(bytes)/float64(kb))
1005927 default:
1006928 return fmt.Sprintf("%d B", bytes)
1007929 }
1008930}
1009931
1010932// toolOutputDiffContent renders a diff between old and new content.
1011−func toolOutputDiffContent(sty *styles.Styles, file, oldContent, newContent string, width int, expanded bool, diffTool string) string {
933+func toolOutputDiffContent(sty *styles.Styles, file, oldContent, newContent string, width int, expanded bool) string {
1012934 bodyWidth := width - toolBodyLeftPaddingTotal
1013935
1014− // Try difftastic if enabled. Fall back to builtin on any failure.
1015− if diffTool == "difftastic" {
1016− if out, err := difftastic.RenderDiff(file, oldContent, newContent, bodyWidth); err == nil && out != "" {
1017− return sty.Tool.Body.Render(truncateDiff(sty, out, bodyWidth, expanded))
1018− }
1019− }
1020−
1021936 formatter := common.DiffFormatter(sty).
1022937 Before(file, oldContent).
1023938 After(file, newContent).
@@ -1029,7 +944,22 @@ func toolOutputDiffContent(sty *styles.Styles, file, oldContent, newContent stri
1029944 }
1030945
1031946 formatted := formatter.String()
1032− return sty.Tool.Body.Render(truncateDiff(sty, formatted, bodyWidth, expanded))
947+ lines := strings.Split(formatted, "\n")
948+
949+ // Truncate if needed.
950+ maxLines := responseContextHeight
951+ if expanded {
952+ maxLines = len(lines)
953+ }
954+
955+ if len(lines) > maxLines && !expanded {
956+ truncMsg := sty.Tool.DiffTruncation.
957+ Width(bodyWidth).
958+ Render(fmt.Sprintf(assistantMessageTruncateFormat, len(lines)-maxLines))
959+ formatted = strings.Join(lines[:maxLines], "\n") + "\n" + truncMsg
960+ }
961+
962+ return sty.Tool.Body.Render(formatted)
1033963}
1034964
1035965// formatTimeout converts timeout seconds to a duration string (e.g., "30s").
@@ -1050,24 +980,9 @@ func formatNonZero(value int) string {
1050980}
1051981
1052982// toolOutputMultiEditDiffContent renders a diff with optional failed edits note.
1053−func toolOutputMultiEditDiffContent(sty *styles.Styles, file string, meta tools.MultiEditResponseMetadata, totalEdits, width int, expanded bool, diffTool string) string {
983+func toolOutputMultiEditDiffContent(sty *styles.Styles, file string, meta tools.MultiEditResponseMetadata, totalEdits, width int, expanded bool) string {
1054984 bodyWidth := width - toolBodyLeftPaddingTotal
1055985
1056− // Try difftastic if enabled. Fall back to builtin on any failure.
1057− if diffTool == "difftastic" {
1058− if out, err := difftastic.RenderDiff(file, meta.OldContent, meta.NewContent, bodyWidth); err == nil && out != "" {
1059− formatted := truncateDiff(sty, out, bodyWidth, expanded)
1060− // Add failed edits note if any exist.
1061− if len(meta.EditsFailed) > 0 {
1062− noteTag := sty.Tool.NoteTag.Render("Note")
1063− noteMsg := fmt.Sprintf("%d of %d edits succeeded", meta.EditsApplied, totalEdits)
1064− note := fmt.Sprintf("%s %s", noteTag, sty.Tool.NoteMessage.Render(noteMsg))
1065− formatted = formatted + "\n\n" + note
1066− }
1067− return sty.Tool.Body.Render(formatted)
1068− }
1069− }
1070−
1071986 formatter := common.DiffFormatter(sty).
1072987 Before(file, meta.OldContent).
1073988 After(file, meta.NewContent).
@@ -1079,7 +994,20 @@ func toolOutputMultiEditDiffContent(sty *styles.Styles, file string, meta tools.
1079994 }
1080995
1081996 formatted := formatter.String()
1082− formatted = truncateDiff(sty, formatted, bodyWidth, expanded)
997+ lines := strings.Split(formatted, "\n")
998+
999+ // Truncate if needed.
1000+ maxLines := responseContextHeight
1001+ if expanded {
1002+ maxLines = len(lines)
1003+ }
1004+
1005+ if len(lines) > maxLines && !expanded {
1006+ truncMsg := sty.Tool.DiffTruncation.
1007+ Width(bodyWidth).
1008+ Render(fmt.Sprintf(assistantMessageTruncateFormat, len(lines)-maxLines))
1009+ formatted = truncMsg + "\n" + strings.Join(lines[:maxLines], "\n")
1010+ }
10831011
10841012 // Add failed edits note if any exist.
10851013 if len(meta.EditsFailed) > 0 {
@@ -1092,22 +1020,6 @@ func toolOutputMultiEditDiffContent(sty *styles.Styles, file string, meta tools.
10921020 return sty.Tool.Body.Render(formatted)
10931021}
10941022
1095−// truncateDiff truncates diff output to diffContextHeight lines unless expanded.
1096−func truncateDiff(sty *styles.Styles, content string, bodyWidth int, expanded bool) string {
1097− lines := strings.Split(content, "\n")
1098− maxLines := diffContextHeight
1099− if expanded {
1100− maxLines = len(lines)
1101− }
1102− if len(lines) > maxLines && !expanded {
1103− truncMsg := sty.Tool.DiffTruncation.
1104− Width(bodyWidth).
1105− Render(fmt.Sprintf(assistantMessageTruncateFormat, len(lines)-maxLines))
1106− return strings.Join(lines[:maxLines], "\n") + "\n" + truncMsg
1107− }
1108− return content
1109−}
1110−
11111023// roundedEnumerator creates a tree enumerator with rounded corners.
11121024func roundedEnumerator(lPadding, width int) tree.Enumerator {
11131025 if width == 0 {
@@ -1408,7 +1320,7 @@ func (t *baseToolMessageItem) formatBashResultForCopy() string {
14081320
14091321 var meta tools.BashResponseMetadata
14101322 if t.result.Metadata != "" {
1411− _ = json.Unmarshal([]byte(t.result.Metadata), &meta)
1323+ json.Unmarshal([]byte(t.result.Metadata), &meta)
14121324 }
14131325
14141326 output := meta.Output
@@ -1431,7 +1343,7 @@ func (t *baseToolMessageItem) formatViewResultForCopy() string {
14311343
14321344 var meta tools.ViewResponseMetadata
14331345 if t.result.Metadata != "" {
1434− _ = json.Unmarshal([]byte(t.result.Metadata), &meta)
1346+ json.Unmarshal([]byte(t.result.Metadata), &meta)
14351347 }
14361348
14371349 if meta.Content == "" {
@@ -1502,7 +1414,7 @@ func (t *baseToolMessageItem) formatEditResultForCopy() string {
15021414 }
15031415
15041416 var params tools.EditParams
1505− _ = json.Unmarshal([]byte(t.toolCall.Input), &params)
1417+ json.Unmarshal([]byte(t.toolCall.Input), &params)
15061418
15071419 var result strings.Builder
15081420
@@ -1537,7 +1449,7 @@ func (t *baseToolMessageItem) formatMultiEditResultForCopy() string {
15371449 }
15381450
15391451 var params tools.MultiEditParams
1540− _ = json.Unmarshal([]byte(t.toolCall.Input), &params)
1452+ json.Unmarshal([]byte(t.toolCall.Input), &params)
15411453
15421454 var result strings.Builder
15431455 if meta.OldContent != "" || meta.NewContent != "" {
@@ -1728,7 +1640,7 @@ func prettifyToolName(name string) string {
17281640 case tools.AgenticFetchToolName:
17291641 return "Agentic Fetch"
17301642 case tools.WebFetchToolName:
1731− return "Web Fetch"
1643+ return "Fetch"
17321644 case tools.WebSearchToolName:
17331645 return "Search"
17341646 case tools.GlobToolName:
internal/ui/chat/unified_diff.go+14−13
@@ -1,11 +1,11 @@
11package chat
22
33import (
4+ "fmt"
45 "strings"
56
67 "github.com/charmbracelet/crush/internal/diffdetect"
78 "github.com/charmbracelet/crush/internal/ui/common"
8− "github.com/charmbracelet/crush/internal/ui/difftastic"
99 "github.com/charmbracelet/crush/internal/ui/styles"
1010)
1111
@@ -130,23 +130,13 @@ func parseUnifiedDiff(content string) []parsedDiffFile {
130130 return result
131131}
132132
133−func toolOutputDiffContentFromUnified(sty *styles.Styles, content string, width int, expanded bool, diffTool string) string {
133+func toolOutputDiffContentFromUnified(sty *styles.Styles, content string, width int, expanded bool) string {
134134 files := parseUnifiedDiff(content)
135135 if len(files) == 0 {
136136 bodyWidth := width - toolBodyLeftPaddingTotal
137137 return sty.Tool.Body.Render(toolOutputCodeContent(sty, "result.diff", content, 0, bodyWidth, expanded))
138138 }
139139 bodyWidth := width - toolBodyLeftPaddingTotal
140−
141− // Try difftastic if enabled and there's a single file diff. Fall back to
142− // builtin on any failure or when multiple files are involved.
143− if diffTool == "difftastic" && len(files) == 1 {
144− f := files[0]
145− if out, err := difftastic.RenderDiff(f.path, f.before, f.after, bodyWidth); err == nil && out != "" {
146− return sty.Tool.Body.Render(truncateDiff(sty, out, bodyWidth, expanded))
147− }
148− }
149−
150140 var blocks []string
151141 for i, f := range files {
152142 formatter := common.DiffFormatter(sty).
@@ -166,5 +156,16 @@ func toolOutputDiffContentFromUnified(sty *styles.Styles, content string, width
166156 blocks = append(blocks, formatted)
167157 }
168158 combined := strings.Join(blocks, "\n")
169− return sty.Tool.Body.Render(truncateDiff(sty, combined, bodyWidth, expanded))
159+ lines := strings.Split(combined, "\n")
160+ maxLines := responseContextHeight
161+ if expanded {
162+ maxLines = len(lines)
163+ }
164+ if len(lines) > maxLines && !expanded {
165+ truncMsg := sty.Tool.DiffTruncation.
166+ Width(bodyWidth).
167+ Render(fmt.Sprintf(assistantMessageTruncateFormat, len(lines)-maxLines))
168+ combined = strings.Join(lines[:maxLines], "\n") + "\n" + truncMsg
169+ }
170+ return sty.Tool.Body.Render(combined)
170171}
internal/ui/chat/version_bump_test.go+10−10
@@ -131,7 +131,7 @@ func TestBaseToolMessageItem_MutatorsBumpVersion(t *testing.T) {
131131
132132 sty := styles.CharmtonePantera()
133133 tc := message.ToolCall{ID: "tc1", Name: "bash", Input: "{}", Finished: false}
134− item := NewToolMessageItem(&sty, "msg", tc, nil, false, "")
134+ item := NewToolMessageItem(&sty, "msg", tc, nil, false)
135135
136136 v := item.(versionedItem)
137137
@@ -283,7 +283,7 @@ func TestAgentToolMessageItem_NestedToolMutatorsBumpVersion(t *testing.T) {
283283
284284 mkChild := func(id string) ToolMessageItem {
285285 tc := message.ToolCall{ID: id, Name: "bash", Input: `{}`, Finished: false}
286− return NewToolMessageItem(&sty, "msg", tc, nil, false, "")
286+ return NewToolMessageItem(&sty, "msg", tc, nil, false)
287287 }
288288
289289 // AddNestedTool always bumps.
@@ -320,7 +320,7 @@ func TestAgenticFetchToolMessageItem_NestedToolMutatorsBumpVersion(t *testing.T)
320320
321321 mkChild := func(id string) ToolMessageItem {
322322 tc := message.ToolCall{ID: id, Name: "fetch", Input: `{}`, Finished: false}
323− return NewToolMessageItem(&sty, "msg", tc, nil, false, "")
323+ return NewToolMessageItem(&sty, "msg", tc, nil, false)
324324 }
325325
326326 requireBump(t, "AddNestedTool", item, func() {
@@ -357,7 +357,7 @@ func TestAgentToolMessageItem_NestedChildInPlaceMutationBumpsParent(t *testing.T
357357 item := NewAgentToolMessageItem(&sty, parent, nil, false)
358358
359359 childTC := message.ToolCall{ID: "c1", Name: "bash", Input: `{}`, Finished: false}
360− child := NewToolMessageItem(&sty, "msg", childTC, nil, false, "")
360+ child := NewToolMessageItem(&sty, "msg", childTC, nil, false)
361361 item.AddNestedTool(child)
362362
363363 v0 := item.Version()
@@ -386,7 +386,7 @@ func TestAgenticFetchToolMessageItem_NestedChildInPlaceMutationBumpsParent(t *te
386386 item := NewAgenticFetchToolMessageItem(&sty, parent, nil, false)
387387
388388 childTC := message.ToolCall{ID: "c1", Name: "fetch", Input: `{}`, Finished: false}
389− child := NewToolMessageItem(&sty, "msg", childTC, nil, false, "")
389+ child := NewToolMessageItem(&sty, "msg", childTC, nil, false)
390390 item.AddNestedTool(child)
391391
392392 v0 := item.Version()
@@ -427,7 +427,7 @@ func TestBaseToolMessageItem_AnimateBumpsVersion(t *testing.T) {
427427
428428 sty := styles.CharmtonePantera()
429429 tc := message.ToolCall{ID: "tc-spin", Name: "bash", Input: "{}", Finished: false}
430− item := NewToolMessageItem(&sty, "msg", tc, nil, false, "")
430+ item := NewToolMessageItem(&sty, "msg", tc, nil, false)
431431 v := item.(versionedItem)
432432 a, ok := item.(Animatable)
433433 require.True(t, ok, "base tool message item must implement Animatable")
@@ -475,7 +475,7 @@ func TestAgentToolMessageItem_AnimateBumpsVersion(t *testing.T) {
475475 parent := NewAgentToolMessageItem(&sty, parentTC, nil, false)
476476
477477 childTC := message.ToolCall{ID: "agent-child", Name: "bash", Input: `{}`, Finished: false}
478− child := NewToolMessageItem(&sty, "msg", childTC, nil, false, "")
478+ child := NewToolMessageItem(&sty, "msg", childTC, nil, false)
479479 parent.AddNestedTool(child)
480480
481481 // Spinning + parent's own ID → parent bumps.
@@ -520,7 +520,7 @@ func TestAgenticFetchToolMessageItem_AnimateBumpsVersion(t *testing.T) {
520520 parent := NewAgenticFetchToolMessageItem(&sty, parentTC, nil, false)
521521
522522 childTC := message.ToolCall{ID: "fetch-child", Name: "fetch", Input: `{}`, Finished: false}
523− child := NewToolMessageItem(&sty, "msg", childTC, nil, false, "")
523+ child := NewToolMessageItem(&sty, "msg", childTC, nil, false)
524524 parent.AddNestedTool(child)
525525
526526 requireBump(t, "Animate[spinning,parent ID]", parent, func() {
@@ -551,7 +551,7 @@ func TestBaseToolMessageItem_FinishedTransition(t *testing.T) {
551551
552552 sty := styles.CharmtonePantera()
553553 tc := message.ToolCall{ID: "tc-fin", Name: "bash", Input: "{}", Finished: false}
554− item := NewToolMessageItem(&sty, "msg", tc, nil, false, "")
554+ item := NewToolMessageItem(&sty, "msg", tc, nil, false)
555555 require.False(t, item.Finished(), "running tool must not be Finished()")
556556
557557 tcFinished := tc
@@ -562,6 +562,6 @@ func TestBaseToolMessageItem_FinishedTransition(t *testing.T) {
562562
563563 // Canceled tool with no result is also Finished.
564564 tcCanceled := message.ToolCall{ID: "tc-cancel", Name: "bash", Input: "{}", Finished: false}
565− canceled := NewToolMessageItem(&sty, "msg", tcCanceled, nil, true, "")
565+ canceled := NewToolMessageItem(&sty, "msg", tcCanceled, nil, true)
566566 require.True(t, canceled.Finished(), "canceled tool must be Finished()")
567567}
internal/ui/common/capabilities.go+12
@@ -1,6 +1,7 @@
11package common
22
33import (
4+ "slices"
45 "strings"
56
67 tea "charm.land/bubbletea/v2"
@@ -28,6 +29,8 @@ type Capabilities struct {
2829 // KittyGraphics indicates whether the terminal supports the Kitty graphics
2930 // protocol.
3031 KittyGraphics bool
32+ // SixelGraphics indicates whether the terminal supports Sixel graphics.
33+ SixelGraphics bool
3134 // Env is the terminal environment variables.
3235 Env uv.Environ
3336 // TerminalVersion is the terminal version string.
@@ -53,6 +56,10 @@ func (c *Capabilities) Update(msg any) {
5356 c.PixelY = m.Height
5457 case uv.KittyGraphicsEvent:
5558 c.KittyGraphics = true
59+ case uv.PrimaryDeviceAttributesEvent:
60+ if slices.Contains(m, 4) {
61+ c.SixelGraphics = true
62+ }
5663 case tea.TerminalVersionMsg:
5764 c.TerminalVersion = m.Name
5865 case tea.ModeReportMsg:
@@ -101,6 +108,11 @@ func (c Capabilities) SupportsKittyGraphics() bool {
101108 return c.KittyGraphics
102109}
103110
111+// SupportsSixelGraphics returns true if the terminal supports Sixel graphics.
112+func (c Capabilities) SupportsSixelGraphics() bool {
113+ return c.SixelGraphics
114+}
115+
104116// CellSize returns the size of a single terminal cell in pixels.
105117func (c Capabilities) CellSize() (width, height int) {
106118 if c.Columns == 0 || c.Rows == 0 {
internal/ui/common/diff.go+1−20
@@ -1,35 +1,16 @@
11package common
22
33import (
4− "sync"
5−
64 "github.com/alecthomas/chroma/v2"
75 "github.com/charmbracelet/crush/internal/ui/diffview"
86 "github.com/charmbracelet/crush/internal/ui/styles"
97)
108
11−// diffStyleCache memoizes the *chroma.Style built for a given theme. Building
12−// the style (MustNewStyle) parses colors for every entry and was previously
13−// done on every DiffFormatter call. The Styles struct is mutated in place on
14−// theme change, so we key on the theme's content fingerprint rather than its
15−// pointer identity.
16−var diffStyleCache sync.Map // map[uint64]*chroma.Style
17−
189// DiffFormatter returns a diff formatter with the given styles that can be
1910// used to format diff outputs.
2011func DiffFormatter(s *styles.Styles) *diffview.DiffView {
2112 formatDiff := diffview.New()
22−
23− theme := s.ChromaTheme()
24− key := chromaStyleKey(theme, nil)
25− var style *chroma.Style
26− if cached, ok := diffStyleCache.Load(key); ok {
27− style = cached.(*chroma.Style)
28− } else {
29− style = chroma.MustNewStyle("crush", theme)
30− diffStyleCache.Store(key, style)
31− }
32−
13+ style := chroma.MustNewStyle("crush", s.ChromaTheme())
3314 diff := formatDiff.ChromaStyle(style).Style(s.Diff).TabWidth(4)
3415 return diff
3516}
internal/ui/common/elements.go−3
@@ -115,9 +115,6 @@ func formatTokensAndCost(t *styles.Styles, tokens, contextWindow int64, cost flo
115115 if contextWindow > 0 {
116116 percentage = (float64(tokens) / float64(contextWindow)) * 100
117117 }
118− if percentage > 100 {
119− percentage = 100
120− }
121118
122119 formattedCost := t.ModelInfo.Cost.Render(fmt.Sprintf("$%.2f", cost))
123120
internal/ui/common/highlight.go+11−60
@@ -2,11 +2,7 @@ package common
22
33import (
44 "bytes"
5− "hash/fnv"
65 "image/color"
7− "sort"
8− "strconv"
9− "sync"
106
117 "github.com/alecthomas/chroma/v2"
128 "github.com/alecthomas/chroma/v2/formatters"
@@ -15,41 +11,6 @@ import (
1511 "github.com/charmbracelet/crush/internal/ui/styles"
1612)
1713
18−// highlightStyleCache memoizes the *chroma.Style built for a given theme +
19−// background combination. Building the style (MustNewStyle + Builder.Transform.
20−// Build) parses colors for every entry and was previously done on every
21−// SyntaxHighlight call. The Styles struct is mutated in place on theme change,
22−// so we key on the theme's content fingerprint (plus bg) rather than its
23−// pointer identity.
24−var highlightStyleCache sync.Map // map[uint64]*chroma.Style
25−
26−// chromaStyleKey derives a stable cache key from the theme entries and bg.
27−func chromaStyleKey(theme chroma.StyleEntries, bg color.Color) uint64 {
28− h := fnv.New64a()
29− // Sort token types for a deterministic fingerprint (map order is random).
30− types := make([]int, 0, len(theme))
31− for tt := range theme {
32− types = append(types, int(tt))
33− }
34− sort.Ints(types)
35− for _, tt := range types {
36− h.Write([]byte(strconv.Itoa(tt)))
37− h.Write([]byte{0})
38− h.Write([]byte(theme[chroma.TokenType(tt)]))
39− h.Write([]byte{0})
40− }
41− var buf [8]byte
42− if bg != nil {
43− r, g, b, a := bg.RGBA()
44− buf[0] = byte(r >> 8)
45− buf[1] = byte(g >> 8)
46− buf[2] = byte(b >> 8)
47− buf[3] = byte(a >> 8)
48− }
49− h.Write(buf[:4])
50− return h.Sum64()
51−}
52−
5314// SyntaxHighlight applies syntax highlighting to the given source code based
5415// on the file name and background color. It returns the highlighted code as a
5516// string.
@@ -70,28 +31,18 @@ func SyntaxHighlight(st *styles.Styles, source, fileName string, bg color.Color)
7031 f = formatters.Fallback
7132 }
7233
73− // Build (or reuse a cached) chroma style for this theme + background.
74− theme := st.ChromaTheme()
75− key := chromaStyleKey(theme, bg)
76− var s *chroma.Style
77− if cached, ok := highlightStyleCache.Load(key); ok {
78− s = cached.(*chroma.Style)
79− } else {
80− style := chroma.MustNewStyle("crush", theme)
34+ style := chroma.MustNewStyle("crush", st.ChromaTheme())
8135
82− // Modify the style to use the provided background
83− built, err := style.Builder().Transform(
84− func(t chroma.StyleEntry) chroma.StyleEntry {
85− r, g, b, _ := bg.RGBA()
86− t.Background = chroma.NewColour(uint8(r>>8), uint8(g>>8), uint8(b>>8))
87− return t
88− },
89− ).Build()
90− if err != nil {
91− built = chromastyles.Fallback
92− }
93− s = built
94− highlightStyleCache.Store(key, s)
36+ // Modify the style to use the provided background
37+ s, err := style.Builder().Transform(
38+ func(t chroma.StyleEntry) chroma.StyleEntry {
39+ r, g, b, _ := bg.RGBA()
40+ t.Background = chroma.NewColour(uint8(r>>8), uint8(g>>8), uint8(b>>8))
41+ return t
42+ },
43+ ).Build()
44+ if err != nil {
45+ s = chromastyles.Fallback
9546 }
9647
9748 // Tokenize and format
internal/ui/common/scrollbar.go+2−4
@@ -30,17 +30,15 @@ func Scrollbar(s *styles.Styles, height, contentSize, viewportSize, offset int)
3030 }
3131
3232 // Build the scrollbar.
33− thumb := s.Dialog.ScrollbarThumb.Render(styles.ScrollbarThumb)
34− track := s.Dialog.ScrollbarTrack.Render(styles.ScrollbarTrack)
3533 var sb strings.Builder
3634 for i := range height {
3735 if i > 0 {
3836 sb.WriteString("\n")
3937 }
4038 if i >= thumbPos && i < thumbPos+thumbSize {
41− sb.WriteString(thumb)
39+ sb.WriteString(s.Dialog.ScrollbarThumb.Render(styles.ScrollbarThumb))
4240 } else {
43− sb.WriteString(track)
41+ sb.WriteString(s.Dialog.ScrollbarTrack.Render(styles.ScrollbarTrack))
4442 }
4543 }
4644
internal/ui/completions/completions.go+4−6
@@ -230,13 +230,11 @@ func (c *Completions) applyNamePriorityFilter(query string) {
230230 }
231231
232232 c.list.SetItems(c.allItems...)
233− // SetFilter computes the filtered set (a single fuzzy pass) and stores it
234− // in the list; read it back instead of recomputing via FilteredItems.
235233 c.list.SetFilter(query)
236− n := c.list.Len()
237− filtered := make([]list.FilterableItem, 0, n)
238− for i := range n {
239− filterable, ok := c.list.ItemAt(i).(list.FilterableItem)
234+ raw := c.list.FilteredItems()
235+ filtered := make([]list.FilterableItem, 0, len(raw))
236+ for _, item := range raw {
237+ filterable, ok := item.(list.FilterableItem)
240238 if !ok {
241239 continue
242240 }
internal/ui/dialog/actions.go−3
@@ -102,9 +102,6 @@ type (
102102type (
103103 ActionChangeAPIKeyState struct {
104104 State APIKeyInputState
105− // Reason is an optional human-readable explanation, set when
106− // transitioning to APIKeyInputStateError.
107− Reason string
108105 }
109106)
110107
internal/ui/dialog/api_key_input.go+7−54
@@ -40,9 +40,8 @@ type APIKeyInput struct {
4040 model config.SelectedModel
4141 modelType config.SelectedModelType
4242
43− width int
44− state APIKeyInputState
45− errMsg string // reason for the last verification failure
43+ width int
44+ state APIKeyInputState
4645
4746 keyMap struct {
4847 Submit key.Binding
@@ -92,7 +91,7 @@ func NewAPIKeyInput(
9291
9392 m.keyMap.Submit = key.NewBinding(
9493 key.WithKeys("enter", "ctrl+y"),
95− key.WithHelp("enter", "confirm"),
94+ key.WithHelp("enter", "submit"),
9695 )
9796 m.keyMap.Close = CloseKey
9897
@@ -111,11 +110,8 @@ func (m *APIKeyInput) HandleMsg(msg tea.Msg) Action {
111110 m.state = msg.State
112111 switch m.state {
113112 case APIKeyInputStateVerifying:
114− m.errMsg = ""
115113 cmd := tea.Batch(m.spinner.Tick, m.verifyAPIKey)
116114 return ActionCmd{cmd}
117− case APIKeyInputStateError:
118− m.errMsg = msg.Reason
119115 }
120116 case spinner.TickMsg:
121117 switch m.state {
@@ -173,23 +169,14 @@ func (m *APIKeyInput) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
173169
174170 m.input.Prompt = m.spinner.View()
175171
176− lines := []string{
172+ content := strings.Join([]string{
177173 m.headerView(),
178174 inputStyle.Render(m.inputView()),
179− }
180− if m.state == APIKeyInputStateError && m.errMsg != "" {
181− errStyle := textStyle.
182− Foreground(charmtone.Cherry).
183− Width(m.width - dialogStyle.GetHorizontalFrameSize())
184− lines = append(lines, errStyle.Render(m.errMsg))
185− }
186− lines = append(lines,
187175 textStyle.Render("This will be written in your global configuration:"),
188176 textStyle.Render(config.GlobalConfigData()),
189177 "",
190178 helpStyle.Render(m.help.View(m)),
191− )
192− content := strings.Join(lines, "\n")
179+ }, "\n")
193180
194181 cur := m.Cursor()
195182
@@ -314,43 +301,9 @@ func (m *APIKeyInput) verifyAPIKey() tea.Msg {
314301 }
315302
316303 if err == nil {
317− return ActionChangeAPIKeyState{State: APIKeyInputStateVerified}
318− }
319− return ActionChangeAPIKeyState{
320− State: APIKeyInputStateError,
321− Reason: apiKeyErrorReason(err),
322− }
323−}
324−
325−// apiKeyErrorReason maps a TestConnection error to a concise, user-facing
326−// explanation. Common cases (auth, permission, network) get a friendly hint;
327−// anything else falls back to the raw error message.
328−func apiKeyErrorReason(err error) string {
329− if err == nil {
330− return ""
331− }
332− msg := strings.TrimSpace(err.Error())
333− low := strings.ToLower(msg)
334− switch {
335− case strings.Contains(low, "401"),
336− strings.Contains(low, "unauthorized"),
337− strings.Contains(low, "invalid api key"),
338− strings.Contains(low, "invalid_api_key"),
339− strings.Contains(low, "authentication"):
340− return "Authentication failed — the key was rejected (401)."
341− case strings.Contains(low, "403"),
342− strings.Contains(low, "forbidden"):
343− return "Access forbidden (403) — the key may lack permissions."
344− case strings.Contains(low, "no such host"),
345− strings.Contains(low, "timeout"),
346− strings.Contains(low, "timed out"),
347− strings.Contains(low, "connection refused"),
348− strings.Contains(low, "network"),
349− strings.Contains(low, "dial tcp"):
350− return "Network error — could not reach the provider."
351− default:
352− return msg
304+ return ActionChangeAPIKeyState{APIKeyInputStateVerified}
353305 }
306+ return ActionChangeAPIKeyState{APIKeyInputStateError}
354307}
355308
356309func (m *APIKeyInput) saveKeyAndContinue() Action {
internal/ui/dialog/arguments.go+11−20
@@ -23,21 +23,6 @@ import (
2323// ArgumentsID is the identifier for the arguments dialog.
2424const ArgumentsID = "arguments"
2525
26−// argumentTitleCaser title-cases argument labels. It is stateless and safe to
27−// reuse, so it is kept package-level to avoid rebuilding it on every Draw.
28−var argumentTitleCaser = cases.Title(language.English)
29−
30−// prettyArgumentLabel converts an argument title into a human-friendly label.
31−func prettyArgumentLabel(title string) string {
32− title = strings.ReplaceAll(title, "_", " ")
33− title = strings.ReplaceAll(title, "-", " ")
34− titleParts := strings.Fields(title)
35− for i, part := range titleParts {
36− titleParts[i] = argumentTitleCaser.String(strings.ToLower(part))
37− }
38− return strings.Join(titleParts, " ")
39−}
40−
4126// Dialog sizing for arguments.
4227const (
4328 maxInputWidth = 120
@@ -51,7 +36,6 @@ type Arguments struct {
5136 com *common.Common
5237 title string
5338 arguments []commands.Argument
54− labels []string // pretty labels, precomputed from argument titles
5539 inputs []textinput.Model
5640 focused int
5741 spinner spinner.Model
@@ -104,9 +88,7 @@ func NewArguments(com *common.Common, title, description string, arguments []com
10488
10589 // Create input fields for each argument.
10690 a.inputs = make([]textinput.Model, len(arguments))
107− a.labels = make([]string, len(arguments))
10891 for i, arg := range arguments {
109− a.labels[i] = prettyArgumentLabel(arg.Title)
11092 input := textinput.New()
11193 input.SetVirtualCursor(false)
11294 input.SetStyles(com.Styles.TextInput)
@@ -280,12 +262,20 @@ func (a *Arguments) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
280262 dialogContentStyle := s.Dialog.Arguments.Content
281263 possibleWidth := area.Dx() - s.Dialog.View.GetHorizontalFrameSize() - dialogContentStyle.GetHorizontalFrameSize()
282264 // Build fields with label and input.
265+ caser := cases.Title(language.English)
266+
283267 var fields []string
284268 for i, arg := range a.arguments {
285269 isFocused := i == a.focused
286270
287− // Label was pretty-printed once at construction.
288− labelText := a.labels[i]
271+ // Try to pretty up the title for the label.
272+ title := strings.ReplaceAll(arg.Title, "_", " ")
273+ title = strings.ReplaceAll(title, "-", " ")
274+ titleParts := strings.Fields(title)
275+ for i, part := range titleParts {
276+ titleParts[i] = caser.String(strings.ToLower(part))
277+ }
278+ labelText := strings.Join(titleParts, " ")
289279
290280 markRequiredStyle := s.Dialog.Arguments.InputRequiredMarkBlurred
291281
@@ -315,6 +305,7 @@ func (a *Arguments) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
315305 renderedFields := lipgloss.JoinVertical(lipgloss.Left, fields...)
316306
317307 // Anchor width to the longest field, capped at maxInputWidth.
308+ const scrollbarWidth = 1
318309 width := lipgloss.Width(renderedFields)
319310 height := lipgloss.Height(renderedFields)
320311
internal/ui/dialog/commands.go+163−48
@@ -2,6 +2,7 @@ package dialog
22
33import (
44 "os"
5+ "strings"
56
67 "charm.land/bubbles/v2/help"
78 "charm.land/bubbles/v2/key"
@@ -12,16 +13,29 @@ import (
1213 "github.com/charmbracelet/crush/internal/config"
1314 "github.com/charmbracelet/crush/internal/ui/common"
1415 "github.com/charmbracelet/crush/internal/ui/list"
16+ "github.com/charmbracelet/crush/internal/ui/styles"
1517 uv "github.com/charmbracelet/ultraviolet"
1618)
1719
1820// CommandsID is the identifier for the commands dialog.
1921const CommandsID = "commands"
2022
23+// CommandType represents the type of commands being displayed.
24+type CommandType uint
25+
26+// String returns the string representation of the CommandType.
27+func (c CommandType) String() string { return []string{"System", "User", "MCP"}[c] }
28+
2129const (
2230 sidebarCompactModeBreakpoint = 120
2331)
2432
33+const (
34+ SystemCommands CommandType = iota
35+ UserCommands
36+ MCPPrompts
37+)
38+
2539// Commands represents a dialog that shows available commands.
2640type dockerMCPAvailabilityCheckedMsg struct {
2741 available bool
@@ -34,6 +48,8 @@ type Commands struct {
3448 UpDown,
3549 Next,
3650 Previous,
51+ Tab,
52+ ShiftTab,
3753 Close key.Binding
3854 }
3955
@@ -41,6 +57,7 @@ type Commands struct {
4157 hasSession bool
4258 hasTodos bool
4359 hasQueue bool
60+ selected CommandType
4461
4562 spinner spinner.Model
4663 loading bool
@@ -64,6 +81,7 @@ var _ Dialog = (*Commands)(nil)
6481func NewCommands(com *common.Common, sessionID string, hasSession, hasTodos, hasQueue bool, customCommands []commands.CustomCommand, mcpPrompts []commands.MCPPrompt) (*Commands, error) {
6582 c := &Commands{
6683 com: com,
84+ selected: SystemCommands,
6785 sessionID: sessionID,
6886 hasSession: hasSession,
6987 hasTodos: hasTodos,
@@ -80,8 +98,6 @@ func NewCommands(com *common.Common, sessionID string, hasSession, hasTodos, has
8098 c.list = list.NewFilterableList()
8199 c.list.Focus()
82100 c.list.SetSelected(0)
83− c.list.SetEmptyMessage("No commands")
84− c.list.SetFilterEmptyMessage("No matches")
85101
86102 c.input = textinput.New()
87103 c.input.SetVirtualCursor(false)
@@ -105,6 +121,14 @@ func NewCommands(com *common.Common, sessionID string, hasSession, hasTodos, has
105121 key.WithKeys("up", "ctrl+p"),
106122 key.WithHelp("↑", "previous item"),
107123 )
124+ c.keyMap.Tab = key.NewBinding(
125+ key.WithKeys("tab"),
126+ key.WithHelp("tab", "switch selection"),
127+ )
128+ c.keyMap.ShiftTab = key.NewBinding(
129+ key.WithKeys("shift+tab"),
130+ key.WithHelp("shift+tab", "switch selection prev"),
131+ )
108132 closeKey := CloseKey
109133 closeKey.SetHelp("esc", "cancel")
110134 c.keyMap.Close = closeKey
@@ -114,7 +138,7 @@ func NewCommands(com *common.Common, sessionID string, hasSession, hasTodos, has
114138 }
115139
116140 // Set initial commands
117− c.setCommandItems()
141+ c.setCommandItems(c.selected)
118142
119143 s := spinner.New()
120144 s.Spinner = spinner.Dot
@@ -135,7 +159,9 @@ func (c *Commands) HandleMsg(msg tea.Msg) Action {
135159 case dockerMCPAvailabilityCheckedMsg:
136160 c.dockerMCPAvailable = &msg.available
137161 c.dockerMCPCheckInFlight = false
138− c.setCommandItems()
162+ if c.selected == SystemCommands {
163+ c.setCommandItems(c.selected)
164+ }
139165 return nil
140166 case spinner.TickMsg:
141167 if c.loading {
@@ -169,6 +195,16 @@ func (c *Commands) HandleMsg(msg tea.Msg) Action {
169195 return item.Action()
170196 }
171197 }
198+ case key.Matches(msg, c.keyMap.Tab):
199+ if len(c.customCommands) > 0 || len(c.mcpPrompts) > 0 {
200+ c.selected = c.nextCommandType()
201+ c.setCommandItems(c.selected)
202+ }
203+ case key.Matches(msg, c.keyMap.ShiftTab):
204+ if len(c.customCommands) > 0 || len(c.mcpPrompts) > 0 {
205+ c.selected = c.previousCommandType()
206+ c.setCommandItems(c.selected)
207+ }
172208 default:
173209 var cmd tea.Cmd
174210 for _, item := range c.list.FilteredItems() {
@@ -208,16 +244,43 @@ func (c *Commands) Cursor() *tea.Cursor {
208244 return InputCursor(c.com.Styles, c.input.Cursor())
209245}
210246
247+// commandsRadioView generates the command type selector radio buttons.
248+func commandsRadioView(sty *styles.Styles, selected CommandType, hasUserCmds bool, hasMCPPrompts bool) string {
249+ if !hasUserCmds && !hasMCPPrompts {
250+ return ""
251+ }
252+
253+ selectedFn := func(t CommandType) string {
254+ if t == selected {
255+ return sty.Radio.On.Padding(0, 1).Render() + sty.Radio.Label.Render(t.String())
256+ }
257+ return sty.Radio.Off.Padding(0, 1).Render() + sty.Radio.Label.Render(t.String())
258+ }
259+
260+ parts := []string{
261+ selectedFn(SystemCommands),
262+ }
263+
264+ if hasUserCmds {
265+ parts = append(parts, selectedFn(UserCommands))
266+ }
267+ if hasMCPPrompts {
268+ parts = append(parts, selectedFn(MCPPrompts))
269+ }
270+
271+ return strings.Join(parts, " ")
272+}
273+
211274// Draw implements [Dialog].
212275func (c *Commands) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
213276 t := c.com.Styles
214277 width := max(0, min(defaultDialogMaxWidth, area.Dx()-t.Dialog.View.GetHorizontalBorderSize()))
215278 height := max(0, min(defaultDialogHeight, area.Dy()-t.Dialog.View.GetVerticalBorderSize()))
216− if area.Dx() != c.windowWidth {
279+ if area.Dx() != c.windowWidth && c.selected == SystemCommands {
217280 c.windowWidth = area.Dx()
218− // Since some items in the list depend on width (e.g. toggle sidebar
219− // command), we need to reset the command items when width changes.
220− c.setCommandItems()
281+ // since some items in the list depend on width (e.g. toggle sidebar command),
282+ // we need to reset the command items when width changes
283+ c.setCommandItems(c.selected)
221284 }
222285
223286 innerWidth := width - c.com.Styles.Dialog.View.GetHorizontalFrameSize()
@@ -233,6 +296,7 @@ func (c *Commands) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
233296
234297 rc := NewRenderContext(t, width)
235298 rc.Title = "Commands"
299+ rc.TitleInfo = commandsRadioView(t, c.selected, len(c.customCommands) > 0, len(c.mcpPrompts) > 0)
236300 inputView := t.Dialog.InputPrompt.Render(c.input.View())
237301 rc.AddPart(inputView)
238302 listView := t.Dialog.List.Height(c.list.Height()).Render(c.list.Render())
@@ -253,6 +317,7 @@ func (c *Commands) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
253317// ShortHelp implements [help.KeyMap].
254318func (c *Commands) ShortHelp() []key.Binding {
255319 return []key.Binding{
320+ c.keyMap.Tab,
256321 c.keyMap.UpDown,
257322 c.keyMap.Select,
258323 c.keyMap.Close,
@@ -262,50 +327,96 @@ func (c *Commands) ShortHelp() []key.Binding {
262327// FullHelp implements [help.KeyMap].
263328func (c *Commands) FullHelp() [][]key.Binding {
264329 return [][]key.Binding{
265− {c.keyMap.Select, c.keyMap.Next, c.keyMap.Previous},
330+ {c.keyMap.Select, c.keyMap.Next, c.keyMap.Previous, c.keyMap.Tab},
266331 {c.keyMap.Close},
267332 }
268333}
269334
270−// setCommandItems sets the command items, combining system, user, and MCP
271−// prompt commands into a single unified list.
272−func (c *Commands) setCommandItems() {
273− commandItems := []list.FilterableItem{}
274−
275− // System commands.
276− for _, cmd := range c.defaultCommands() {
277− commandItems = append(commandItems, cmd)
335+// nextCommandType returns the next command type in the cycle.
336+func (c *Commands) nextCommandType() CommandType {
337+ switch c.selected {
338+ case SystemCommands:
339+ if len(c.customCommands) > 0 {
340+ return UserCommands
341+ }
342+ if len(c.mcpPrompts) > 0 {
343+ return MCPPrompts
344+ }
345+ fallthrough
346+ case UserCommands:
347+ if len(c.mcpPrompts) > 0 {
348+ return MCPPrompts
349+ }
350+ fallthrough
351+ case MCPPrompts:
352+ return SystemCommands
353+ default:
354+ return SystemCommands
278355 }
356+}
279357
280− // User (custom) commands.
281− for _, cmd := range c.customCommands {
282− var action Action
283− if cmd.Skill != nil {
284− action = ActionAttachSkill{ID: cmd.Skill.SkillFilePath, Name: cmd.Skill.Name}
285− } else {
286− action = ActionRunCustomCommand{
287− Content: cmd.Content,
288− Arguments: cmd.Arguments,
289− Skill: cmd.Skill,
290− }
358+// previousCommandType returns the previous command type in the cycle.
359+func (c *Commands) previousCommandType() CommandType {
360+ switch c.selected {
361+ case SystemCommands:
362+ if len(c.mcpPrompts) > 0 {
363+ return MCPPrompts
364+ }
365+ if len(c.customCommands) > 0 {
366+ return UserCommands
291367 }
292− item := NewCommandItem(c.com.Styles, "custom_"+cmd.ID, cmd.Name, "", action)
293− if cmd.Skill != nil {
294− item = item.WithDescription(cmd.Skill.Description)
368+ return SystemCommands
369+ case UserCommands:
370+ return SystemCommands
371+ case MCPPrompts:
372+ if len(c.customCommands) > 0 {
373+ return UserCommands
295374 }
296− commandItems = append(commandItems, item)
375+ return SystemCommands
376+ default:
377+ return SystemCommands
297378 }
379+}
380+
381+// setCommandItems sets the command items based on the specified command type.
382+func (c *Commands) setCommandItems(commandType CommandType) {
383+ c.selected = commandType
298384
299− // MCP prompts.
300− for _, cmd := range c.mcpPrompts {
301− action := ActionRunMCPPrompt{
302− Title: cmd.Title,
303− Description: cmd.Description,
304− PromptID: cmd.PromptID,
305− ClientID: cmd.ClientID,
306− Arguments: cmd.Arguments,
385+ commandItems := []list.FilterableItem{}
386+ switch c.selected {
387+ case SystemCommands:
388+ for _, cmd := range c.defaultCommands() {
389+ commandItems = append(commandItems, cmd)
390+ }
391+ case UserCommands:
392+ for _, cmd := range c.customCommands {
393+ var action Action
394+ if cmd.Skill != nil {
395+ action = ActionAttachSkill{ID: cmd.Skill.SkillFilePath, Name: cmd.Skill.Name}
396+ } else {
397+ action = ActionRunCustomCommand{
398+ Content: cmd.Content,
399+ Arguments: cmd.Arguments,
400+ Skill: cmd.Skill,
401+ }
402+ }
403+ item := NewCommandItem(c.com.Styles, "custom_"+cmd.ID, cmd.Name, "", action)
404+ if cmd.Skill != nil {
405+ item = item.WithDescription(cmd.Skill.Description)
406+ }
407+ commandItems = append(commandItems, item)
408+ }
409+ case MCPPrompts:
410+ for _, cmd := range c.mcpPrompts {
411+ action := ActionRunMCPPrompt{
412+ Title: cmd.Title,
413+ Description: cmd.Description,
414+ PromptID: cmd.PromptID,
415+ ClientID: cmd.ClientID,
416+ Arguments: cmd.Arguments,
417+ }
418+ commandItems = append(commandItems, NewCommandItem(c.com.Styles, "mcp_"+cmd.ID, cmd.PromptID, "", action))
307419 }
308− commandItems = append(commandItems, NewCommandItem(c.com.Styles, "mcp_"+cmd.ID, cmd.PromptID, "", action))
309420 }
310421
311422 c.list.SetItems(commandItems...)
@@ -370,9 +481,9 @@ func (c *Commands) defaultCommands() []*CommandItem {
370481
371482 // Add external editor command if $EDITOR is available.
372483 //
373− // TODO: Use [tea.EnvMsg] to get environment variables instead of os.Getenv;
374− // reading the environment directly bypasses the TEA message system and is
375− // generally an antipattern.
484+ // TODO: Use [tea.EnvMsg] to get environment variable instead of os.Getenv;
485+ // because os.Getenv does IO is breaks the TEA paradigm and is generally an
486+ // antipattern.
376487 if os.Getenv("EDITOR") != "" {
377488 commands = append(commands, NewCommandItem(c.com.Styles, "open_external_editor", "Open External Editor", "ctrl+o", ActionExternalEditor{}))
378489 }
@@ -426,16 +537,20 @@ func (c *Commands) defaultCommands() []*CommandItem {
426537 return commands
427538}
428539
429−// SetCustomCommands sets the custom commands and refreshes the view.
540+// SetCustomCommands sets the custom commands and refreshes the view if user commands are currently displayed.
430541func (c *Commands) SetCustomCommands(customCommands []commands.CustomCommand) {
431542 c.customCommands = customCommands
432− c.setCommandItems()
543+ if c.selected == UserCommands {
544+ c.setCommandItems(c.selected)
545+ }
433546}
434547
435−// SetMCPPrompts sets the MCP prompts and refreshes the view.
548+// SetMCPPrompts sets the MCP prompts and refreshes the view if MCP prompts are currently displayed.
436549func (c *Commands) SetMCPPrompts(mcpPrompts []commands.MCPPrompt) {
437550 c.mcpPrompts = mcpPrompts
438− c.setCommandItems()
551+ if c.selected == MCPPrompts {
552+ c.setCommandItems(c.selected)
553+ }
439554}
440555
441556// StartLoading implements [LoadingDialog].
internal/ui/dialog/commands_item.go+6−6
@@ -124,13 +124,13 @@ func (c *CommandItem) Render(width int) string {
124124 descStyle = c.t.Dialog.SelectedItem
125125 }
126126 contentWidth := max(0, width-descStyle.GetHorizontalFrameSize()+1)
127− // Indent the description to align with the marker/title text.
128− indent := strings.Repeat(" ", listItemMarkerWidth)
129− textWidth := max(0, contentWidth-listItemMarkerWidth)
130− description := ansi.Truncate(strings.TrimSpace(c.description), textWidth, "…")
127+ description := ansi.Truncate(strings.TrimSpace(c.description), contentWidth, "...")
131128 descVisWidth := lipgloss.Width(description)
132− gap := strings.Repeat(" ", max(0, textWidth-descVisWidth))
133− rendered = lipgloss.JoinVertical(lipgloss.Left, rendered, descStyle.Render(indent+description+gap))
129+ gap := strings.Repeat(" ", max(0, contentWidth-descVisWidth))
130+ if description == "" {
131+ description = " "
132+ }
133+ rendered = lipgloss.JoinVertical(lipgloss.Left, rendered, descStyle.Render(description+gap))
134134 }
135135 return rendered
136136}
internal/ui/dialog/dialog.go+1−1
@@ -25,7 +25,7 @@ const (
2525// CloseKey is the default key binding to close dialogs.
2626var CloseKey = key.NewBinding(
2727 key.WithKeys("esc", "alt+esc"),
28− key.WithHelp("esc", "cancel"),
28+ key.WithHelp("esc", "exit"),
2929)
3030
3131// Action represents an action taken in a dialog after handling a message.
internal/ui/dialog/filepicker.go+2−11
@@ -14,12 +14,10 @@ import (
1414 "charm.land/bubbles/v2/key"
1515 tea "charm.land/bubbletea/v2"
1616 "charm.land/lipgloss/v2"
17− "github.com/charmbracelet/crush/internal/fsext"
1817 "github.com/charmbracelet/crush/internal/home"
1918 "github.com/charmbracelet/crush/internal/ui/common"
2019 fimage "github.com/charmbracelet/crush/internal/ui/image"
2120 uv "github.com/charmbracelet/ultraviolet"
22− "github.com/charmbracelet/x/ansi"
2321)
2422
2523// FilePickerID is the identifier for the FilePicker dialog.
@@ -71,7 +69,7 @@ func NewFilePicker(com *common.Common) (*FilePicker, tea.Cmd) {
7169
7270 f.km.Select = key.NewBinding(
7371 key.WithKeys("enter"),
74− key.WithHelp("enter", "confirm"),
72+ key.WithHelp("enter", "accept"),
7573 )
7674 f.km.Down = key.NewBinding(
7775 key.WithKeys("down", "j"),
@@ -95,7 +93,7 @@ func NewFilePicker(com *common.Common) (*FilePicker, tea.Cmd) {
9593 )
9694 f.km.Close = key.NewBinding(
9795 key.WithKeys("esc", "alt+esc"),
98− key.WithHelp("esc", "cancel"),
96+ key.WithHelp("esc", "close/exit"),
9997 )
10098
10199 fp := filepicker.New()
@@ -243,15 +241,8 @@ func (f *FilePicker) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
243241 rc := NewRenderContext(t, width)
244242 rc.Gap = 1
245243 rc.Title = "Add Image"
246− // Make the image-only restriction visible instead of silently no-oping.
247− rc.TitleInfo = t.Dialog.SecondaryText.Render(" Images only: jpg, png")
248244 rc.Help = f.help.View(f)
249245
250− // Breadcrumb: show the (truncated) current directory so users know where
251− // they are while navigating.
252− currentDir := ansi.Truncate(fsext.PrettyPath(f.fp.CurrentDirectory), innerWidth, "…")
253− rc.AddPart(t.Dialog.SecondaryText.Render(currentDir))
254−
255246 imgPreview := t.Dialog.ImagePreview.Align(lipgloss.Center).Width(innerWidth).Render(f.imagePreview(imgPrevWidth, imgPrevHeight))
256247 rc.AddPart(imgPreview)
257248
internal/ui/dialog/models.go−2
@@ -110,8 +110,6 @@ func NewModels(com *common.Common, isOnboarding bool) (*Models, error) {
110110 m.list = NewModelsList(t)
111111 m.list.Focus()
112112 m.list.SetSelected(0)
113− m.list.SetEmptyMessage("No models")
114− m.list.SetFilterEmptyMessage("No matches")
115113
116114 m.input = textinput.New()
117115 m.input.SetVirtualCursor(false)
internal/ui/dialog/models_list.go+56−52
@@ -1,6 +1,9 @@
11package dialog
22
33import (
4+ "fmt"
5+ "slices"
6+ "sort"
47 "strings"
58
69 "github.com/charmbracelet/crush/internal/ui/list"
@@ -14,11 +17,6 @@ type ModelsList struct {
1417 groups []ModelGroup
1518 query string
1619 t *styles.Styles
17−
18− // emptyMessage is shown when there are no items and no active filter.
19− emptyMessage string
20− // filterEmptyMessage is shown when a filter is active but matches nothing.
21− filterEmptyMessage string
2220}
2321
2422// NewModelsList creates a new list suitable for model items and groups.
@@ -192,56 +190,58 @@ func (f *ModelsList) VisibleItems() []list.Item {
192190 return items
193191 }
194192
195− // Build the match text once across all items (O(N) rather than O(N*G)).
196− // Each entry is prefixed with its group's lowercased title, exactly as the
197− // previous per-group code matched against.
198− names := make([]string, 0, f.Len())
193+ filterableItems := make([]list.FilterableItem, 0, f.Len())
199194 for _, g := range f.groups {
200− prefix := strings.ToLower(g.Title) + " "
201195 for _, item := range g.Items {
202− names = append(names, prefix+item.Filter())
196+ filterableItems = append(filterableItems, item)
203197 }
204198 }
205199
206− // Single fuzzy pass, then bucket matches back to their group by index.
207− matches := fuzzy.Find(query, names)
208− matched := make([]*fuzzy.Match, len(names))
209− for i := range matches {
210− matched[matches[i].Index] = &matches[i]
211− }
212−
213200 items := []list.Item{}
201+ visitedGroups := map[int]bool{}
214202
215− // Reconstruct groups with matched items, preserving original group and
216− // within-group item order.
217− idx := 0
218− for _, g := range f.groups {
219− prefixLen := len(strings.ToLower(g.Title) + " ")
203+ // Reconstruct groups with matched items
204+ // Find which group this item belongs to
205+ for gi, g := range f.groups {
220206 addedCount := 0
221− for _, item := range g.Items {
222− if m := matched[idx]; m != nil {
223− idxs := []int{}
224− for _, mIdx := range m.MatchedIndexes {
225− // Adjusts removing provider name highlights
226− if mIdx < prefixLen {
227− continue
228− }
229− idxs = append(idxs, mIdx-prefixLen)
207+ name := strings.ToLower(g.Title) + " "
208+
209+ names := make([]string, len(filterableItems))
210+ for i, item := range filterableItems {
211+ ms := item.(*ModelItem)
212+ names[i] = fmt.Sprintf("%s%s", name, ms.Filter())
213+ }
214+
215+ matches := fuzzy.Find(query, names)
216+
217+ // Sort by original index to preserve order within the group
218+ sort.SliceStable(matches, func(i, j int) bool {
219+ return matches[i].Index < matches[j].Index
220+ })
221+
222+ for _, match := range matches {
223+ item := filterableItems[match.Index].(*ModelItem)
224+ idxs := []int{}
225+ for _, idx := range match.MatchedIndexes {
226+ // Adjusts removing provider name highlights
227+ if idx < len(name) {
228+ continue
230229 }
230+ idxs = append(idxs, idx-len(name))
231+ }
231232
232− match := *m
233− match.MatchedIndexes = idxs
234− if addedCount == 0 {
233+ match.MatchedIndexes = idxs
234+ if slices.Contains(g.Items, item) {
235+ if !visitedGroups[gi] {
235236 // Add section header
236− g := g
237237 items = append(items, &g)
238+ visitedGroups[gi] = true
238239 }
239240 // Add the matched item
240241 item.SetMatch(match)
241242 items = append(items, item)
242243 addedCount++
243244 }
244− idx++
245245 }
246246 if addedCount > 0 {
247247 // Add a space separator after each provider section
@@ -252,24 +252,28 @@ func (f *ModelsList) VisibleItems() []list.Item {
252252 return items
253253}
254254
255−// SetEmptyMessage sets the placeholder shown when the list has no items and
256−// no filter is active.
257−func (f *ModelsList) SetEmptyMessage(msg string) {
258− f.emptyMessage = msg
255+// Render renders the filterable list.
256+func (f *ModelsList) Render() string {
257+ return f.List.Render()
259258}
260259
261−// SetFilterEmptyMessage sets the placeholder shown when a filter is active but
262−// yields no matches.
263−func (f *ModelsList) SetFilterEmptyMessage(msg string) {
264− f.filterEmptyMessage = msg
260+type modelGroups []ModelGroup
261+
262+func (m modelGroups) Len() int {
263+ n := 0
264+ for _, g := range m {
265+ n += len(g.Items)
266+ }
267+ return n
265268}
266269
267−// Render renders the filterable list.
268−func (f *ModelsList) Render() string {
269− if f.query != "" && f.filterEmptyMessage != "" {
270− f.List.SetEmptyMessage(f.filterEmptyMessage)
271− } else {
272− f.List.SetEmptyMessage(f.emptyMessage)
270+func (m modelGroups) String(i int) string {
271+ count := 0
272+ for _, g := range m {
273+ if i < count+len(g.Items) {
274+ return g.Items[i-count].Filter()
275+ }
276+ count += len(g.Items)
273277 }
274− return f.List.Render()
278+ return ""
275279}
internal/ui/dialog/oauth.go+2
@@ -1,6 +1,7 @@
11package dialog
22
33import (
4+ "context"
45 "fmt"
56 "strings"
67
@@ -65,6 +66,7 @@ type OAuth struct {
6566 expiresIn int
6667 interval int
6768 token *oauth.Token
69+ cancelFunc context.CancelFunc
6870}
6971
7072var _ Dialog = (*OAuth)(nil)
internal/ui/dialog/permissions.go+21−84
@@ -15,7 +15,6 @@ import (
1515 "github.com/charmbracelet/crush/internal/permission"
1616 "github.com/charmbracelet/crush/internal/stringext"
1717 "github.com/charmbracelet/crush/internal/ui/common"
18− "github.com/charmbracelet/crush/internal/ui/diffview"
1918 "github.com/charmbracelet/crush/internal/ui/styles"
2019 uv "github.com/charmbracelet/ultraviolet"
2120)
@@ -56,8 +55,10 @@ const (
5655
5756// Permissions represents a dialog for permission requests.
5857type Permissions struct {
59− com *common.Common
60− fullscreen bool // true when dialog is fullscreen
58+ com *common.Common
59+ windowWidth int // Terminal window dimensions.
60+ windowHeight int
61+ fullscreen bool // true when dialog is fullscreen
6162
6263 permission permission.PermissionRequest
6364 selectedOption int // 0: Allow, 1: Allow for session, 2: Deny
@@ -72,13 +73,6 @@ type Permissions struct {
7273 diffXOffset int // horizontal scroll offset for diff view
7374 unifiedDiffContent string
7475 splitDiffContent string
75− // diffView is a persistent diff renderer, reused across scrolls/width
76− // changes so its syntax and diff caches are preserved.
77− diffView *diffview.DiffView
78−
79− // Cached non-diff content, invalidated by viewportDirty or a width change.
80− cachedContent string
81− cachedContentWidth int
8276
8377 help help.Model
8478 keyMap permissionsKeyMap
@@ -133,7 +127,7 @@ func defaultPermissionsKeyMap() permissionsKeyMap {
133127 key.WithKeys("d", "D"),
134128 key.WithHelp("d", "deny"),
135129 ),
136− Close: denyCloseKey(),
130+ Close: CloseKey,
137131 ToggleDiffMode: key.NewBinding(
138132 key.WithKeys("t"),
139133 key.WithHelp("t", "toggle diff view"),
@@ -169,15 +163,6 @@ func defaultPermissionsKeyMap() permissionsKeyMap {
169163 }
170164}
171165
172−// denyCloseKey returns the close binding for the permissions dialog. It
173−// mirrors [CloseKey] but its help label reads "deny" because pressing esc
174−// rejects the permission request rather than merely dismissing the dialog.
175−func denyCloseKey() key.Binding {
176− k := CloseKey
177− k.SetHelp("esc", "deny")
178− return k
179−}
180−
181166var _ Dialog = (*Permissions)(nil)
182167
183168// PermissionsOption configures the permissions dialog.
@@ -212,11 +197,9 @@ func NewPermissions(com *common.Common, perm permission.PermissionRequest, opts
212197 }
213198
214199 p := &Permissions{
215− com: com,
216− permission: perm,
217− // Default to Deny (index 2) so that confirming with enter does not
218− // grant the most permissive choice. See renderButtons for ordering.
219− selectedOption: 2,
200+ com: com,
201+ permission: perm,
202+ selectedOption: 0,
220203 viewport: vp,
221204 help: h,
222205 keyMap: km,
@@ -455,11 +438,7 @@ func (p *Permissions) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
455438 if content != "" {
456439 parts = append(parts, "", content)
457440 }
458− parts = append(parts, "", buttons)
459− if hint := p.renderButtonHint(contentWidth); hint != "" {
460− parts = append(parts, hint)
461− }
462− parts = append(parts, "", helpView)
441+ parts = append(parts, "", buttons, "", helpView)
463442
464443 innerContent := lipgloss.JoinVertical(lipgloss.Left, parts...)
465444 DrawCenterCursor(scr, area, dialogStyle.Render(innerContent), nil)
@@ -548,44 +527,28 @@ func prettyName(name string) string {
548527}
549528
550529func (p *Permissions) renderContent(width int) string {
551− // Diff views manage their own caching (see renderDiff).
552530 switch p.permission.ToolName {
531+ case tools.BashToolName:
532+ return p.renderBashContent(width)
553533 case tools.EditToolName:
554534 return p.renderEditContent(width)
555535 case tools.WriteToolName:
556536 return p.renderWriteContent(width)
557537 case tools.MultiEditToolName:
558538 return p.renderMultiEditContent(width)
559− }
560−
561− // Non-diff content is cached the same way the diff is: recompute only when
562− // the viewport is dirty or the width changed. This avoids re-running JSON
563− // (un)marshalling, syntax highlighting and wrapping on every frame.
564− if !p.viewportDirty && width == p.cachedContentWidth {
565− return p.cachedContent
566− }
567−
568− var content string
569− switch p.permission.ToolName {
570− case tools.BashToolName:
571− content = p.renderBashContent(width)
572539 case tools.DownloadToolName:
573− content = p.renderDownloadContent(width)
540+ return p.renderDownloadContent(width)
574541 case tools.FetchToolName:
575− content = p.renderFetchContent(width)
542+ return p.renderFetchContent(width)
576543 case tools.AgenticFetchToolName:
577− content = p.renderAgenticFetchContent(width)
544+ return p.renderAgenticFetchContent(width)
578545 case tools.ViewToolName:
579− content = p.renderViewContent(width)
546+ return p.renderViewContent(width)
580547 case tools.LSToolName:
581− content = p.renderLSContent(width)
548+ return p.renderLSContent(width)
582549 default:
583− content = p.renderDefaultContent(width)
550+ return p.renderDefaultContent(width)
584551 }
585−
586− p.cachedContent = content
587− p.cachedContentWidth = width
588− return content
589552}
590553
591554func (p *Permissions) renderBashContent(width int) string {
@@ -630,17 +593,9 @@ func (p *Permissions) renderDiff(filePath, oldContent, newContent string, conten
630593 }
631594
632595 isSplitMode := p.isSplitMode()
633−
634− // Build the diff view once. Its content never changes for the lifetime of
635− // the dialog, so reusing it across scrolls and width changes preserves the
636− // syntax/diff caches; only XOffset/Width/layout are updated per render.
637− if p.diffView == nil {
638− p.diffView = common.DiffFormatter(p.com.Styles).
639− Before(fsext.PrettyPath(filePath), oldContent).
640− After(fsext.PrettyPath(filePath), newContent)
641− }
642−
643− formatter := p.diffView.
596+ formatter := common.DiffFormatter(p.com.Styles).
597+ Before(fsext.PrettyPath(filePath), oldContent).
598+ After(fsext.PrettyPath(filePath), newContent).
644599 XOffset(p.diffXOffset).
645600 Width(contentWidth)
646601
@@ -707,7 +662,7 @@ func (p *Permissions) renderViewContent(width int) string {
707662 if params.Offset > 0 {
708663 content += fmt.Sprintf("\nStarting from line: %d", params.Offset+1)
709664 }
710− if params.Limit > 0 && params.Limit != tools.DefaultReadLimit {
665+ if params.Limit > 0 && params.Limit != 2000 {
711666 content += fmt.Sprintf("\nLines to read: %d", params.Limit)
712667 }
713668
@@ -803,24 +758,6 @@ func (p *Permissions) renderButtons(contentWidth int) string {
803758 Render(content)
804759}
805760
806−// renderButtonHint renders a dim one-line description of the currently
807−// highlighted button so the consequence of the selection is explicit.
808−func (p *Permissions) renderButtonHint(contentWidth int) string {
809− var msg string
810− switch p.selectedOption {
811− case 0:
812− msg = "Allow this action once"
813− case 1:
814− msg = "Auto-allow this tool for the rest of the session"
815− default:
816− msg = "Reject and tell the agent"
817− }
818− return p.com.Styles.Dialog.SecondaryText.
819− Width(contentWidth).
820− Align(lipgloss.Right).
821− Render(msg)
822−}
823−
824761func (p *Permissions) canScroll() bool {
825762 if p.hasDiffView() {
826763 // Diff views can always scroll.
internal/ui/dialog/permissions_test.go+6−7
@@ -54,23 +54,22 @@ func TestPermissions_NavigationCyclesOptions(t *testing.T) {
5454 t.Parallel()
5555
5656 p := newTestPermissions(t)
57− // The dialog defaults to Deny (index 2) so enter does not grant the most
58− // permissive choice.
59− require.Equal(t, 2, p.selectedOption)
60−
61− // Tab cycles forward (wraps to 0).
62− p.HandleMsg(tea.KeyPressMsg{Code: tea.KeyTab})
6357 require.Equal(t, 0, p.selectedOption)
6458
59+ // Tab cycles forward.
6560 p.HandleMsg(tea.KeyPressMsg{Code: tea.KeyTab})
6661 require.Equal(t, 1, p.selectedOption)
6762
6863 p.HandleMsg(tea.KeyPressMsg{Code: tea.KeyTab})
6964 require.Equal(t, 2, p.selectedOption)
7065
66+ // Wrap around.
67+ p.HandleMsg(tea.KeyPressMsg{Code: tea.KeyTab})
68+ require.Equal(t, 0, p.selectedOption)
69+
7170 // Left cycles backward.
7271 p.HandleMsg(keyMsg('h'))
73− require.Equal(t, 1, p.selectedOption)
72+ require.Equal(t, 2, p.selectedOption)
7473}
7574
7675// TestPermissions_EnterConfirmsSelection verifies that enter confirms the
internal/ui/dialog/sessions.go+11−16
@@ -61,7 +61,7 @@ func NewSessions(com *common.Common, selectedSessionID string) (*Session, error)
6161 s := new(Session)
6262 s.sessionsMode = sessionsModeNormal
6363 s.com = com
64− sessions, err := com.Workspace.ListSessions(context.Background())
64+ sessions, err := com.Workspace.ListSessions(context.TODO())
6565 if err != nil {
6666 return nil, err
6767 }
@@ -81,8 +81,6 @@ func NewSessions(com *common.Common, selectedSessionID string) (*Session, error)
8181 s.list = list.NewFilterableList(sessionItems(com.Styles, sessionsModeNormal, sessions...)...)
8282 s.list.Focus()
8383 s.list.SetSelected(s.selectedSessionInx)
84− s.list.SetEmptyMessage("No sessions yet")
85− s.list.SetFilterEmptyMessage("No matches")
8684
8785 s.input = textinput.New()
8886 s.input.SetVirtualCursor(false)
@@ -91,8 +89,8 @@ func NewSessions(com *common.Common, selectedSessionID string) (*Session, error)
9189 s.input.Focus()
9290
9391 s.keyMap.Select = key.NewBinding(
94− key.WithKeys("enter", "ctrl+y"),
95− key.WithHelp("enter", "confirm"),
92+ key.WithKeys("enter", "tab", "ctrl+y"),
93+ key.WithHelp("enter", "choose"),
9694 )
9795 s.keyMap.Next = key.NewBinding(
9896 key.WithKeys("down", "ctrl+n"),
@@ -123,7 +121,7 @@ func NewSessions(com *common.Common, selectedSessionID string) (*Session, error)
123121 key.WithHelp("esc", "cancel"),
124122 )
125123 s.keyMap.ConfirmDelete = key.NewBinding(
126− key.WithKeys("y", "enter"),
124+ key.WithKeys("y"),
127125 key.WithHelp("y", "delete"),
128126 )
129127 s.keyMap.CancelDelete = key.NewBinding(
@@ -206,9 +204,8 @@ func (s *Session) HandleMsg(msg tea.Msg) Action {
206204 s.list.ScrollToSelected()
207205 case key.Matches(msg, s.keyMap.Select):
208206 if item := s.list.SelectedItem(); item != nil {
209− if sessionItem, ok := item.(*SessionItem); ok {
210− return ActionSelectSession{sessionItem.Session}
211− }
207+ sessionItem := item.(*SessionItem)
208+ return ActionSelectSession{sessionItem.Session}
212209 }
213210 default:
214211 var cmd tea.Cmd
@@ -330,9 +327,7 @@ func (s *Session) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
330327
331328func (s *Session) selectedSessionItem() *SessionItem {
332329 if item := s.list.SelectedItem(); item != nil {
333− if sessionItem, ok := item.(*SessionItem); ok {
334− return sessionItem
335− }
330+ return item.(*SessionItem)
336331 }
337332 return nil
338333}
@@ -361,7 +356,7 @@ func (s *Session) removeSession(id string) {
361356
362357func (s *Session) deleteSessionCmd(id string) tea.Cmd {
363358 return func() tea.Msg {
364− err := s.com.Workspace.DeleteSession(context.Background(), id)
359+ err := s.com.Workspace.DeleteSession(context.TODO(), id)
365360 if err != nil {
366361 return util.NewErrorMsg(err)
367362 }
@@ -387,9 +382,9 @@ func (s *Session) confirmRenameSession() Action {
387382}
388383
389384func (s *Session) updateSession(session session.Session) {
390− for i, sess := range s.sessions {
385+ for existingID, sess := range s.sessions {
391386 if sess.ID == session.ID {
392− s.sessions[i] = session
387+ s.sessions[existingID] = session
393388 break
394389 }
395390 }
@@ -397,7 +392,7 @@ func (s *Session) updateSession(session session.Session) {
397392
398393func (s *Session) updateSessionCmd(session session.Session) tea.Cmd {
399394 return func() tea.Msg {
400− _, err := s.com.Workspace.SaveSession(context.Background(), session)
395+ _, err := s.com.Workspace.SaveSession(context.TODO(), session)
401396 if err != nil {
402397 return util.NewErrorMsg(err)
403398 }
internal/ui/dialog/sessions_item.go+2−12
@@ -140,14 +140,6 @@ type ListItemStyles struct {
140140 InfoTextFocused lipgloss.Style
141141}
142142
143−// listItemMarker is a leading glyph prepended to the focused list row so
144−// selection is conveyed by shape (not background color alone). Non-focused
145−// rows are padded with equivalent leading spaces to keep text aligned.
146−const (
147− listItemMarker = "▍ "
148− listItemMarkerWidth = 2
149−)
150−
151143func renderItem(t ListItemStyles, title string, info string, focused bool, width int, cache map[int]string, m *fuzzy.Match) string {
152144 if cache == nil {
153145 cache = make(map[int]string)
@@ -159,15 +151,13 @@ func renderItem(t ListItemStyles, title string, info string, focused bool, width
159151 }
160152
161153 style := t.ItemBlurred
162− marker := strings.Repeat(" ", listItemMarkerWidth)
163154 if focused {
164155 style = t.ItemFocused
165− marker = listItemMarker
166156 }
167157
168158 var infoText string
169159 var infoWidth int
170− lineWidth := max(0, width-listItemMarkerWidth)
160+ lineWidth := width
171161 if len(info) > 0 {
172162 infoText = fmt.Sprintf(" %s ", info)
173163 if focused {
@@ -212,7 +202,7 @@ func renderItem(t ListItemStyles, title string, info string, focused bool, width
212202 content = strings.Join(parts, "")
213203 }
214204
215− content = style.Render(marker + content + gap + infoText)
205+ content = style.Render(content + gap + infoText)
216206 cache[width] = content
217207 return content
218208}
internal/ui/difftastic/difftastic.godeleted−60
@@ -1,60 +0,0 @@
1−// Package difftastic provides a syntax-aware structural diff renderer by
2−// shelling out to the difftastic binary (https://github.com/Wilfred/difftastic).
3−package difftastic
4−
5−import (
6− "context"
7− "fmt"
8− "os"
9− "os/exec"
10− "path/filepath"
11− "strconv"
12− "strings"
13−)
14−
15−// RenderDiff runs difftastic on before/after content and returns ANSI-colored
16−// output. Returns empty string and a nil error if difft is not available so
17−// callers can fall back to the builtin renderer.
18−func RenderDiff(filePath, before, after string, width int) (string, error) {
19− if _, err := exec.LookPath("difft"); err != nil {
20− return "", nil
21− }
22−
23− dir, err := os.MkdirTemp("", "crush-difftastic-*")
24− if err != nil {
25− return "", fmt.Errorf("failed to create temp directory: %w", err)
26− }
27− defer os.RemoveAll(dir)
28−
29− ext := filepath.Ext(filePath)
30− oldPath := filepath.Join(dir, "old"+ext)
31− newPath := filepath.Join(dir, "new"+ext)
32−
33− if err := os.WriteFile(oldPath, []byte(before), 0o644); err != nil {
34− return "", fmt.Errorf("failed to write before content: %w", err)
35− }
36− if err := os.WriteFile(newPath, []byte(after), 0o644); err != nil {
37− return "", fmt.Errorf("failed to write after content: %w", err)
38− }
39−
40− args := []string{
41− "--color=always",
42− "--display=inline",
43− "--width=" + strconv.Itoa(width),
44− oldPath,
45− newPath,
46− }
47−
48− cmd := exec.CommandContext(context.Background(), "difft", args...)
49− output, err := cmd.CombinedOutput()
50− if err != nil {
51− // difftastic returns non-zero exit code when differences are found.
52− // The output is still valid, so return it rather than the error.
53− if len(output) > 0 {
54− return string(output), nil
55− }
56− return "", fmt.Errorf("failed to run difftastic: %w", err)
57− }
58−
59− return strings.TrimRight(string(output), "\n"), nil
60−}
internal/ui/diffview/diffview.go+2−2
@@ -791,8 +791,8 @@ func (dv *DiffView) createSyntaxCacheKey(source string, bgColor color.Color) str
791791
792792 // Create a hash of the content + color to use as cache key
793793 h := xxh3.New()
794− _, _ = h.Write([]byte(source))
795− _, _ = h.Write([]byte(colorStr))
794+ h.Write([]byte(source))
795+ h.Write([]byte(colorStr))
796796 return fmt.Sprintf("%x", h.Sum(nil))
797797}
798798
internal/ui/diffview/diffview_test.go+8
@@ -329,3 +329,11 @@ func assertLineWidth(t *testing.T, expected int, output string) {
329329 t.Errorf("expected output width to be == %d, got %d", expected, lineWidth)
330330 }
331331}
332+
333+func assertHeight(t *testing.T, expected int, output string) {
334+ output = strings.TrimSuffix(output, "\n")
335+ lines := strings.Count(output, "\n") + 1
336+ if lines != expected {
337+ t.Errorf("expected output height to be == %d, got %d", expected, lines)
338+ }
339+}
internal/ui/diffview/style.go+44−46
@@ -1,52 +1,10 @@
11package diffview
22
33import (
4− "image/color"
5−
64 "charm.land/lipgloss/v2"
75 "github.com/charmbracelet/x/exp/charmtone"
86)
97
10−// Diff insert/delete backgrounds are derived by washing a semantic accent
11−// (green for inserts, red for deletes) over the base background. Keeping the
12−// derivation here gives both DefaultDarkStyle/DefaultLightStyle and the styles
13−// package's palette-driven theme a single source of truth, rather than several
14−// copies of the same hardcoded hex.
15−const (
16− diffCodeTint = 0.10 // symbol/code background wash strength.
17− diffLineNumTint = 0.07 // line-number background wash (slightly darker).
18−)
19−
20−// BlendBg washes accent over base by ratio in [0,1], returning a subtle tint.
21−// It is the shared basis for diff insert/delete backgrounds.
22−func BlendBg(base, accent color.Color, ratio float64) color.Color {
23− br, bg, bb, _ := base.RGBA()
24− ar, ag, ab, _ := accent.RGBA()
25− lerp := func(a, b uint32) uint8 {
26− return uint8(float64(a>>8)*(1-ratio) + float64(b>>8)*ratio + 0.5)
27− }
28− return color.RGBA{R: lerp(br, ar), G: lerp(bg, ag), B: lerp(bb, ab), A: 0xff}
29−}
30−
31−// ChangeLineStyle builds an insert- or delete-line style from semantic colors:
32−// accent drives the symbol/line-number foreground and the background wash over
33−// base, while codeFg colors the code text (pass nil to leave it unset).
34−func ChangeLineStyle(accent, base, codeFg color.Color) LineStyle {
35− code := lipgloss.NewStyle().Background(BlendBg(base, accent, diffCodeTint))
36− if codeFg != nil {
37− code = code.Foreground(codeFg)
38− }
39− return LineStyle{
40− LineNumber: lipgloss.NewStyle().
41− Foreground(accent).
42− Background(BlendBg(base, accent, diffLineNumTint)),
43− Symbol: lipgloss.NewStyle().
44− Foreground(accent).
45− Background(BlendBg(base, accent, diffCodeTint)),
46− Code: code,
47− }
48−}
49−
508// LineStyle defines the styles for a given line type in the diff view.
519type LineStyle struct {
5210 LineNumber lipgloss.Style
@@ -91,8 +49,28 @@ func DefaultLightStyle() Style {
9149 Foreground(charmtone.Pepper).
9250 Background(charmtone.Salt),
9351 },
94− InsertLine: ChangeLineStyle(charmtone.Turtle, charmtone.Salt, charmtone.Pepper),
95− DeleteLine: ChangeLineStyle(charmtone.Cherry, charmtone.Salt, charmtone.Pepper),
52+ InsertLine: LineStyle{
53+ LineNumber: lipgloss.NewStyle().
54+ Foreground(charmtone.Turtle).
55+ Background(lipgloss.Color("#c8e6c9")),
56+ Symbol: lipgloss.NewStyle().
57+ Foreground(charmtone.Turtle).
58+ Background(lipgloss.Color("#e8f5e9")),
59+ Code: lipgloss.NewStyle().
60+ Foreground(charmtone.Pepper).
61+ Background(lipgloss.Color("#e8f5e9")),
62+ },
63+ DeleteLine: LineStyle{
64+ LineNumber: lipgloss.NewStyle().
65+ Foreground(charmtone.Cherry).
66+ Background(lipgloss.Color("#ffcdd2")),
67+ Symbol: lipgloss.NewStyle().
68+ Foreground(charmtone.Cherry).
69+ Background(lipgloss.Color("#ffebee")),
70+ Code: lipgloss.NewStyle().
71+ Foreground(charmtone.Pepper).
72+ Background(lipgloss.Color("#ffebee")),
73+ },
9674 Filename: LineStyle{
9775 LineNumber: lipgloss.NewStyle().
9876 Foreground(charmtone.Iron).
@@ -129,8 +107,28 @@ func DefaultDarkStyle() Style {
129107 Foreground(charmtone.Salt).
130108 Background(charmtone.Pepper),
131109 },
132− InsertLine: ChangeLineStyle(charmtone.Turtle, charmtone.Pepper, charmtone.Salt),
133− DeleteLine: ChangeLineStyle(charmtone.Cherry, charmtone.Pepper, charmtone.Salt),
110+ InsertLine: LineStyle{
111+ LineNumber: lipgloss.NewStyle().
112+ Foreground(charmtone.Turtle).
113+ Background(lipgloss.Color("#293229")),
114+ Symbol: lipgloss.NewStyle().
115+ Foreground(charmtone.Turtle).
116+ Background(lipgloss.Color("#303a30")),
117+ Code: lipgloss.NewStyle().
118+ Foreground(charmtone.Salt).
119+ Background(lipgloss.Color("#303a30")),
120+ },
121+ DeleteLine: LineStyle{
122+ LineNumber: lipgloss.NewStyle().
123+ Foreground(charmtone.Cherry).
124+ Background(lipgloss.Color("#332929")),
125+ Symbol: lipgloss.NewStyle().
126+ Foreground(charmtone.Cherry).
127+ Background(lipgloss.Color("#3a3030")),
128+ Code: lipgloss.NewStyle().
129+ Foreground(charmtone.Salt).
130+ Background(lipgloss.Color("#3a3030")),
131+ },
134132 Filename: LineStyle{
135133 LineNumber: lipgloss.NewStyle().
136134 Foreground(charmtone.Smoke).
internal/ui/diffview/testdata/TestDiffView/Split/CustomContextLines/DarkMode.golden+3−3
@@ -3,7 +3,7 @@
33  2     2   
44  3  import (   3  import ( 
55  4   "fmt"   4   "fmt" 
6−       5 +  "strings" 
6+       5 +  "strings" 
77  5  )   6  ) 
88  6     7   
99  7  func main() {   8  func main() { 
@@ -11,6 +11,6 @@
1111  9  }  10  } 
1212 10    11   
1313 11  func getContent() string {  12  func getContent() string { 
14− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
15−      14 +  return content 
14+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
15+      14 +  return content 
1616 13  }  15  } 
internal/ui/diffview/testdata/TestDiffView/Split/CustomContextLines/LightMode.golden+3−3
@@ -3,7 +3,7 @@
33  2     2   
44  3  import (   3  import ( 
55  4   "fmt"   4   "fmt" 
6−       5 +  "strings" 
6+       5 +  "strings" 
77  5  )   6  ) 
88  6     7   
99  7  func main() {   8  func main() { 
@@ -11,6 +11,6 @@
1111  9  }  10  } 
1212 10    11   
1313 11  func getContent() string {  12  func getContent() string { 
14− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
15−      14 +  return content 
14+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
15+      14 +  return content 
1616 13  }  15  } 
internal/ui/diffview/testdata/TestDiffView/Split/Default/DarkMode.golden+2−2
@@ -2,6 +2,6 @@
22  5  )   5  ) 
33  6     6   
44  7  func main() {   7  func main() { 
5−  8 -  fmt.Println("Hello, world!")   8 +  content := "Hello, world!" 
6−       9 +  fmt.Println(content) 
5+  8 -  fmt.Println("Hello, world!")   8 +  content := "Hello, world!" 
6+       9 +  fmt.Println(content) 
77  9  }  10  } 
internal/ui/diffview/testdata/TestDiffView/Split/Default/LightMode.golden+2−2
@@ -2,6 +2,6 @@
22  5  )   5  ) 
33  6     6   
44  7  func main() {   7  func main() { 
5−  8 -  fmt.Println("Hello, world!")   8 +  content := "Hello, world!" 
6−       9 +  fmt.Println(content) 
5+  8 -  fmt.Println("Hello, world!")   8 +  content := "Hello, world!" 
6+       9 +  fmt.Println(content) 
77  9  }  10  } 
internal/ui/diffview/testdata/TestDiffView/Split/LargeWidth/DarkMode.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffView/Split/LargeWidth/LightMode.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffView/Split/MultipleHunks/DarkMode.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffView/Split/MultipleHunks/LightMode.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffView/Split/Narrow/DarkMode.golden+3−3
@@ -1,4 +1,4 @@
11 …  @@ -1,3 +1,3 @@   …   
2− 1 - a  1 + d 
3− 2 - b  2 + e 
4− 3 - c  3 + f 
2+ 1 - a  1 + d 
3+ 2 - b  2 + e 
4+ 3 - c  3 + f 
internal/ui/diffview/testdata/TestDiffView/Split/Narrow/LightMode.golden+3−3
@@ -1,4 +1,4 @@
11 …  @@ -1,3 +1,3 @@   …   
2− 1 - a  1 + d 
3− 2 - b  2 + e 
4− 3 - c  3 + f 
2+ 1 - a  1 + d 
3+ 2 - b  2 + e 
4+ 3 - c  3 + f 
internal/ui/diffview/testdata/TestDiffView/Split/NoLineNumbers/DarkMode.golden+2−2
@@ -2,6 +2,6 @@
22 )  ) 
33    
44 func main() {  func main() { 
5−-  fmt.Println("Hello, world!") +  content := "Hello, world!" 
6−  +  fmt.Println(content) 
5+-  fmt.Println("Hello, world!") +  content := "Hello, world!" 
6+  +  fmt.Println(content) 
77 }  } 
internal/ui/diffview/testdata/TestDiffView/Split/NoLineNumbers/LightMode.golden+2−2
@@ -2,6 +2,6 @@
22 )  ) 
33    
44 func main() {  func main() { 
5−-  fmt.Println("Hello, world!") +  content := "Hello, world!" 
6−  +  fmt.Println(content) 
5+-  fmt.Println("Hello, world!") +  content := "Hello, world!" 
6+  +  fmt.Println(content) 
77 }  } 
internal/ui/diffview/testdata/TestDiffView/Split/NoSyntaxHighlight/DarkMode.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4  "fmt"   4  "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffView/Split/NoSyntaxHighlight/LightMode.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4  "fmt"   4  "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffView/Split/SmallWidth/DarkMode.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getConte… 12  func getConte…
13− 12 -  return "H… 13 +  content :…
14−      14 +  return co…
13+ 12 -  return "H… 13 +  content :…
14+      14 +  return co…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffView/Split/SmallWidth/LightMode.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getConte… 12  func getConte…
13− 12 -  return "H… 13 +  content :…
14−      14 +  return co…
13+ 12 -  return "H… 13 +  content :…
14+      14 +  return co…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffView/Unified/CustomContextLines/DarkMode.golden+4−4
@@ -3,7 +3,7 @@
33  2   2   
44  3   3  import ( 
55  4   4   "fmt" 
6−     5 +  "strings" 
6+     5 +  "strings" 
77  5   6  ) 
88  6   7   
99  7   8  func main() { 
@@ -11,7 +11,7 @@
1111  9  10  } 
1212 10  11   
1313 11  12  func getContent() string { 
14− 12    -  return "Hello, world!" 
15−    13 +  content := strings.ToUpper("Hello, World!") 
16−    14 +  return content 
14+ 12    -  return "Hello, world!" 
15+    13 +  content := strings.ToUpper("Hello, World!") 
16+    14 +  return content 
1717 13  15  } 
internal/ui/diffview/testdata/TestDiffView/Unified/CustomContextLines/LightMode.golden+4−4
@@ -3,7 +3,7 @@
33  2   2   
44  3   3  import ( 
55  4   4   "fmt" 
6−     5 +  "strings" 
6+     5 +  "strings" 
77  5   6  ) 
88  6   7   
99  7   8  func main() { 
@@ -11,7 +11,7 @@
1111  9  10  } 
1212 10  11   
1313 11  12  func getContent() string { 
14− 12    -  return "Hello, world!" 
15−    13 +  content := strings.ToUpper("Hello, World!") 
16−    14 +  return content 
14+ 12    -  return "Hello, world!" 
15+    13 +  content := strings.ToUpper("Hello, World!") 
16+    14 +  return content 
1717 13  15  } 
internal/ui/diffview/testdata/TestDiffView/Unified/Default/DarkMode.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  ) 
33  6   6   
44  7   7  func main() { 
5−  8    -  fmt.Println("Hello, world!") 
6−     8 +  content := "Hello, world!" 
7−     9 +  fmt.Println(content) 
5+  8    -  fmt.Println("Hello, world!") 
6+     8 +  content := "Hello, world!" 
7+     9 +  fmt.Println(content) 
88  9  10  } 
internal/ui/diffview/testdata/TestDiffView/Unified/Default/LightMode.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  ) 
33  6   6   
44  7   7  func main() { 
5−  8    -  fmt.Println("Hello, world!") 
6−     8 +  content := "Hello, world!" 
7−     9 +  fmt.Println(content) 
5+  8    -  fmt.Println("Hello, world!") 
6+     8 +  content := "Hello, world!" 
7+     9 +  fmt.Println(content) 
88  9  10  } 
internal/ui/diffview/testdata/TestDiffView/Unified/LargeWidth/DarkMode.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffView/Unified/LargeWidth/LightMode.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffView/Unified/MultipleHunks/DarkMode.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffView/Unified/MultipleHunks/LightMode.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffView/Unified/Narrow/DarkMode.golden+6−6
@@ -1,7 +1,7 @@
11 …  …  @@ -1,3 +1,3 @@  
2− 1    - a 
3− 2    - b 
4− 3    - c 
5−    1 + d 
6−    2 + e 
7−    3 + f 
2+ 1    - a 
3+ 2    - b 
4+ 3    - c 
5+    1 + d 
6+    2 + e 
7+    3 + f 
internal/ui/diffview/testdata/TestDiffView/Unified/Narrow/LightMode.golden+6−6
@@ -1,7 +1,7 @@
11 …  …  @@ -1,3 +1,3 @@  
2− 1    - a 
3− 2    - b 
4− 3    - c 
5−    1 + d 
6−    2 + e 
7−    3 + f 
2+ 1    - a 
3+ 2    - b 
4+ 3    - c 
5+    1 + d 
6+    2 + e 
7+    3 + f 
internal/ui/diffview/testdata/TestDiffView/Unified/NoLineNumbers/DarkMode.golden+3−3
@@ -2,7 +2,7 @@
22 ) 
33  
44 func main() { 
5−-  fmt.Println("Hello, world!") 
6−+  content := "Hello, world!" 
7−+  fmt.Println(content) 
5+-  fmt.Println("Hello, world!") 
6++  content := "Hello, world!" 
7++  fmt.Println(content) 
88 } 
internal/ui/diffview/testdata/TestDiffView/Unified/NoLineNumbers/LightMode.golden+3−3
@@ -2,7 +2,7 @@
22 ) 
33  
44 func main() { 
5−-  fmt.Println("Hello, world!") 
6−+  content := "Hello, world!" 
7−+  fmt.Println(content) 
5+-  fmt.Println("Hello, world!") 
6++  content := "Hello, world!" 
7++  fmt.Println(content) 
88 } 
internal/ui/diffview/testdata/TestDiffView/Unified/NoSyntaxHighlight/DarkMode.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4  "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffView/Unified/NoSyntaxHighlight/LightMode.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4  "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffView/Unified/SmallWidth/DarkMode.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUppe…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUppe…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffView/Unified/SmallWidth/LightMode.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUppe…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUppe…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf005.golden+1−1
@@ -2,4 +2,4 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−  …  …   …  … 
5+  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf006.golden+1−1
@@ -2,5 +2,5 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf007.golden+1−1
@@ -2,6 +2,6 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf008.golden+1−1
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf009.golden+1−1
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf010.golden+1−1
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf011.golden+1−1
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf012.golden+1−1
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf013.golden+2−2
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,4 +10,4 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13−  …  …   …  … 
13+  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf014.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,5 +10,5 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−  …  …   …  … 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf015.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf016.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf017.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf018.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf019.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewHeight/Split/HeightOf020.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf005.golden+1−1
@@ -2,4 +2,4 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−  …   …  … 
5+  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf006.golden+1−1
@@ -2,5 +2,5 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf007.golden+1−1
@@ -2,6 +2,6 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf008.golden+1−1
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf009.golden+1−1
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf010.golden+1−1
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf011.golden+1−1
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf012.golden+1−1
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf013.golden+2−2
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,4 +10,4 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13−  …   …  … 
13+  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf014.golden+3−3
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,5 +10,5 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−  …   …  … 
13+ 12    -  return "Hello, world!" 
14+  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf015.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−  …   …  … 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+  …   …  … 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf016.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf017.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf018.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf019.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewHeight/Unified/HeightOf020.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewLineBreakIssue/Split.golden+5−5
@@ -1,9 +1,9 @@
11  …  @@ -1,6 +1,8 @@    …   
2−  1 - // this is   1 + /** 
3−  2 - // a regular   2 +  * this is 
4−  3 - // comment   3 +  * a block 
5−       4 +  * comment 
6−       5 +  */ 
2+  1 - // this is   1 + /** 
3+  2 - // a regular   2 +  * this is 
4+  3 - // comment   3 +  * a block 
5+       4 +  * comment 
6+       5 +  */ 
77  4  $(function() {   6  $(function() { 
88  5   console.log("Hello, world!");   7   console.log("Hello, world!"); 
99  6  });   8  }); 
internal/ui/diffview/testdata/TestDiffViewLineBreakIssue/Unified.golden+8−8
@@ -1,12 +1,12 @@
11  …   …  @@ -1,6 +1,8 @@  
2−  1    - // this is 
3−  2    - // a regular 
4−  3    - // comment 
5−     1 + /** 
6−     2 +  * this is 
7−     3 +  * a block 
8−     4 +  * comment 
9−     5 +  */ 
2+  1    - // this is 
3+  2    - // a regular 
4+  3    - // comment 
5+     1 + /** 
6+     2 +  * this is 
7+     3 +  * a block 
8+     4 +  * comment 
9+     5 +  */ 
1010  4   6  $(function() { 
1111  5   7   console.log("Hello, world!"); 
1212  6   8  }); 
internal/ui/diffview/testdata/TestDiffViewTabs/Split.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewTabs/Unified.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf001.golden+3−3
@@ -2,7 +2,7 @@
22 
33 
44 
5− 
5+ 
66 
77 
88 
@@ -10,6 +10,6 @@
1010 
1111 
1212 
13− 
14− 
13+ 
14+ 
1515 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf002.golden+3−3
@@ -2,7 +2,7 @@
22  
33  
44  
5−  
5+  
66  
77  
88  
@@ -10,6 +10,6 @@
1010  
1111 1
1212 1
13− 1
14−  
13+ 1
14+  
1515 1
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf003.golden+3−3
@@ -2,7 +2,7 @@
22  2
33  3
44  4
5−  
5+  
66  5
77  6
88  7
@@ -10,6 +10,6 @@
1010  9
1111 10
1212 11
13− 12
14−  
13+ 12
14+  
1515 13
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf004.golden+3−3
@@ -2,7 +2,7 @@
22  2 
33  3 
44  4 
5−   
5+   
66  5 
77  6 
88  7 
@@ -10,6 +10,6 @@
1010  9 
1111 10 
1212 11 
13− 12 
14−   
13+ 12 
14+   
1515 13 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf005.golden+3−3
@@ -2,7 +2,7 @@
22  2  
33  3  
44  4  
5−    
5+    
66  5  
77  6  
88  7  
@@ -10,6 +10,6 @@
1010  9  
1111 10  
1212 11  
13− 12 -
14−    
13+ 12 -
14+    
1515 13  
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf006.golden+3−3
@@ -2,7 +2,7 @@
22  2  
33  3  
44  4  
5−    
5+    
66  5  
77  6  
88  7  
@@ -10,6 +10,6 @@
1010  9  
1111 10  
1212 11  
13− 12 - 
14−    
13+ 12 - 
14+    
1515 13  
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf007.golden+3−3
@@ -2,7 +2,7 @@
22  2   
33  3   
44  4   
5−     
5+     
66  5   
77  6   
88  7   
@@ -10,6 +10,6 @@
1010  9   
1111 10   
1212 11   
13− 12 -  
14−     
13+ 12 -  
14+     
1515 13   
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf008.golden+3−3
@@ -2,7 +2,7 @@
22  2    
33  3    
44  4    
5−      
5+      
66  5    
77  6    
88  7    
@@ -10,6 +10,6 @@
1010  9   1
1111 10   1
1212 11   1
13− 12 -  1
14−     1
13+ 12 -  1
14+     1
1515 13   1
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf009.golden+3−3
@@ -2,7 +2,7 @@
22  2    2 
33  3    3 
44  4    4 
5−      5 
5+      5 
66  5    6 
77  6    7 
88  7    8 
@@ -10,6 +10,6 @@
1010  9   10 
1111 10   11 
1212 11   12 
13− 12 - 13 
14−     14 
13+ 12 - 13 
14+     14 
1515 13   15 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf010.golden+3−3
@@ -2,7 +2,7 @@
22  2    2  
33  3    3  
44  4    4  
5−      5 +
5+      5 +
66  5    6  
77  6    7  
88  7    8  
@@ -10,6 +10,6 @@
1010  9   10  
1111 10   11  
1212 11   12  
13− 12 - 13 +
14−     14 +
13+ 12 - 13 +
14+     14 +
1515 13   15  
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf011.golden+3−3
@@ -2,7 +2,7 @@
22  2    2  
33  3    3  
44  4    4  
5−      5 +
5+      5 +
66  5    6  
77  6    7  
88  7    8  
@@ -10,6 +10,6 @@
1010  9   10  
1111 10   11  
1212 11   12  
13− 12 -  13 +
14−     14 +
13+ 12 -  13 +
14+     14 +
1515 13   15  
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf012.golden+3−3
@@ -2,7 +2,7 @@
22  2    2  
33  3    3  
44  4    4  
5−      5 + 
5+      5 + 
66  5    6  
77  6    7  
88  7    8  
@@ -10,6 +10,6 @@
1010  9   10  
1111 10   11  
1212 11   12  
13− 12 -  13 + 
14−     14 + 
13+ 12 -  13 + 
14+     14 + 
1515 13   15  
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf013.golden+3−3
@@ -2,7 +2,7 @@
22  2    2   
33  3    3   
44  4    4   
5−      5 +  
5+      5 +  
66  5    6   
77  6    7   
88  7    8   
@@ -10,6 +10,6 @@
1010  9   10   
1111 10   11   
1212 11   12   
13− 12 -  13 +  
14−     14 +  
13+ 12 -  13 +  
14+     14 +  
1515 13   15   
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf014.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  …  3  …
44  4  …  4  …
5−       5 + …
5+       5 + …
66  5  )  6  )
77  6     7   
88  7  …  8  …
@@ -10,6 +10,6 @@
1010  9  } 10  }
1111 10    11   
1212 11  … 12  …
13− 12 - … 13 + …
14−      14 + …
13+ 12 - … 13 + …
14+      14 + …
1515 13  } 15  }
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf015.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  …  3  … 
44  4  …  4  … 
5−       5 + … 
5+       5 + … 
66  5  )  6  ) 
77  6     7   
88  7  …  8  … 
@@ -10,6 +10,6 @@
1010  9  } 10  } 
1111 10    11   
1212 11  … 12  … 
13− 12 - … 13 + … 
14−      14 + … 
13+ 12 - … 13 + … 
14+      14 + … 
1515 13  } 15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf016.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  i…  3  i…
44  4   …  4   …
5−       5 +  …
5+       5 +  …
66  5  )   6  ) 
77  6     7   
88  7  f…  8  f…
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  f… 12  f…
13− 12 -  … 13 +  …
14−      14 +  …
13+ 12 -  … 13 +  …
14+      14 +  …
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf017.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  i…  3  i… 
44  4   …  4   … 
5−       5 +  … 
5+       5 +  … 
66  5  )   6  ) 
77  6     7   
88  7  f…  8  f… 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  f… 12  f… 
13− 12 -  … 13 +  … 
14−      14 +  … 
13+ 12 -  … 13 +  … 
14+      14 +  … 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf018.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  im…  3  im…
44  4   …  4   …
5−       5 +  …
5+       5 +  …
66  5  )   6  ) 
77  6     7   
88  7  fu…  8  fu…
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  fu… 12  fu…
13− 12 -  … 13 +  …
14−      14 +  …
13+ 12 -  … 13 +  …
14+      14 +  …
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf019.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  im…  3  im… 
44  4   …  4   … 
5−       5 +  … 
5+       5 +  … 
66  5  )   6  ) 
77  6     7   
88  7  fu…  8  fu… 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  fu… 12  fu… 
13− 12 -  … 13 +  … 
14−      14 +  … 
13+ 12 -  … 13 +  … 
14+      14 +  … 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf020.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  imp…  3  imp…
44  4   …  4   …
5−       5 +  …
5+       5 +  …
66  5  )   6  ) 
77  6     7   
88  7  fun…  8  fun…
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  fun… 12  fun…
13− 12 -  … 13 +  …
14−      14 +  …
13+ 12 -  … 13 +  …
14+      14 +  …
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf021.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  imp…  3  imp… 
44  4   …  4   … 
5−       5 +  … 
5+       5 +  … 
66  5  )   6  ) 
77  6     7   
88  7  fun…  8  fun… 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  fun… 12  fun… 
13− 12 -  … 13 +  … 
14−      14 +  … 
13+ 12 -  … 13 +  … 
14+      14 +  … 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf022.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  impo…  3  impo…
44  4   …  4   …
5−       5 +  …
5+       5 +  …
66  5  )   6  ) 
77  6     7   
88  7  func…  8  func…
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func… 12  func…
13− 12 -  … 13 +  …
14−      14 +  …
13+ 12 -  … 13 +  …
14+      14 +  …
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf023.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  impo…  3  impo… 
44  4   …  4   … 
5−       5 +  … 
5+       5 +  … 
66  5  )   6  ) 
77  6     7   
88  7  func…  8  func… 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func… 12  func… 
13− 12 -  … 13 +  … 
14−      14 +  … 
13+ 12 -  … 13 +  … 
14+      14 +  … 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf024.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  impor…  3  impor…
44  4   "…  4   "…
5−       5 +  "…
5+       5 +  "…
66  5  )   6  ) 
77  6     7   
88  7  func …  8  func …
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func … 12  func …
13− 12 -  r… 13 +  c…
14−      14 +  r…
13+ 12 -  r… 13 +  c…
14+      14 +  r…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf025.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  impor…  3  impor… 
44  4   "…  4   "… 
5−       5 +  "… 
5+       5 +  "… 
66  5  )   6  ) 
77  6     7   
88  7  func …  8  func … 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func … 12  func … 
13− 12 -  r… 13 +  c… 
14−      14 +  r… 
13+ 12 -  r… 13 +  c… 
14+      14 +  r… 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf026.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import…  3  import…
44  4   "f…  4   "f…
5−       5 +  "s…
5+       5 +  "s…
66  5  )   6  ) 
77  6     7   
88  7  func m…  8  func m…
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func g… 12  func g…
13− 12 -  re… 13 +  co…
14−      14 +  re…
13+ 12 -  re… 13 +  co…
14+      14 +  re…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf027.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import…  3  import… 
44  4   "f…  4   "f… 
5−       5 +  "s… 
5+       5 +  "s… 
66  5  )   6  ) 
77  6     7   
88  7  func m…  8  func m… 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func g… 12  func g… 
13− 12 -  re… 13 +  co… 
14−      14 +  re… 
13+ 12 -  re… 13 +  co… 
14+      14 +  re… 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf028.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (  3  import (
44  4   "fm…  4   "fm…
5−       5 +  "st…
5+       5 +  "st…
66  5  )   6  ) 
77  6     7   
88  7  func ma…  8  func ma…
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func ge… 12  func ge…
13− 12 -  ret… 13 +  con…
14−      14 +  ret…
13+ 12 -  ret… 13 +  con…
14+      14 +  ret…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf029.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (  3  import ( 
44  4   "fm…  4   "fm… 
5−       5 +  "st… 
5+       5 +  "st… 
66  5  )   6  ) 
77  6     7   
88  7  func ma…  8  func ma… 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func ge… 12  func ge… 
13− 12 -  ret… 13 +  con… 
14−      14 +  ret… 
13+ 12 -  ret… 13 +  con… 
14+      14 +  ret… 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf030.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"  4   "fmt"
5−       5 +  "str…
5+       5 +  "str…
66  5  )   6  ) 
77  6     7   
88  7  func mai…  8  func mai…
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func get… 12  func get…
13− 12 -  retu… 13 +  cont…
14−      14 +  retu…
13+ 12 -  retu… 13 +  cont…
14+      14 +  retu…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf031.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"  4   "fmt" 
5−       5 +  "str… 
5+       5 +  "str… 
66  5  )   6  ) 
77  6     7   
88  7  func mai…  8  func mai… 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func get… 12  func get… 
13− 12 -  retu… 13 +  cont… 
14−      14 +  retu… 
13+ 12 -  retu… 13 +  cont… 
14+      14 +  retu… 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf032.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "stri…
5+       5 +  "stri…
66  5  )   6  ) 
77  6     7   
88  7  func main…  8  func main…
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getC… 12  func getC…
13− 12 -  retur… 13 +  conte…
14−      14 +  retur…
13+ 12 -  retur… 13 +  conte…
14+      14 +  retur…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf033.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "stri… 
5+       5 +  "stri… 
66  5  )   6  ) 
77  6     7   
88  7  func main…  8  func main… 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getC… 12  func getC… 
13− 12 -  retur… 13 +  conte… 
14−      14 +  retur… 
13+ 12 -  retur… 13 +  conte… 
14+      14 +  retur… 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf034.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strin…
5+       5 +  "strin…
66  5  )   6  ) 
77  6     7   
88  7  func main(…  8  func main(…
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getCo… 12  func getCo…
13− 12 -  return… 13 +  conten…
14−      14 +  return…
13+ 12 -  return… 13 +  conten…
14+      14 +  return…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf035.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strin… 
5+       5 +  "strin… 
66  5  )   6  ) 
77  6     7   
88  7  func main(…  8  func main(… 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getCo… 12  func getCo… 
13− 12 -  return… 13 +  conten… 
14−      14 +  return… 
13+ 12 -  return… 13 +  conten… 
14+      14 +  return… 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf036.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "string…
5+       5 +  "string…
66  5  )   6  ) 
77  6     7   
88  7  func main()…  8  func main()…
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getCon… 12  func getCon…
13− 12 -  return … 13 +  content…
14−      14 +  return …
13+ 12 -  return … 13 +  content…
14+      14 +  return …
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf037.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "string… 
5+       5 +  "string… 
66  5  )   6  ) 
77  6     7   
88  7  func main()…  8  func main()… 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getCon… 12  func getCon… 
13− 12 -  return … 13 +  content… 
14−      14 +  return … 
13+ 12 -  return … 13 +  content… 
14+      14 +  return … 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf038.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings"
5+       5 +  "strings"
66  5  )   6  ) 
77  6     7   
88  7  func main() {  8  func main() {
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getCont… 12  func getCont…
13− 12 -  return "… 13 +  content …
14−      14 +  return c…
13+ 12 -  return "… 13 +  content …
14+      14 +  return c…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf039.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {  8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getCont… 12  func getCont… 
13− 12 -  return "… 13 +  content … 
14−      14 +  return c… 
13+ 12 -  return "… 13 +  content … 
14+      14 +  return c… 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf040.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getConte… 12  func getConte…
13− 12 -  return "H… 13 +  content :…
14−      14 +  return co…
13+ 12 -  return "H… 13 +  content :…
14+      14 +  return co…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf041.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getConte… 12  func getConte… 
13− 12 -  return "H… 13 +  content :… 
14−      14 +  return co… 
13+ 12 -  return "H… 13 +  content :… 
14+      14 +  return co… 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf042.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getConten… 12  func getConten…
13− 12 -  return "He… 13 +  content :=…
14−      14 +  return con…
13+ 12 -  return "He… 13 +  content :=…
14+      14 +  return con…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf043.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getConten… 12  func getConten… 
13− 12 -  return "He… 13 +  content :=… 
14−      14 +  return con… 
13+ 12 -  return "He… 13 +  content :=… 
14+      14 +  return con… 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf044.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent… 12  func getContent…
13− 12 -  return "Hel… 13 +  content := …
14−      14 +  return cont…
13+ 12 -  return "Hel… 13 +  content := …
14+      14 +  return cont…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf045.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent… 12  func getContent… 
13− 12 -  return "Hel… 13 +  content := … 
14−      14 +  return cont… 
13+ 12 -  return "Hel… 13 +  content := … 
14+      14 +  return cont… 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf046.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent(… 12  func getContent(…
13− 12 -  return "Hell… 13 +  content := s…
14−      14 +  return conte…
13+ 12 -  return "Hell… 13 +  content := s…
14+      14 +  return conte…
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf047.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent(… 12  func getContent(… 
13− 12 -  return "Hell… 13 +  content := s… 
14−      14 +  return conte… 
13+ 12 -  return "Hell… 13 +  content := s… 
14+      14 +  return conte… 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf048.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent()… 12  func getContent()…
13− 12 -  return "Hello… 13 +  content := st…
14−      14 +  return content
13+ 12 -  return "Hello… 13 +  content := st…
14+      14 +  return content
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf049.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent()… 12  func getContent()… 
13− 12 -  return "Hello… 13 +  content := st… 
14−      14 +  return content 
13+ 12 -  return "Hello… 13 +  content := st… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf050.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() … 12  func getContent() …
13− 12 -  return "Hello,… 13 +  content := str…
14−      14 +  return content 
13+ 12 -  return "Hello,… 13 +  content := str…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf051.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() … 12  func getContent() … 
13− 12 -  return "Hello,… 13 +  content := str… 
14−      14 +  return content 
13+ 12 -  return "Hello,… 13 +  content := str… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf052.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() s… 12  func getContent() s…
13− 12 -  return "Hello, … 13 +  content := stri…
14−      14 +  return content 
13+ 12 -  return "Hello, … 13 +  content := stri…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf053.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() s… 12  func getContent() s… 
13− 12 -  return "Hello, … 13 +  content := stri… 
14−      14 +  return content 
13+ 12 -  return "Hello, … 13 +  content := stri… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf054.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() st… 12  func getContent() st…
13− 12 -  return "Hello, w… 13 +  content := strin…
14−      14 +  return content 
13+ 12 -  return "Hello, w… 13 +  content := strin…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf055.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() st… 12  func getContent() st… 
13− 12 -  return "Hello, w… 13 +  content := strin… 
14−      14 +  return content 
13+ 12 -  return "Hello, w… 13 +  content := strin… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf056.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() str… 12  func getContent() str…
13− 12 -  return "Hello, wo… 13 +  content := string…
14−      14 +  return content 
13+ 12 -  return "Hello, wo… 13 +  content := string…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf057.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() str… 12  func getContent() str… 
13− 12 -  return "Hello, wo… 13 +  content := string… 
14−      14 +  return content 
13+ 12 -  return "Hello, wo… 13 +  content := string… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf058.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() stri… 12  func getContent() stri…
13− 12 -  return "Hello, wor… 13 +  content := strings…
14−      14 +  return content 
13+ 12 -  return "Hello, wor… 13 +  content := strings…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf059.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() stri… 12  func getContent() stri… 
13− 12 -  return "Hello, wor… 13 +  content := strings… 
14−      14 +  return content 
13+ 12 -  return "Hello, wor… 13 +  content := strings… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf060.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() strin… 12  func getContent() strin…
13− 12 -  return "Hello, worl… 13 +  content := strings.…
14−      14 +  return content 
13+ 12 -  return "Hello, worl… 13 +  content := strings.…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf061.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() strin… 12  func getContent() strin… 
13− 12 -  return "Hello, worl… 13 +  content := strings.… 
14−      14 +  return content 
13+ 12 -  return "Hello, worl… 13 +  content := strings.… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf062.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string… 12  func getContent() string…
13− 12 -  return "Hello, world… 13 +  content := strings.T…
14−      14 +  return content 
13+ 12 -  return "Hello, world… 13 +  content := strings.T…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf063.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string… 12  func getContent() string… 
13− 12 -  return "Hello, world… 13 +  content := strings.T… 
14−      14 +  return content 
13+ 12 -  return "Hello, world… 13 +  content := strings.T… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf064.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string { 12  func getContent() string {
13− 12 -  return "Hello, world!" 13 +  content := strings.To…
14−      14 +  return content 
13+ 12 -  return "Hello, world!" 13 +  content := strings.To…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf065.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string { 12  func getContent() string { 
13− 12 -  return "Hello, world!" 13 +  content := strings.To… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!" 13 +  content := strings.To… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf066.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToU…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToU…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf067.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToU… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToU… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf068.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUp…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUp…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf069.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUp… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUp… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf070.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpp…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpp…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf071.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpp… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpp… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf072.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUppe…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUppe…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf073.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUppe… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUppe… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf074.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf075.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf076.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper(…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper(…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf077.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper(… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper(… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf078.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf079.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf080.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("H…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("H…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf081.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("H… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("H… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf082.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("He…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("He…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf083.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("He… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("He… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf084.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hel…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hel…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf085.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hel… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hel… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf086.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hell…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hell…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf087.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hell… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hell… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf088.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf089.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf090.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello,…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello,…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf091.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello,… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello,… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf092.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, …
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, …
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf093.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, … 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, … 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf094.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, W…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, W…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf095.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, W… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, W… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf096.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Wo…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Wo…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf097.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Wo… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Wo… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf098.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Wor…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Wor…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf099.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Wor… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Wor… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf100.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Worl…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Worl…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf101.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Worl… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, Worl… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf102.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf103.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf104.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!…
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!…
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf105.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!… 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!… 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf106.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!")
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!")
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf107.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf108.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf109.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Split/WidthOf110.golden+3−3
@@ -2,7 +2,7 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−       5 +  "strings" 
5+       5 +  "strings" 
66  5  )   6  ) 
77  6     7   
88  7  func main() {   8  func main() { 
@@ -10,6 +10,6 @@
1010  9  }  10  } 
1111 10    11   
1212 11  func getContent() string {  12  func getContent() string { 
13− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14−      14 +  return content 
13+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
14+      14 +  return content 
1515 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf001.golden+4−4
@@ -2,7 +2,7 @@
22 
33 
44 
5− 
5+ 
66 
77 
88 
@@ -10,7 +10,7 @@
1010 
1111 
1212 
13− 
14− 
15− 
13+ 
14+ 
15+ 
1616 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf002.golden+4−4
@@ -2,7 +2,7 @@
22  
33  
44  
5−  
5+  
66  
77  
88  
@@ -10,7 +10,7 @@
1010  
1111 1
1212 1
13− 1
14−  
15−  
13+ 1
14+  
15+  
1616 1
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf003.golden+4−4
@@ -2,7 +2,7 @@
22  2
33  3
44  4
5−  
5+  
66  5
77  6
88  7
@@ -10,7 +10,7 @@
1010  9
1111 10
1212 11
13− 12
14−  
15−  
13+ 12
14+  
15+  
1616 13
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf004.golden+4−4
@@ -2,7 +2,7 @@
22  2 
33  3 
44  4 
5−   
5+   
66  5 
77  6 
88  7 
@@ -10,7 +10,7 @@
1010  9 
1111 10 
1212 11 
13− 12 
14−   
15−   
13+ 12 
14+   
15+   
1616 13 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf005.golden+4−4
@@ -2,7 +2,7 @@
22  2  
33  3  
44  4  
5−    
5+    
66  5  
77  6  
88  7  
@@ -10,7 +10,7 @@
1010  9  
1111 10  
1212 11  
13− 12  
14−    
15−    
13+ 12  
14+    
15+    
1616 13  
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf006.golden+4−4
@@ -2,7 +2,7 @@
22  2   
33  3   
44  4   
5−     
5+     
66  5   
77  6   
88  7   
@@ -10,7 +10,7 @@
1010  9  1
1111 10  1
1212 11  1
13− 12   
14−    1
15−    1
13+ 12   
14+    1
15+    1
1616 13  1
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf007.golden+4−4
@@ -2,7 +2,7 @@
22  2   2
33  3   3
44  4   4
5−     5
5+     5
66  5   6
77  6   7
88  7   8
@@ -10,7 +10,7 @@
1010  9  10
1111 10  11
1212 11  12
13− 12   
14−    13
15−    14
13+ 12   
14+    13
15+    14
1616 13  15
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf008.golden+4−4
@@ -2,7 +2,7 @@
22  2   2 
33  3   3 
44  4   4 
5−     5 
5+     5 
66  5   6 
77  6   7 
88  7   8 
@@ -10,7 +10,7 @@
1010  9  10 
1111 10  11 
1212 11  12 
13− 12    
14−    13 
15−    14 
13+ 12    
14+    13 
15+    14 
1616 13  15 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf009.golden+4−4
@@ -2,7 +2,7 @@
22  2   2  
33  3   3  
44  4   4  
5−     5 +
5+     5 +
66  5   6  
77  6   7  
88  7   8  
@@ -10,7 +10,7 @@
1010  9  10  
1111 10  11  
1212 11  12  
13− 12    -
14−    13 +
15−    14 +
13+ 12    -
14+    13 +
15+    14 +
1616 13  15  
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf010.golden+4−4
@@ -2,7 +2,7 @@
22  2   2  
33  3   3  
44  4   4  
5−     5 + 
5+     5 + 
66  5   6  
77  6   7  
88  7   8  
@@ -10,7 +10,7 @@
1010  9  10  
1111 10  11  
1212 11  12  
13− 12    - 
14−    13 + 
15−    14 + 
13+ 12    - 
14+    13 + 
15+    14 + 
1616 13  15  
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf011.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  …
44  4   4  …
5−     5 + …
5+     5 + …
66  5   6  )
77  6   7   
88  7   8  …
@@ -10,7 +10,7 @@
1010  9  10  }
1111 10  11   
1212 11  12  …
13− 12    - …
14−    13 + …
15−    14 + …
13+ 12    - …
14+    13 + …
15+    14 + …
1616 13  15  }
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf012.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  i…
44  4   4   …
5−     5 +  …
5+     5 +  …
66  5   6  ) 
77  6   7   
88  7   8  f…
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  f…
13− 12    -  …
14−    13 +  …
15−    14 +  …
13+ 12    -  …
14+    13 +  …
15+    14 +  …
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf013.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  im…
44  4   4   …
5−     5 +  …
5+     5 +  …
66  5   6  ) 
77  6   7   
88  7   8  fu…
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  fu…
13− 12    -  …
14−    13 +  …
15−    14 +  …
13+ 12    -  …
14+    13 +  …
15+    14 +  …
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf014.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  imp…
44  4   4   …
5−     5 +  …
5+     5 +  …
66  5   6  ) 
77  6   7   
88  7   8  fun…
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  fun…
13− 12    -  …
14−    13 +  …
15−    14 +  …
13+ 12    -  …
14+    13 +  …
15+    14 +  …
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf015.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  impo…
44  4   4   …
5−     5 +  …
5+     5 +  …
66  5   6  ) 
77  6   7   
88  7   8  func…
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func…
13− 12    -  …
14−    13 +  …
15−    14 +  …
13+ 12    -  …
14+    13 +  …
15+    14 +  …
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf016.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  impor…
44  4   4   "…
5−     5 +  "…
5+     5 +  "…
66  5   6  ) 
77  6   7   
88  7   8  func …
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func …
13− 12    -  r…
14−    13 +  c…
15−    14 +  r…
13+ 12    -  r…
14+    13 +  c…
15+    14 +  r…
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf017.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import…
44  4   4   "f…
5−     5 +  "s…
5+     5 +  "s…
66  5   6  ) 
77  6   7   
88  7   8  func m…
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func g…
13− 12    -  re…
14−    13 +  co…
15−    14 +  re…
13+ 12    -  re…
14+    13 +  co…
15+    14 +  re…
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf018.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import (
44  4   4   "fm…
5−     5 +  "st…
5+     5 +  "st…
66  5   6  ) 
77  6   7   
88  7   8  func ma…
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func ge…
13− 12    -  ret…
14−    13 +  con…
15−    14 +  ret…
13+ 12    -  ret…
14+    13 +  con…
15+    14 +  ret…
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf019.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt"
5−     5 +  "str…
5+     5 +  "str…
66  5   6  ) 
77  6   7   
88  7   8  func mai…
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func get…
13− 12    -  retu…
14−    13 +  cont…
15−    14 +  retu…
13+ 12    -  retu…
14+    13 +  cont…
15+    14 +  retu…
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf020.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "stri…
5+     5 +  "stri…
66  5   6  ) 
77  6   7   
88  7   8  func main…
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getC…
13− 12    -  retur…
14−    13 +  conte…
15−    14 +  retur…
13+ 12    -  retur…
14+    13 +  conte…
15+    14 +  retur…
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf021.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strin…
5+     5 +  "strin…
66  5   6  ) 
77  6   7   
88  7   8  func main(…
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getCo…
13− 12    -  return…
14−    13 +  conten…
15−    14 +  return…
13+ 12    -  return…
14+    13 +  conten…
15+    14 +  return…
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf022.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "string…
5+     5 +  "string…
66  5   6  ) 
77  6   7   
88  7   8  func main()…
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getCon…
13− 12    -  return …
14−    13 +  content…
15−    14 +  return …
13+ 12    -  return …
14+    13 +  content…
15+    14 +  return …
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf023.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings"
5+     5 +  "strings"
66  5   6  ) 
77  6   7   
88  7   8  func main() {
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getCont…
13− 12    -  return "…
14−    13 +  content …
15−    14 +  return c…
13+ 12    -  return "…
14+    13 +  content …
15+    14 +  return c…
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf024.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getConte…
13− 12    -  return "H…
14−    13 +  content :…
15−    14 +  return co…
13+ 12    -  return "H…
14+    13 +  content :…
15+    14 +  return co…
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf025.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getConten…
13− 12    -  return "He…
14−    13 +  content :=…
15−    14 +  return con…
13+ 12    -  return "He…
14+    13 +  content :=…
15+    14 +  return con…
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf026.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent…
13− 12    -  return "Hel…
14−    13 +  content := …
15−    14 +  return cont…
13+ 12    -  return "Hel…
14+    13 +  content := …
15+    14 +  return cont…
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf027.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent(…
13− 12    -  return "Hell…
14−    13 +  content := s…
15−    14 +  return conte…
13+ 12    -  return "Hell…
14+    13 +  content := s…
15+    14 +  return conte…
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf028.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent()…
13− 12    -  return "Hello…
14−    13 +  content := st…
15−    14 +  return content
13+ 12    -  return "Hello…
14+    13 +  content := st…
15+    14 +  return content
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf029.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() …
13− 12    -  return "Hello,…
14−    13 +  content := str…
15−    14 +  return content 
13+ 12    -  return "Hello,…
14+    13 +  content := str…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf030.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() s…
13− 12    -  return "Hello, …
14−    13 +  content := stri…
15−    14 +  return content 
13+ 12    -  return "Hello, …
14+    13 +  content := stri…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf031.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() st…
13− 12    -  return "Hello, w…
14−    13 +  content := strin…
15−    14 +  return content 
13+ 12    -  return "Hello, w…
14+    13 +  content := strin…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf032.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() str…
13− 12    -  return "Hello, wo…
14−    13 +  content := string…
15−    14 +  return content 
13+ 12    -  return "Hello, wo…
14+    13 +  content := string…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf033.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() stri…
13− 12    -  return "Hello, wor…
14−    13 +  content := strings…
15−    14 +  return content 
13+ 12    -  return "Hello, wor…
14+    13 +  content := strings…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf034.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() strin…
13− 12    -  return "Hello, worl…
14−    13 +  content := strings.…
15−    14 +  return content 
13+ 12    -  return "Hello, worl…
14+    13 +  content := strings.…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf035.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string…
13− 12    -  return "Hello, world…
14−    13 +  content := strings.T…
15−    14 +  return content 
13+ 12    -  return "Hello, world…
14+    13 +  content := strings.T…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf036.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string {
13− 12    -  return "Hello, world!"
14−    13 +  content := strings.To…
15−    14 +  return content 
13+ 12    -  return "Hello, world!"
14+    13 +  content := strings.To…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf037.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToU…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToU…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf038.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUp…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUp…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf039.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpp…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpp…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf040.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUppe…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUppe…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf041.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf042.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper(…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper(…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf043.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf044.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("H…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("H…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf045.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("He…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("He…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf046.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hel…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hel…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf047.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hell…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hell…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf048.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf049.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello,…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello,…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf050.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, …
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, …
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf051.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, W…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, W…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf052.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, Wo…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, Wo…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf053.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, Wor…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, Wor…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf054.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, Worl…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, Worl…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf055.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf056.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!…
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!…
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf057.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!")
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!")
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf058.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf059.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewWidth/Unified/WidthOf060.golden+4−4
@@ -2,7 +2,7 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−     5 +  "strings" 
5+     5 +  "strings" 
66  5   6  ) 
77  6   7   
88  7   8  func main() { 
@@ -10,7 +10,7 @@
1010  9  10  } 
1111 10  11   
1212 11  12  func getContent() string { 
13− 12    -  return "Hello, world!" 
14−    13 +  content := strings.ToUpper("Hello, World!") 
15−    14 +  return content 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!") 
15+    14 +  return content 
1616 13  15  } 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf00.golden+2−2
@@ -2,6 +2,6 @@
22  5  )   5  ) 
33  6     6   
44  7  func main() {   7  func main() { 
5−  8 -  fmt.Println("Hello,…  8 +  content := "Hello, …
6−       9 +  fmt.Println(content)
5+  8 -  fmt.Println("Hello,…  8 +  content := "Hello, …
6+       9 +  fmt.Println(content)
77  9  }  10  } 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf01.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …unc main() {   7  …unc main() { 
5−  8 -… fmt.Println("Hello, …  8 +… content := "Hello, w…
6−       9 +… fmt.Println(content) 
5+  8 -… fmt.Println("Hello, …  8 +… content := "Hello, w…
6+       9 +… fmt.Println(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf02.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …nc main() {   7  …nc main() { 
5−  8 -… fmt.Println("Hello, w…  8 +… content := "Hello, wo…
6−       9 +… fmt.Println(content) 
5+  8 -… fmt.Println("Hello, w…  8 +… content := "Hello, wo…
6+       9 +… fmt.Println(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf03.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …c main() {   7  …c main() { 
5−  8 -… fmt.Println("Hello, wo…  8 +… content := "Hello, wor…
6−       9 +… fmt.Println(content) 
5+  8 -… fmt.Println("Hello, wo…  8 +… content := "Hello, wor…
6+       9 +… fmt.Println(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf04.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  … main() {   7  … main() { 
5−  8 -…fmt.Println("Hello, wor…  8 +…content := "Hello, worl…
6−       9 +…fmt.Println(content) 
5+  8 -…fmt.Println("Hello, wor…  8 +…content := "Hello, worl…
6+       9 +…fmt.Println(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf05.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …main() {   7  …main() { 
5−  8 -…mt.Println("Hello, worl…  8 +…ontent := "Hello, world…
6−       9 +…mt.Println(content) 
5+  8 -…mt.Println("Hello, worl…  8 +…ontent := "Hello, world…
6+       9 +…mt.Println(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf06.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …ain() {   7  …ain() { 
5−  8 -…t.Println("Hello, world…  8 +…ntent := "Hello, world!"
6−       9 +…t.Println(content) 
5+  8 -…t.Println("Hello, world…  8 +…ntent := "Hello, world!"
6+       9 +…t.Println(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf07.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …in() {   7  …in() { 
5−  8 -….Println("Hello, world!…  8 +…tent := "Hello, world!" 
6−       9 +….Println(content) 
5+  8 -….Println("Hello, world!…  8 +…tent := "Hello, world!" 
6+       9 +….Println(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf08.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …n() {   7  …n() { 
5−  8 -…Println("Hello, world!")  8 +…ent := "Hello, world!" 
6−       9 +…Println(content) 
5+  8 -…Println("Hello, world!")  8 +…ent := "Hello, world!" 
6+       9 +…Println(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf09.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …() {   7  …() { 
5−  8 -…rintln("Hello, world!")   8 +…nt := "Hello, world!" 
6−       9 +…rintln(content) 
5+  8 -…rintln("Hello, world!")   8 +…nt := "Hello, world!" 
6+       9 +…rintln(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf10.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …) {   7  …) { 
5−  8 -…intln("Hello, world!")   8 +…t := "Hello, world!" 
6−       9 +…intln(content) 
5+  8 -…intln("Hello, world!")   8 +…t := "Hello, world!" 
6+       9 +…intln(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf11.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  … {   7  … { 
5−  8 -…ntln("Hello, world!")   8 +… := "Hello, world!" 
6−       9 +…ntln(content) 
5+  8 -…ntln("Hello, world!")   8 +… := "Hello, world!" 
6+       9 +…ntln(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf12.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …{   7  …{ 
5−  8 -…tln("Hello, world!")   8 +…:= "Hello, world!" 
6−       9 +…tln(content) 
5+  8 -…tln("Hello, world!")   8 +…:= "Hello, world!" 
6+       9 +…tln(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf13.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …   7  … 
5−  8 -…ln("Hello, world!")   8 +…= "Hello, world!" 
6−       9 +…ln(content) 
5+  8 -…ln("Hello, world!")   8 +…= "Hello, world!" 
6+       9 +…ln(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf14.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …   7  … 
5−  8 -…n("Hello, world!")   8 +… "Hello, world!" 
6−       9 +…n(content) 
5+  8 -…n("Hello, world!")   8 +… "Hello, world!" 
6+       9 +…n(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf15.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …   7  … 
5−  8 -…("Hello, world!")   8 +…"Hello, world!" 
6−       9 +…(content) 
5+  8 -…("Hello, world!")   8 +…"Hello, world!" 
6+       9 +…(content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf16.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …   7  … 
5−  8 -…"Hello, world!")   8 +…Hello, world!" 
6−       9 +…content) 
5+  8 -…"Hello, world!")   8 +…Hello, world!" 
6+       9 +…content) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf17.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …   7  … 
5−  8 -…Hello, world!")   8 +…ello, world!" 
6−       9 +…ontent) 
5+  8 -…Hello, world!")   8 +…ello, world!" 
6+       9 +…ontent) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf18.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …   7  … 
5−  8 -…ello, world!")   8 +…llo, world!" 
6−       9 +…ntent) 
5+  8 -…ello, world!")   8 +…llo, world!" 
6+       9 +…ntent) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf19.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …   7  … 
5−  8 -…llo, world!")   8 +…lo, world!" 
6−       9 +…tent) 
5+  8 -…llo, world!")   8 +…lo, world!" 
6+       9 +…tent) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf20.golden+2−2
@@ -2,6 +2,6 @@
22  5  …   5  … 
33  6     6   
44  7  …   7  … 
5−  8 -…lo, world!")   8 +…o, world!" 
6−       9 +…ent) 
5+  8 -…lo, world!")   8 +…o, world!" 
6+       9 +…ent) 
77  9  …  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf00.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  ) 
33  6   6   
44  7   7  func main() { 
5−  8    -  fmt.Println("Hello, world!") 
6−     8 +  content := "Hello, world!" 
7−     9 +  fmt.Println(content) 
5+  8    -  fmt.Println("Hello, world!") 
6+     8 +  content := "Hello, world!" 
7+     9 +  fmt.Println(content) 
88  9  10  } 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf01.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  …unc main() { 
5−  8    -… fmt.Println("Hello, world!") 
6−     8 +… content := "Hello, world!" 
7−     9 +… fmt.Println(content) 
5+  8    -… fmt.Println("Hello, world!") 
6+     8 +… content := "Hello, world!" 
7+     9 +… fmt.Println(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf02.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  …nc main() { 
5−  8    -… fmt.Println("Hello, world!") 
6−     8 +… content := "Hello, world!" 
7−     9 +… fmt.Println(content) 
5+  8    -… fmt.Println("Hello, world!") 
6+     8 +… content := "Hello, world!" 
7+     9 +… fmt.Println(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf03.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  …c main() { 
5−  8    -… fmt.Println("Hello, world!") 
6−     8 +… content := "Hello, world!" 
7−     9 +… fmt.Println(content) 
5+  8    -… fmt.Println("Hello, world!") 
6+     8 +… content := "Hello, world!" 
7+     9 +… fmt.Println(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf04.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  … main() { 
5−  8    -…fmt.Println("Hello, world!") 
6−     8 +…content := "Hello, world!" 
7−     9 +…fmt.Println(content) 
5+  8    -…fmt.Println("Hello, world!") 
6+     8 +…content := "Hello, world!" 
7+     9 +…fmt.Println(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf05.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  …main() { 
5−  8    -…mt.Println("Hello, world!") 
6−     8 +…ontent := "Hello, world!" 
7−     9 +…mt.Println(content) 
5+  8    -…mt.Println("Hello, world!") 
6+     8 +…ontent := "Hello, world!" 
7+     9 +…mt.Println(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf06.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  …ain() { 
5−  8    -…t.Println("Hello, world!") 
6−     8 +…ntent := "Hello, world!" 
7−     9 +…t.Println(content) 
5+  8    -…t.Println("Hello, world!") 
6+     8 +…ntent := "Hello, world!" 
7+     9 +…t.Println(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf07.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  …in() { 
5−  8    -….Println("Hello, world!") 
6−     8 +…tent := "Hello, world!" 
7−     9 +….Println(content) 
5+  8    -….Println("Hello, world!") 
6+     8 +…tent := "Hello, world!" 
7+     9 +….Println(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf08.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  …n() { 
5−  8    -…Println("Hello, world!") 
6−     8 +…ent := "Hello, world!" 
7−     9 +…Println(content) 
5+  8    -…Println("Hello, world!") 
6+     8 +…ent := "Hello, world!" 
7+     9 +…Println(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf09.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  …() { 
5−  8    -…rintln("Hello, world!") 
6−     8 +…nt := "Hello, world!" 
7−     9 +…rintln(content) 
5+  8    -…rintln("Hello, world!") 
6+     8 +…nt := "Hello, world!" 
7+     9 +…rintln(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf10.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  …) { 
5−  8    -…intln("Hello, world!") 
6−     8 +…t := "Hello, world!" 
7−     9 +…intln(content) 
5+  8    -…intln("Hello, world!") 
6+     8 +…t := "Hello, world!" 
7+     9 +…intln(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf11.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  … { 
5−  8    -…ntln("Hello, world!") 
6−     8 +… := "Hello, world!" 
7−     9 +…ntln(content) 
5+  8    -…ntln("Hello, world!") 
6+     8 +… := "Hello, world!" 
7+     9 +…ntln(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf12.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  …{ 
5−  8    -…tln("Hello, world!") 
6−     8 +…:= "Hello, world!" 
7−     9 +…tln(content) 
5+  8    -…tln("Hello, world!") 
6+     8 +…:= "Hello, world!" 
7+     9 +…tln(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf13.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  … 
5−  8    -…ln("Hello, world!") 
6−     8 +…= "Hello, world!" 
7−     9 +…ln(content) 
5+  8    -…ln("Hello, world!") 
6+     8 +…= "Hello, world!" 
7+     9 +…ln(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf14.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  … 
5−  8    -…n("Hello, world!") 
6−     8 +… "Hello, world!" 
7−     9 +…n(content) 
5+  8    -…n("Hello, world!") 
6+     8 +… "Hello, world!" 
7+     9 +…n(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf15.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  … 
5−  8    -…("Hello, world!") 
6−     8 +…"Hello, world!" 
7−     9 +…(content) 
5+  8    -…("Hello, world!") 
6+     8 +…"Hello, world!" 
7+     9 +…(content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf16.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  … 
5−  8    -…"Hello, world!") 
6−     8 +…Hello, world!" 
7−     9 +…content) 
5+  8    -…"Hello, world!") 
6+     8 +…Hello, world!" 
7+     9 +…content) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf17.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  … 
5−  8    -…Hello, world!") 
6−     8 +…ello, world!" 
7−     9 +…ontent) 
5+  8    -…Hello, world!") 
6+     8 +…ello, world!" 
7+     9 +…ontent) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf18.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  … 
5−  8    -…ello, world!") 
6−     8 +…llo, world!" 
7−     9 +…ntent) 
5+  8    -…ello, world!") 
6+     8 +…llo, world!" 
7+     9 +…ntent) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf19.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  … 
5−  8    -…llo, world!") 
6−     8 +…lo, world!" 
7−     9 +…tent) 
5+  8    -…llo, world!") 
6+     8 +…lo, world!" 
7+     9 +…tent) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf20.golden+3−3
@@ -2,7 +2,7 @@
22  5   5  … 
33  6   6   
44  7   7  … 
5−  8    -…lo, world!") 
6−     8 +…o, world!" 
7−     9 +…ent) 
5+  8    -…lo, world!") 
6+     8 +…o, world!" 
7+     9 +…ent) 
88  9  10  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf00.golden+1−1
@@ -2,4 +2,4 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−  …  …   …  … 
5+  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf01.golden+1−1
@@ -1,5 +1,5 @@
11  2     2   
22  3  import (   3  import ( 
33  4   "fmt"   4   "fmt" 
4−       5 +  "strings" 
4+       5 +  "strings" 
55  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf02.golden+1−1
@@ -1,5 +1,5 @@
11  3  import (   3  import ( 
22  4   "fmt"   4   "fmt" 
3−       5 +  "strings" 
3+       5 +  "strings" 
44  5  )   6  ) 
55  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf03.golden+1−1
@@ -1,5 +1,5 @@
11  4   "fmt"   4   "fmt" 
2−       5 +  "strings" 
2+       5 +  "strings" 
33  5  )   6  ) 
44  6     7   
55  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf04.golden+1−1
@@ -1,4 +1,4 @@
1−       5 +  "strings" 
1+       5 +  "strings" 
22  5  )   6  ) 
33  6     7   
44  7  func main() {   8  func main() { 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf08.golden+1−1
@@ -2,4 +2,4 @@
22  9  }  10  } 
33 10    11   
44 11  func getContent() string {  12  func getContent() string { 
5−  …  …   …  … 
5+  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf09.golden+2−2
@@ -1,5 +1,5 @@
11  9  }  10  } 
22 10    11   
33 11  func getContent() string {  12  func getContent() string { 
4− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
5−  …  …   …  … 
4+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
5+  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf10.golden+2−2
@@ -1,5 +1,5 @@
11 10    11   
22 11  func getContent() string {  12  func getContent() string { 
3− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4−      14 +  return content 
3+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4+      14 +  return content 
55 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf11.golden+2−2
@@ -1,5 +1,5 @@
11 10    11   
22 11  func getContent() string {  12  func getContent() string { 
3− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4−      14 +  return content 
3+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4+      14 +  return content 
55 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf12.golden+2−2
@@ -1,5 +1,5 @@
11 10    11   
22 11  func getContent() string {  12  func getContent() string { 
3− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4−      14 +  return content 
3+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4+      14 +  return content 
55 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf13.golden+2−2
@@ -1,5 +1,5 @@
11 10    11   
22 11  func getContent() string {  12  func getContent() string { 
3− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4−      14 +  return content 
3+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4+      14 +  return content 
55 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf14.golden+2−2
@@ -1,5 +1,5 @@
11 10    11   
22 11  func getContent() string {  12  func getContent() string { 
3− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4−      14 +  return content 
3+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4+      14 +  return content 
55 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf15.golden+2−2
@@ -1,5 +1,5 @@
11 10    11   
22 11  func getContent() string {  12  func getContent() string { 
3− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4−      14 +  return content 
3+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4+      14 +  return content 
55 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf16.golden+2−2
@@ -1,5 +1,5 @@
11 10    11   
22 11  func getContent() string {  12  func getContent() string { 
3− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4−      14 +  return content 
3+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4+      14 +  return content 
55 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf00.golden+1−1
@@ -2,4 +2,4 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−  …   …  … 
5+  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf01.golden+1−1
@@ -1,5 +1,5 @@
11  2   2   
22  3   3  import ( 
33  4   4   "fmt" 
4−     5 +  "strings" 
4+     5 +  "strings" 
55  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf02.golden+1−1
@@ -1,5 +1,5 @@
11  3   3  import ( 
22  4   4   "fmt" 
3−     5 +  "strings" 
3+     5 +  "strings" 
44  5   6  ) 
55  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf03.golden+1−1
@@ -1,5 +1,5 @@
11  4   4   "fmt" 
2−     5 +  "strings" 
2+     5 +  "strings" 
33  5   6  ) 
44  6   7   
55  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf04.golden+1−1
@@ -1,4 +1,4 @@
1−     5 +  "strings" 
1+     5 +  "strings" 
22  5   6  ) 
33  6   7   
44  7   8  func main() { 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf08.golden+1−1
@@ -2,4 +2,4 @@
22  9  10  } 
33 10  11   
44 11  12  func getContent() string { 
5−  …   …  … 
5+  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf09.golden+2−2
@@ -1,5 +1,5 @@
11  9  10  } 
22 10  11   
33 11  12  func getContent() string { 
4− 12    -  return "Hello, world!" 
5−  …   …  … 
4+ 12    -  return "Hello, world!" 
5+  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf10.golden+3−3
@@ -1,5 +1,5 @@
11 10  11   
22 11  12  func getContent() string { 
3− 12    -  return "Hello, world!" 
4−    13 +  content := strings.ToUpper("Hello, World!") 
5−  …   …  … 
3+ 12    -  return "Hello, world!" 
4+    13 +  content := strings.ToUpper("Hello, World!") 
5+  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf11.golden+3−3
@@ -1,5 +1,5 @@
11 11  12  func getContent() string { 
2− 12    -  return "Hello, world!" 
3−    13 +  content := strings.ToUpper("Hello, World!") 
4−    14 +  return content 
2+ 12    -  return "Hello, world!" 
3+    13 +  content := strings.ToUpper("Hello, World!") 
4+    14 +  return content 
55 13  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf12.golden+3−3
@@ -1,5 +1,5 @@
11 11  12  func getContent() string { 
2− 12    -  return "Hello, world!" 
3−    13 +  content := strings.ToUpper("Hello, World!") 
4−    14 +  return content 
2+ 12    -  return "Hello, world!" 
3+    13 +  content := strings.ToUpper("Hello, World!") 
4+    14 +  return content 
55 13  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf13.golden+3−3
@@ -1,5 +1,5 @@
11 11  12  func getContent() string { 
2− 12    -  return "Hello, world!" 
3−    13 +  content := strings.ToUpper("Hello, World!") 
4−    14 +  return content 
2+ 12    -  return "Hello, world!" 
3+    13 +  content := strings.ToUpper("Hello, World!") 
4+    14 +  return content 
55 13  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf14.golden+3−3
@@ -1,5 +1,5 @@
11 11  12  func getContent() string { 
2− 12    -  return "Hello, world!" 
3−    13 +  content := strings.ToUpper("Hello, World!") 
4−    14 +  return content 
2+ 12    -  return "Hello, world!" 
3+    13 +  content := strings.ToUpper("Hello, World!") 
4+    14 +  return content 
55 13  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf15.golden+3−3
@@ -1,5 +1,5 @@
11 11  12  func getContent() string { 
2− 12    -  return "Hello, world!" 
3−    13 +  content := strings.ToUpper("Hello, World!") 
4−    14 +  return content 
2+ 12    -  return "Hello, world!" 
3+    13 +  content := strings.ToUpper("Hello, World!") 
4+    14 +  return content 
55 13  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf16.golden+3−3
@@ -1,5 +1,5 @@
11 11  12  func getContent() string { 
2− 12    -  return "Hello, world!" 
3−    13 +  content := strings.ToUpper("Hello, World!") 
4−    14 +  return content 
2+ 12    -  return "Hello, world!" 
3+    13 +  content := strings.ToUpper("Hello, World!") 
4+    14 +  return content 
55 13  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf00.golden+1−1
@@ -2,4 +2,4 @@
22  2     2   
33  3  import (   3  import ( 
44  4   "fmt"   4   "fmt" 
5−  …  …   …  … 
5+  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf01.golden+1−1
@@ -1,5 +1,5 @@
11  2     2   
22  3  import (   3  import ( 
33  4   "fmt"   4   "fmt" 
4−       5 +  "strings" 
4+       5 +  "strings" 
55  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf02.golden+1−1
@@ -1,5 +1,5 @@
11  3  import (   3  import ( 
22  4   "fmt"   4   "fmt" 
3−       5 +  "strings" 
3+       5 +  "strings" 
44  5  )   6  ) 
55  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf03.golden+1−1
@@ -1,5 +1,5 @@
11  4   "fmt"   4   "fmt" 
2−       5 +  "strings" 
2+       5 +  "strings" 
33  5  )   6  ) 
44  6     7   
55  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf04.golden+1−1
@@ -1,4 +1,4 @@
1−       5 +  "strings" 
1+       5 +  "strings" 
22  5  )   6  ) 
33  6     7   
44  7  func main() {   8  func main() { 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf08.golden+1−1
@@ -2,4 +2,4 @@
22  9  }  10  } 
33 10    11   
44 11  func getContent() string {  12  func getContent() string { 
5−  …  …   …  … 
5+  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf09.golden+2−2
@@ -1,5 +1,5 @@
11  9  }  10  } 
22 10    11   
33 11  func getContent() string {  12  func getContent() string { 
4− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
5−  …  …   …  … 
4+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
5+  …  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf10.golden+2−2
@@ -1,5 +1,5 @@
11 10    11   
22 11  func getContent() string {  12  func getContent() string { 
3− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4−      14 +  return content 
3+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
4+      14 +  return content 
55 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf11.golden+2−2
@@ -1,4 +1,4 @@
11 11  func getContent() string {  12  func getContent() string { 
2− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
3−      14 +  return content 
2+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
3+      14 +  return content 
44 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf12.golden+2−2
@@ -1,3 +1,3 @@
1− 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
2−      14 +  return content 
1+ 12 -  return "Hello, world!"  13 +  content := strings.ToUpper("Hello, World!") 
2+      14 +  return content 
33 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf13.golden+1−1
@@ -1,2 +1,2 @@
1−      14 +  return content 
1+      14 +  return content 
22 13  }  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf00.golden+1−1
@@ -2,4 +2,4 @@
22  2   2   
33  3   3  import ( 
44  4   4   "fmt" 
5−  …   …  … 
5+  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf01.golden+1−1
@@ -1,5 +1,5 @@
11  2   2   
22  3   3  import ( 
33  4   4   "fmt" 
4−     5 +  "strings" 
4+     5 +  "strings" 
55  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf02.golden+1−1
@@ -1,5 +1,5 @@
11  3   3  import ( 
22  4   4   "fmt" 
3−     5 +  "strings" 
3+     5 +  "strings" 
44  5   6  ) 
55  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf03.golden+1−1
@@ -1,5 +1,5 @@
11  4   4   "fmt" 
2−     5 +  "strings" 
2+     5 +  "strings" 
33  5   6  ) 
44  6   7   
55  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf04.golden+1−1
@@ -1,4 +1,4 @@
1−     5 +  "strings" 
1+     5 +  "strings" 
22  5   6  ) 
33  6   7   
44  7   8  func main() { 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf08.golden+1−1
@@ -2,4 +2,4 @@
22  9  10  } 
33 10  11   
44 11  12  func getContent() string { 
5−  …   …  … 
5+  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf09.golden+2−2
@@ -1,5 +1,5 @@
11  9  10  } 
22 10  11   
33 11  12  func getContent() string { 
4− 12    -  return "Hello, world!" 
5−  …   …  … 
4+ 12    -  return "Hello, world!" 
5+  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf10.golden+3−3
@@ -1,5 +1,5 @@
11 10  11   
22 11  12  func getContent() string { 
3− 12    -  return "Hello, world!" 
4−    13 +  content := strings.ToUpper("Hello, World!") 
5−  …   …  … 
3+ 12    -  return "Hello, world!" 
4+    13 +  content := strings.ToUpper("Hello, World!") 
5+  …   …  … 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf11.golden+3−3
@@ -1,5 +1,5 @@
11 11  12  func getContent() string { 
2− 12    -  return "Hello, world!" 
3−    13 +  content := strings.ToUpper("Hello, World!") 
4−    14 +  return content 
2+ 12    -  return "Hello, world!" 
3+    13 +  content := strings.ToUpper("Hello, World!") 
4+    14 +  return content 
55 13  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf12.golden+3−3
@@ -1,4 +1,4 @@
1− 12    -  return "Hello, world!" 
2−    13 +  content := strings.ToUpper("Hello, World!") 
3−    14 +  return content 
1+ 12    -  return "Hello, world!" 
2+    13 +  content := strings.ToUpper("Hello, World!") 
3+    14 +  return content 
44 13  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf13.golden+2−2
@@ -1,3 +1,3 @@
1−    13 +  content := strings.ToUpper("Hello, World!") 
2−    14 +  return content 
1+    13 +  content := strings.ToUpper("Hello, World!") 
2+    14 +  return content 
33 13  15  } 
internal/ui/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf14.golden+1−1
@@ -1,2 +1,2 @@
1−    14 +  return content 
1+    14 +  return content 
22 13  15  } 
internal/ui/diffview/util.go+1−8
@@ -2,20 +2,13 @@ package diffview
22
33import (
44 "fmt"
5− "strconv"
65 "strings"
76
87 "github.com/charmbracelet/x/ansi"
98)
109
1110func pad(v any, width int) string {
12− var s string
13− switch n := v.(type) {
14− case int:
15− s = strconv.Itoa(n)
16− default:
17− s = fmt.Sprintf("%v", v)
18− }
11+ s := fmt.Sprintf("%v", v)
1912 w := ansi.StringWidth(s)
2013 if w >= width {
2114 return s
internal/ui/list/filterable.go+1−33
@@ -23,14 +23,6 @@ type FilterableList struct {
2323 *List
2424 items []FilterableItem
2525 query string
26− // dirty tracks whether the filtered set needs to be recomputed. It is set
27− // by any mutation of items or query and cleared after Render recomputes.
28− dirty bool
29−
30− // emptyMessage is shown when there are no items and no active filter.
31− emptyMessage string
32− // filterEmptyMessage is shown when a filter is active but matches nothing.
33− filterEmptyMessage string
3426}
3527
3628// NewFilterableList creates a new filterable list.
@@ -47,7 +39,6 @@ func NewFilterableList(items ...FilterableItem) *FilterableList {
4739// SetItems sets the list items and updates the filtered items.
4840func (f *FilterableList) SetItems(items ...FilterableItem) {
4941 f.items = items
50− f.dirty = true
5142 fitems := make([]Item, len(items))
5243 for i, item := range items {
5344 fitems[i] = item
@@ -58,7 +49,6 @@ func (f *FilterableList) SetItems(items ...FilterableItem) {
5849// AppendItems appends items to the list and updates the filtered items.
5950func (f *FilterableList) AppendItems(items ...FilterableItem) {
6051 f.items = append(f.items, items...)
61− f.dirty = true
6252 itms := make([]Item, len(f.items))
6353 for i, item := range f.items {
6454 itms[i] = item
@@ -69,7 +59,6 @@ func (f *FilterableList) AppendItems(items ...FilterableItem) {
6959// PrependItems prepends items to the list and updates the filtered items.
7060func (f *FilterableList) PrependItems(items ...FilterableItem) {
7161 f.items = append(items, f.items...)
72− f.dirty = true
7362 itms := make([]Item, len(f.items))
7463 for i, item := range f.items {
7564 itms[i] = item
@@ -80,7 +69,6 @@ func (f *FilterableList) PrependItems(items ...FilterableItem) {
8069// SetFilter sets the filter query and updates the list items.
8170func (f *FilterableList) SetFilter(q string) {
8271 f.query = q
83− f.dirty = true
8472 f.List.SetItems(f.FilteredItems()...)
8573 f.ScrollToTop()
8674}
@@ -130,28 +118,8 @@ func (f *FilterableList) FilteredItems() []Item {
130118 return matchedItems
131119}
132120
133−// SetEmptyMessage sets the placeholder shown when the list has no items and
134−// no filter is active.
135−func (f *FilterableList) SetEmptyMessage(msg string) {
136− f.emptyMessage = msg
137−}
138−
139−// SetFilterEmptyMessage sets the placeholder shown when a filter is active but
140−// yields no matches.
141−func (f *FilterableList) SetFilterEmptyMessage(msg string) {
142− f.filterEmptyMessage = msg
143−}
144−
145121// Render renders the filterable list.
146122func (f *FilterableList) Render() string {
147− if f.dirty {
148− f.List.SetItems(f.FilteredItems()...)
149− f.dirty = false
150− }
151− if f.query != "" && f.filterEmptyMessage != "" {
152− f.List.SetEmptyMessage(f.filterEmptyMessage)
153− } else {
154− f.List.SetEmptyMessage(f.emptyMessage)
155− }
123+ f.List.SetItems(f.FilteredItems()...)
156124 return f.List.Render()
157125}
internal/ui/list/highlight.go+1−2
@@ -136,10 +136,9 @@ func HighlightBuffer(content string, area image.Rectangle, startLine, startCol,
136136
137137// ToHighlighter converts a [lipgloss.Style] to a [Highlighter].
138138func ToHighlighter(lgStyle lipgloss.Style) Highlighter {
139− uvStyle := ToStyle(lgStyle)
140139 return func(_ int, _ int, c *uv.Cell) *uv.Cell {
141140 if c != nil {
142− c.Style = uvStyle
141+ c.Style = ToStyle(lgStyle)
143142 }
144143 return c
145144 }
internal/ui/list/list.go+4−23
@@ -2,8 +2,6 @@ package list
22
33import (
44 "strings"
5−
6− "charm.land/lipgloss/v2"
75)
86
97// List represents a list of items that can be lazily rendered. A list is
@@ -52,11 +50,6 @@ type List struct {
5250 // without calling back into Render.
5351 cache map[Item]*listCacheEntry
5452
55− // emptyMessage is a placeholder rendered centered (and dimmed) when
56− // the list has zero items, so an empty or fully-filtered list is not
57− // shown as a blank void. Empty by default (renders nothing).
58− emptyMessage string
59−
6053 // freezeSuppressed marks items the list must not freeze on the
6154 // next render even when their Finished() reports true. This is
6255 // the §4.5.1 selection-drag escape hatch (option (a)): items
@@ -116,12 +109,6 @@ func (l *List) SetSize(width, height int) {
116109 l.height = height
117110}
118111
119−// SetEmptyMessage sets a placeholder message rendered centered and dimmed
120−// when the list has no items. An empty string disables the placeholder.
121−func (l *List) SetEmptyMessage(msg string) {
122− l.emptyMessage = msg
123−}
124−
125112// SetGap sets the gap between items.
126113func (l *List) SetGap(gap int) {
127114 l.gap = gap
@@ -334,7 +321,9 @@ func (l *List) renderItemEntry(idx int) *listCacheEntry {
334321
335322// invalidateAll drops every cache entry. Called on width changes.
336323func (l *List) invalidateAll() {
337− l.cache = make(map[Item]*listCacheEntry)
324+ for k := range l.cache {
325+ delete(l.cache, k)
326+ }
338327 l.totalHeightValid = false
339328}
340329
@@ -527,15 +516,7 @@ func (l *List) VisibleItemIndices() (startIdx, endIdx int) {
527516// lines we now drop implicitly per item).
528517func (l *List) Render() string {
529518 if len(l.items) == 0 {
530− if l.emptyMessage == "" {
531− return ""
532− }
533− msg := lipgloss.NewStyle().Faint(true).Render(l.emptyMessage)
534− return lipgloss.Place(
535− max(l.width, 0), max(l.height, 0),
536− lipgloss.Center, lipgloss.Center,
537− msg,
538− )
519+ return ""
539520 }
540521
541522 budget := max(l.height, 0)
internal/ui/model/chat.go+33−150
@@ -1,13 +1,11 @@
11package model
22
33import (
4− "image"
54 "strings"
65 "time"
76
87 tea "charm.land/bubbletea/v2"
98 "charm.land/lipgloss/v2"
10− "github.com/charmbracelet/crush/internal/config"
119 "github.com/charmbracelet/crush/internal/ui/anim"
1210 "github.com/charmbracelet/crush/internal/ui/chat"
1311 "github.com/charmbracelet/crush/internal/ui/common"
@@ -32,18 +30,6 @@ type DelayedClickMsg struct {
3230 X, Y int
3331}
3432
35−// scrollbarHideMsg is sent to hide the scrollbar after the timeout period.
36−type scrollbarHideMsg struct {
37− seq int // sequence number to ignore stale messages
38−}
39−
40−// scrollbarHideCmd returns a command that sends a scrollbarHideMsg after the timeout.
41−func scrollbarHideCmd(seq int) tea.Cmd {
42− return tea.Tick(scrollbarHideDuration, func(_ time.Time) tea.Msg {
43− return scrollbarHideMsg{seq: seq}
44− })
45−}
46−
4733// Chat represents the chat UI model that handles chat interactions and
4834// messages.
4935type Chat struct {
@@ -84,16 +70,8 @@ type Chat struct {
8470 // (docs/notes/2026-05-12-chat-rendering-perf.md §4.8). Bounded to one
8571 // entry; invalidated implicitly by string inequality on the next Draw.
8672 drawCache *chatDrawCache
87−
88− // Scrollbar visibility state
89− scrollbarVisible bool
90− scrollbarHideSeq int // current sequence number for hide timer
91− scrollbarMode string // "default", "always", or "never"
9273}
9374
94−// scrollbarHideDuration is how long the scrollbar remains visible after scroll activity.
95−const scrollbarHideDuration = 2 * time.Second
96−
9775// chatDrawCache holds the pre-decoded form of the last list.Render output.
9876// The cache is keyed by the rendered string and the screen's width method
9977// (graphemes vs wcwidth pick different decoders inside ultraviolet's
@@ -115,12 +93,11 @@ type chatDrawCache struct {
11593
11694// NewChat creates a new instance of [Chat] that handles chat interactions and
11795// messages.
118−func NewChat(com *common.Common, scrollbarMode string) *Chat {
96+func NewChat(com *common.Common) *Chat {
11997 c := &Chat{
12098 com: com,
12199 idInxMap: make(map[string]int),
122100 pausedAnimations: make(map[string]struct{}),
123− scrollbarMode: scrollbarMode,
124101 }
125102 l := list.NewList()
126103 l.SetGap(1)
@@ -145,61 +122,21 @@ func (m *Chat) Height() int {
145122// rendered string and the screen's width method; area / scroll changes do not
146123// invalidate it.
147124func (m *Chat) Draw(scr uv.Screen, area uv.Rectangle) {
148− // Check if scrollbar should be visible.
149− listHeight := m.list.Height() - 1
150− listTotalHeight := m.list.TotalHeight() - 1
151− needsScrollbar := listTotalHeight > listHeight
152−
153− // Determine visibility based on scrollbar mode.
154− showScrollbar := false
155− switch m.scrollbarMode {
156− case config.ScrollbarAlways:
157− showScrollbar = needsScrollbar
158− case config.ScrollbarDefault:
159− showScrollbar = needsScrollbar && m.scrollbarVisible
160− case config.ScrollbarNever:
161− showScrollbar = false
162− }
163−
164− // Reserve space for scrollbar only when visible.
165− scrollbarWidth := 0
166− if showScrollbar {
167− scrollbarWidth = 1
168− }
169−
170− // Adjust list width to reserve space for scrollbar.
171− listArea := area
172− if scrollbarWidth > 0 {
173− listArea.Max.X -= scrollbarWidth
174− }
175−
176125 rendered := m.list.Render()
177126 method, ok := scr.WidthMethod().(ansi.Method)
178127 if !ok {
179128 // Width method isn't an ansi.Method (unlikely in practice — both
180129 // TerminalScreen and ScreenBuffer store ansi.Method). Fall back
181130 // to the uncached path so behavior matches upstream exactly.
182− uv.NewStyledString(rendered).Draw(scr, listArea)
183− } else {
184− if m.drawCache == nil ||
185− m.drawCache.rendered != rendered ||
186− m.drawCache.method != method {
187− m.drawCache = newChatDrawCache(rendered, method)
188− }
189− drawCachedBuffer(scr, listArea, m.drawCache.buf)
131+ uv.NewStyledString(rendered).Draw(scr, area)
132+ return
190133 }
191−
192− // Draw scrollbar if visible and needed.
193− if scrollbarWidth > 0 {
194− scrollbar := common.Scrollbar(m.com.Styles, listHeight, listTotalHeight, listHeight, m.list.Offset())
195− if scrollbar != "" {
196− scrollbarArea := image.Rectangle{
197− Min: image.Point{X: area.Max.X - scrollbarWidth, Y: area.Min.Y},
198− Max: image.Point{X: area.Max.X, Y: area.Max.Y},
199− }
200− uv.NewStyledString(scrollbar).Draw(scr, scrollbarArea)
201− }
134+ if m.drawCache == nil ||
135+ m.drawCache.rendered != rendered ||
136+ m.drawCache.method != method {
137+ m.drawCache = newChatDrawCache(rendered, method)
202138 }
139+ drawCachedBuffer(scr, area, m.drawCache.buf)
203140}
204141
205142// newChatDrawCache builds a chatDrawCache for the given rendered string by
@@ -269,12 +206,7 @@ func drawCachedBuffer(scr uv.Screen, area uv.Rectangle, buf uv.ScreenBuffer) {
269206
270207// SetSize sets the size of the chat view port.
271208func (m *Chat) SetSize(width, height int) {
272− // Reserve space for scrollbar if content exceeds viewport height.
273− listWidth := width
274− if m.list.TotalHeight() > height {
275− listWidth = max(0, width-1)
276− }
277− m.list.SetSize(listWidth, height)
209+ m.list.SetSize(width, height)
278210 // Anchor to bottom if we were at the bottom.
279211 if m.AtBottom() {
280212 m.ScrollToBottom()
@@ -299,10 +231,9 @@ func (m *Chat) InvalidateRenderCaches() {
299231}
300232
301233// SetMessages sets the chat messages to the provided list of message items.
302−func (m *Chat) SetMessages(msgs ...chat.MessageItem) tea.Cmd {
234+func (m *Chat) SetMessages(msgs ...chat.MessageItem) {
303235 m.idInxMap = make(map[string]int)
304236 m.pausedAnimations = make(map[string]struct{})
305− m.scrollbarVisible = false // Reset scrollbar visibility on new session load
306237
307238 items := make([]list.Item, len(msgs))
308239 for i, msg := range msgs {
@@ -317,7 +248,6 @@ func (m *Chat) SetMessages(msgs ...chat.MessageItem) tea.Cmd {
317248 }
318249 m.list.SetItems(items...)
319250 m.ScrollToBottom()
320− return nil
321251}
322252
323253// AppendMessages appends a new message item to the chat list.
@@ -448,91 +378,61 @@ func (m *Chat) Follow() bool {
448378}
449379
450380// ScrollToBottom scrolls the chat view to the bottom.
451−// Does not trigger scrollbar visibility (auto-scroll to show new content).
452−func (m *Chat) ScrollToBottom() tea.Cmd {
381+func (m *Chat) ScrollToBottom() {
453382 m.list.ScrollToBottom()
454− m.follow = true
455− return nil
383+ m.follow = true // Enable follow mode when user scrolls to bottom
456384}
457385
458386// ScrollToTop scrolls the chat view to the top.
459−func (m *Chat) ScrollToTop() tea.Cmd {
387+func (m *Chat) ScrollToTop() {
460388 m.list.ScrollToTop()
461389 m.follow = false // Disable follow mode when user scrolls up
462− return m.showScrollbar()
463390}
464391
465392// ScrollBy scrolls the chat view by the given number of line deltas.
466−func (m *Chat) ScrollBy(lines int) tea.Cmd {
393+func (m *Chat) ScrollBy(lines int) {
467394 m.list.ScrollBy(lines)
468− if lines < 0 {
469− // Scrolling up always disables follow mode.
470− m.follow = false
471− } else if m.AtBottom() {
472− // Scrolling down re-enables follow when we reach the bottom.
473− m.follow = true
474− }
475− return m.showScrollbar()
395+ m.follow = lines > 0 && m.AtBottom() // Disable follow mode if user scrolls up
476396}
477397
478398// ScrollToSelected scrolls the chat view to the selected item.
479−func (m *Chat) ScrollToSelected() tea.Cmd {
399+func (m *Chat) ScrollToSelected() {
480400 m.list.ScrollToSelected()
481401 m.follow = m.AtBottom() // Disable follow mode if user scrolls up
482− return m.showScrollbar()
483402}
484403
485404// ScrollToIndex scrolls the chat view to the item at the given index.
486−func (m *Chat) ScrollToIndex(index int) tea.Cmd {
405+func (m *Chat) ScrollToIndex(index int) {
487406 m.list.ScrollToIndex(index)
488407 m.follow = m.AtBottom() // Disable follow mode if user scrolls up
489− return m.showScrollbar()
490−}
491−
492−// showScrollbar makes the scrollbar visible and returns a command to hide it after timeout.
493−func (m *Chat) showScrollbar() tea.Cmd {
494− // Only start timer for "default" mode
495− if m.scrollbarMode != config.ScrollbarDefault {
496− return nil
497− }
498− m.scrollbarVisible = true
499− m.scrollbarHideSeq++
500− return scrollbarHideCmd(m.scrollbarHideSeq)
501−}
502−
503−// HideScrollbar hides the scrollbar if the sequence matches.
504−func (m *Chat) HideScrollbar(seq int) {
505− // Only hide scrollbar for "default" mode
506− if m.scrollbarMode != config.ScrollbarDefault {
507− return
508− }
509− if seq == m.scrollbarHideSeq {
510− m.scrollbarVisible = false
511− }
512408}
513409
514410// ScrollToTopAndAnimate scrolls the chat view to the top and returns a command to restart
515411// any paused animations that are now visible.
516412func (m *Chat) ScrollToTopAndAnimate() tea.Cmd {
517− return tea.Batch(m.ScrollToTop(), m.RestartPausedVisibleAnimations())
413+ m.ScrollToTop()
414+ return m.RestartPausedVisibleAnimations()
518415}
519416
520417// ScrollToBottomAndAnimate scrolls the chat view to the bottom and returns a command to
521418// restart any paused animations that are now visible.
522419func (m *Chat) ScrollToBottomAndAnimate() tea.Cmd {
523− return tea.Batch(m.ScrollToBottom(), m.RestartPausedVisibleAnimations())
420+ m.ScrollToBottom()
421+ return m.RestartPausedVisibleAnimations()
524422}
525423
526424// ScrollByAndAnimate scrolls the chat view by the given number of line deltas and returns
527425// a command to restart any paused animations that are now visible.
528426func (m *Chat) ScrollByAndAnimate(lines int) tea.Cmd {
529− return tea.Batch(m.ScrollBy(lines), m.RestartPausedVisibleAnimations())
427+ m.ScrollBy(lines)
428+ return m.RestartPausedVisibleAnimations()
530429}
531430
532431// ScrollToSelectedAndAnimate scrolls the chat view to the selected item and returns a
533432// command to restart any paused animations that are now visible.
534433func (m *Chat) ScrollToSelectedAndAnimate() tea.Cmd {
535− return tea.Batch(m.ScrollToSelected(), m.RestartPausedVisibleAnimations())
434+ m.ScrollToSelected()
435+ return m.RestartPausedVisibleAnimations()
536436}
537437
538438// SelectedItemInView returns whether the selected item is currently in view.
@@ -661,7 +561,6 @@ func (m *Chat) SelectLastInView() {
661561func (m *Chat) ClearMessages() {
662562 m.idInxMap = make(map[string]int)
663563 m.pausedAnimations = make(map[string]struct{})
664− m.scrollbarVisible = false
665564 m.list.SetItems()
666565 m.ClearMouse()
667566}
@@ -673,18 +572,6 @@ func (m *Chat) RemoveMessage(id string) {
673572 return
674573 }
675574
676− // Clean up nested tool IDs that point to this item's index before
677− // removing it, so they don't end up pointing to a wrong item after
678− // the rebuild.
679− if item, ok := m.list.ItemAt(idx).(chat.MessageItem); ok {
680− if container, ok := item.(chat.NestedToolContainer); ok {
681− for _, nested := range container.NestedTools() {
682− delete(m.idInxMap, nested.ID())
683− delete(m.pausedAnimations, nested.ID())
684− }
685− }
686− }
687−
688575 // Remove from list
689576 m.list.RemoveItem(idx)
690577
@@ -695,12 +582,6 @@ func (m *Chat) RemoveMessage(id string) {
695582 for i := idx; i < m.list.Len(); i++ {
696583 if item, ok := m.list.ItemAt(i).(chat.MessageItem); ok {
697584 m.idInxMap[item.ID()] = i
698− // Also update nested tool IDs for containers.
699− if container, ok := item.(chat.NestedToolContainer); ok {
700− for _, nested := range container.NestedTools() {
701− m.idInxMap[nested.ID()] = i
702− }
703− }
704585 }
705586 }
706587
@@ -724,11 +605,10 @@ func (m *Chat) MessageItem(id string) chat.MessageItem {
724605// ToggleExpandedSelectedItem expands the selected message item if it is expandable.
725606func (m *Chat) ToggleExpandedSelectedItem() {
726607 if expandable, ok := m.list.SelectedItem().(chat.Expandable); ok {
727− wasFollowing := m.follow
728608 if !expandable.ToggleExpanded() {
729609 m.ScrollToIndex(m.list.Selected())
730610 }
731− if wasFollowing {
611+ if m.AtBottom() {
732612 m.ScrollToBottom()
733613 }
734614 }
@@ -855,15 +735,14 @@ func (m *Chat) HandleDelayedClick(msg DelayedClickMsg) bool {
855735 // toggling expansion for clicks outside the clickable area.
856736 if handled {
857737 if expandable, ok := selectedItem.(chat.Expandable); ok {
858− wasFollowing := m.follow
859738 if !expandable.ToggleExpanded() {
860739 m.ScrollToIndex(m.list.Selected())
861740 }
862− if wasFollowing {
863− m.ScrollToBottom()
864− }
865741 }
866742 }
743+ if m.AtBottom() {
744+ m.ScrollToBottom()
745+ }
867746 return handled
868747 }
869748
@@ -1124,6 +1003,10 @@ func findWordBoundaries(line string, col int) (startCol, endCol int) {
11241003 return 0, 0
11251004 }
11261005
1006+ i := displaywidth.StringGraphemes(line)
1007+ for i.Next() {
1008+ }
1009+
11271010 // Segment the line into words using UAX#29.
11281011 lineCol := 0 // tracks the visited column widths
11291012 lastCol := 0 // tracks the start of the current token
internal/ui/model/header.go+10−93
@@ -2,8 +2,6 @@ package model
22
33import (
44 "fmt"
5− "hash/fnv"
6− "strconv"
75 "strings"
86
97 "charm.land/lipgloss/v2"
@@ -32,18 +30,6 @@ type header struct {
3230 com *common.Common
3331 width int
3432 compact bool
35−
36− // compact-header render cache. drawHeader otherwise rebuilds the whole
37− // compact header (LSP loop, config/model lookups, several
38− // lipgloss.Render calls, PrettyPath+DirTrim, ansi.Truncate) every
39− // frame; memoize the rendered string keyed by a cheap fingerprint of
40− // everything it depends on.
41− compactCacheKey uint64
42− compactCacheView string
43− hasCompactCache bool
44− // compactFPBuf is a scratch buffer reused across frames to build the
45− // compact-header fingerprint without allocating (see compactFingerprint).
46− compactFPBuf []byte
4733}
4834
4935// newHeader creates a new header model.
@@ -73,13 +59,9 @@ func (h *header) refresh() {
7359 // Force drawHeader to re-render the wide logo on the next frame.
7460 h.width = 0
7561 h.logo = ""
76− // The compact-header cache embeds the old theme's styles; drop it.
77− h.hasCompactCache = false
7862}
7963
80−// drawHeader draws the header for the given session. lspErrorCount is the
81−// total diagnostic count summed by the caller from local LSP state, so
82−// this render path never issues an LSPGetStates RPC.
64+// drawHeader draws the header for the given session.
8365func (h *header) drawHeader(
8466 scr uv.Screen,
8567 area uv.Rectangle,
@@ -88,8 +70,8 @@ func (h *header) drawHeader(
8870 detailsOpen bool,
8971 width int,
9072 hyperCredits *int,
91− lspErrorCount int,
9273) {
74+ t := h.com.Styles
9375 if width != h.width || compact != h.compact {
9476 h.logo = renderLogo(h.com.Styles, compact, h.com.IsHyper(), width)
9577 }
@@ -106,79 +88,14 @@ func (h *header) drawHeader(
10688 return
10789 }
10890
109− key := h.compactFingerprint(session, detailsOpen, width, hyperCredits, lspErrorCount)
110− if !h.hasCompactCache || key != h.compactCacheKey {
111− h.compactCacheView = h.buildCompactHeader(session, detailsOpen, width, hyperCredits, lspErrorCount)
112− h.compactCacheKey = key
113− h.hasCompactCache = true
114− }
115−
116− uv.NewStyledString(h.compactCacheView).Draw(scr, area)
117−}
118−
119−// compactFingerprint hashes every input that affects the compact header
120−// render, so drawHeader can reuse the cached string when nothing changed.
121−func (h *header) compactFingerprint(
122− session *session.Session,
123− detailsOpen bool,
124− width int,
125− hyperCredits *int,
126− lspErrorCount int,
127−) uint64 {
128− var contextWindow int64
129− agentCfg := h.com.Config().Agents[config.AgentCoder]
130− if model := h.com.Config().GetModelByType(agentCfg.Model); model != nil {
131− contextWindow = model.ContextWindow
132− }
133− credits := 0
134− hasCredits := hyperCredits != nil
135− if hasCredits {
136− credits = *hyperCredits
137− }
138− // Build into a reused buffer with strconv appends and hash once, rather
139− // than fmt.Sprintf: this runs every frame in compact mode, so the arg
140− // boxing and result-string allocation Sprintf performed were pure churn.
141− b := h.compactFPBuf[:0]
142− putInt := func(k string, v int64) { b = append(b, k...); b = strconv.AppendInt(b, v, 10) }
143− putInt("w=", int64(width))
144− b = append(b, ";sid="...)
145− b = append(b, session.ID...)
146− putInt(";ct=", session.CompletionTokens)
147− putInt(";pt=", session.PromptTokens)
148− b = append(b, ";eu="...)
149− b = strconv.AppendBool(b, session.EstimatedUsage)
150− putInt(";lsp=", int64(lspErrorCount))
151− b = append(b, ";det="...)
152− b = strconv.AppendBool(b, detailsOpen)
153− b = append(b, ";hyper="...)
154− b = strconv.AppendBool(b, h.com.IsHyper())
155− b = append(b, ";hc="...)
156− b = strconv.AppendBool(b, hasCredits)
157− putInt("/", int64(credits))
158− putInt(";cw=", contextWindow)
159− b = append(b, ";cwd="...)
160− b = append(b, h.com.Workspace.WorkingDir()...)
161− h.compactFPBuf = b // retain the grown buffer for reuse next frame
162−
163− hash := fnv.New64a()
164− hash.Write(b)
165− return hash.Sum64()
166−}
167−
168−// buildCompactHeader renders the compact header string.
169−func (h *header) buildCompactHeader(
170− session *session.Session,
171− detailsOpen bool,
172− width int,
173− hyperCredits *int,
174− lspErrorCount int,
175−) string {
176− t := h.com.Styles
177−
17891 var b strings.Builder
17992 b.WriteString(h.compactLogo)
18093
18194 availDetailWidth := width - leftPadding - rightPadding - lipgloss.Width(b.String()) - minHeaderDiags - diagToDetailsSpacing
95+ lspErrorCount := 0
96+ for _, info := range h.com.Workspace.LSPGetStates() {
97+ lspErrorCount += info.DiagnosticCount
98+ }
18299 details := renderHeaderDetails(
183100 h.com,
184101 session,
@@ -204,7 +121,10 @@ func (h *header) buildCompactHeader(
204121
205122 b.WriteString(details)
206123
207− return t.Header.Wrapper.Padding(0, rightPadding, 0, leftPadding).Render(b.String())
124+ view := uv.NewStyledString(
125+ t.Header.Wrapper.Padding(0, rightPadding, 0, leftPadding).Render(b.String()),
126+ )
127+ view.Draw(scr, area)
208128}
209129
210130// renderHeaderDetails renders the details section of the header.
@@ -228,9 +148,6 @@ func renderHeaderDetails(
228148 model := com.Config().GetModelByType(agentCfg.Model)
229149 if model != nil && model.ContextWindow > 0 {
230150 percentage := (float64(session.CompletionTokens+session.PromptTokens) / float64(model.ContextWindow)) * 100
231− if percentage > 100 {
232− percentage = 100
233− }
234151 percentageText := fmt.Sprintf("%d%%", int(percentage))
235152 if session.EstimatedUsage {
236153 percentageText = "~" + percentageText
internal/ui/model/history.go+2−1
@@ -1,6 +1,7 @@
11package model
22
33import (
4+ "context"
45 "log/slog"
56 "strings"
67
@@ -17,7 +18,7 @@ type promptHistoryLoadedMsg struct {
1718// loadPromptHistory loads user messages for history navigation.
1819func (m *UI) loadPromptHistory() tea.Cmd {
1920 return func() tea.Msg {
20− ctx := m.ctx
21+ ctx := context.Background()
2122 var messages []message.Message
2223 var err error
2324
internal/ui/model/keys.go+21−25
@@ -4,14 +4,14 @@ import "charm.land/bubbles/v2/key"
44
55type KeyMap struct {
66 Editor struct {
7− SendMessage key.Binding
8− OpenEditor key.Binding
9− Newline key.Binding
10− AddImage key.Binding
11− PasteImage key.Binding
12− MentionFile key.Binding
13− Commands key.Binding
14− ShellCommand key.Binding
7+ AddFile key.Binding
8+ SendMessage key.Binding
9+ OpenEditor key.Binding
10+ Newline key.Binding
11+ AddImage key.Binding
12+ PasteImage key.Binding
13+ MentionFile key.Binding
14+ Commands key.Binding
1515
1616 // Attachments key maps
1717 AttachmentDeleteMode key.Binding
@@ -25,6 +25,7 @@ type KeyMap struct {
2525
2626 Chat struct {
2727 NewSession key.Binding
28+ AddAttachment key.Binding
2829 Cancel key.Binding
2930 Tab key.Binding
3031 Details key.Binding
@@ -66,7 +67,6 @@ type KeyMap struct {
6667 Sessions key.Binding
6768 Tab key.Binding
6869 ToggleYolo key.Binding
69− CopyError key.Binding
7070}
7171
7272func DefaultKeyMap() KeyMap {
@@ -103,12 +103,12 @@ func DefaultKeyMap() KeyMap {
103103 key.WithKeys("ctrl+y"),
104104 key.WithHelp("ctrl+y", "toggle yolo"),
105105 ),
106− CopyError: key.NewBinding(
107− key.WithKeys("ctrl+e"),
108− key.WithHelp("ctrl+e", "copy error"),
109− ),
110106 }
111107
108+ km.Editor.AddFile = key.NewBinding(
109+ key.WithKeys("/"),
110+ key.WithHelp("/", "add file"),
111+ )
112112 km.Editor.SendMessage = key.NewBinding(
113113 key.WithKeys("enter"),
114114 key.WithHelp("enter", "send"),
@@ -140,14 +140,6 @@ func DefaultKeyMap() KeyMap {
140140 key.WithKeys("/"),
141141 key.WithHelp("/", "commands"),
142142 )
143− // ShellCommand is a help-only hint: bang (!) shell mode is entered by
144− // typing "!" at the start of an empty editor (see the bang-mode handling
145− // in ui.go), not via a key.Matches lookup, so this binding exists purely
146− // to surface the feature in the help view.
147− km.Editor.ShellCommand = key.NewBinding(
148− key.WithKeys("!"),
149− key.WithHelp("!", "shell command"),
150− )
151143 km.Editor.AttachmentDeleteMode = key.NewBinding(
152144 key.WithKeys("ctrl+r"),
153145 key.WithHelp("ctrl+r+{i}", "delete attachment at index i"),
@@ -171,6 +163,10 @@ func DefaultKeyMap() KeyMap {
171163 key.WithKeys("ctrl+n"),
172164 key.WithHelp("ctrl+n", "new session"),
173165 )
166+ km.Chat.AddAttachment = key.NewBinding(
167+ key.WithKeys("ctrl+f"),
168+ key.WithHelp("ctrl+f", "add attachment"),
169+ )
174170 km.Chat.Cancel = key.NewBinding(
175171 key.WithKeys("esc", "alt+esc"),
176172 key.WithHelp("esc", "cancel"),
@@ -198,11 +194,11 @@ func DefaultKeyMap() KeyMap {
198194
199195 km.Chat.Down = key.NewBinding(
200196 key.WithKeys("down", "ctrl+j", "j"),
201− key.WithHelp("↓/j", "down"),
197+ key.WithHelp("↓", "down"),
202198 )
203199 km.Chat.Up = key.NewBinding(
204200 key.WithKeys("up", "ctrl+k", "k"),
205− key.WithHelp("↑/k", "up"),
201+ key.WithHelp("↑", "up"),
206202 )
207203 km.Chat.UpDown = key.NewBinding(
208204 key.WithKeys("up", "down"),
@@ -210,11 +206,11 @@ func DefaultKeyMap() KeyMap {
210206 )
211207 km.Chat.UpOneItem = key.NewBinding(
212208 key.WithKeys("shift+up", "K"),
213− key.WithHelp("shift+↑/K", "up one item"),
209+ key.WithHelp("shift+↑", "up one item"),
214210 )
215211 km.Chat.DownOneItem = key.NewBinding(
216212 key.WithKeys("shift+down", "J"),
217− key.WithHelp("shift+↓/J", "down one item"),
213+ key.WithHelp("shift+↓", "down one item"),
218214 )
219215 km.Chat.UpDownOneItem = key.NewBinding(
220216 key.WithKeys("shift+up", "shift+down"),
internal/ui/model/landing.go+9−78
@@ -1,11 +1,9 @@
11package model
22
33import (
4− "fmt"
54 "image"
65
76 "charm.land/lipgloss/v2"
8− "github.com/charmbracelet/crush/internal/config"
97 "github.com/charmbracelet/crush/internal/ui/common"
108 "github.com/charmbracelet/crush/internal/workspace"
119 "github.com/charmbracelet/ultraviolet/layout"
@@ -13,68 +11,14 @@ import (
1311
1412// selectedLargeModel returns the currently selected large language model from
1513// the agent coordinator, if one exists.
16−//
17−// Resolving the model hits the agent coordinator, which is an RPC
18−// (AgentIsReady + AgentModel) in client mode. Because this runs on the
19−// per-frame sidebar fingerprint path, the result is cached against a
20−// cheap, locally-cached config fingerprint (largeModelConfigKey) and
21−// only re-fetched when the selection changes. A nil result is never
22−// cached, so the not-ready → ready transition is picked up on the next
23−// frame.
2414func (m *UI) selectedLargeModel() *workspace.AgentModel {
25− key := m.largeModelConfigKey()
26− if m.renderCache.largeModel != nil && key == m.renderCache.largeModelCacheKey {
27− return m.renderCache.largeModel
28− }
29− m.renderCache.largeModelCacheKey = key
3015 if m.com.Workspace.AgentIsReady() {
3116 model := m.com.Workspace.AgentModel()
32− m.renderCache.largeModel = &model
33− return m.renderCache.largeModel
17+ return &model
3418 }
35− m.renderCache.largeModel = nil
3619 return nil
3720}
3821
39−// largeModelConfigKey builds a cheap fingerprint of the locally-cached
40−// config that determines the selected large (coder) model. The resolved
41−// model is cached against this key so selectedLargeModel only re-fetches
42−// when the selection actually changes. The cached workspace config is
43−// refreshed on every server-side config change (ConfigChanged), so
44−// remote model switches flip this key too.
45−func (m *UI) largeModelConfigKey() string {
46− cfg := m.com.Config()
47− if cfg == nil {
48− return ""
49− }
50− agentCfg, ok := cfg.Agents[config.AgentCoder]
51− if !ok {
52− return "no-agent"
53− }
54− sm, ok := cfg.Models[agentCfg.Model]
55− if !ok {
56− return "no-model:" + string(agentCfg.Model)
57− }
58− // Fold in the catwalk-derived fields that the sidebar renders (looked
59− // up from the same cached config, no RPC), so a provider-config
60− // change that alters e.g. the context window or reasoning support for
61− // the same provider+model still flips the key and forces a refresh.
62− var cwName, provName string
63− var contextWindow int64
64− var canReason bool
65− if cm := cfg.GetModelByType(agentCfg.Model); cm != nil {
66− cwName = cm.Name
67− contextWindow = cm.ContextWindow
68− canReason = cm.CanReason
69− }
70− if pc, ok := cfg.Providers.Get(sm.Provider); ok {
71− provName = pc.Name
72− }
73− return fmt.Sprintf("%s|%s|%s|%t|%s|%s|%s|%d|%t",
74− agentCfg.Model, sm.Provider, sm.Model, sm.Think, sm.ReasoningEffort,
75− provName, cwName, contextWindow, canReason)
76−}
77−
7822// landingView renders the landing page view showing the current working
7923// directory, model information, and LSP/MCP status in a two-column layout.
8024func (m *UI) landingView() string {
@@ -86,8 +30,7 @@ func (m *UI) landingView() string {
8630 cwd,
8731 }
8832
89− cta := t.Resource.AdditionalText.Render("Type a message to begin · / for commands · ctrl+s for sessions")
90− parts = append(parts, "", m.modelInfo(width), "", cta)
33+ parts = append(parts, "", m.modelInfo(width))
9134 infoSection := lipgloss.JoinVertical(lipgloss.Left, parts...)
9235
9336 var remainingHeightArea image.Rectangle
@@ -96,25 +39,13 @@ func (m *UI) landingView() string {
9639 layout.Fill(1),
9740 ).Split(m.layout.main).Assign(new(image.Rectangle), &remainingHeightArea)
9841
99− // On narrow terminals the three side-by-side sections get squeezed to a
100− // few columns each, so stack them vertically instead. narrowLandingWidth
101− // is the point below which a 3-column split would leave each column under
102− // ~20 cells wide ((width-2)/3 < 20).
103− const narrowLandingWidth = 60
104− var content string
105− if width < narrowLandingWidth {
106− perSection := max(1, remainingHeightArea.Dy()/3)
107− lspSection := m.lspInfo(width, perSection, false)
108− mcpSection := m.mcpInfo(width, perSection, false)
109− skillsSection := m.skillsInfo(width, perSection, false)
110− content = lipgloss.JoinVertical(lipgloss.Left, lspSection, "", mcpSection, "", skillsSection)
111− } else {
112− mcpLspSectionWidth := min(30, (width-2)/3)
113− lspSection := m.lspInfo(mcpLspSectionWidth, max(1, remainingHeightArea.Dy()), false)
114− mcpSection := m.mcpInfo(mcpLspSectionWidth, max(1, remainingHeightArea.Dy()), false)
115− skillsSection := m.skillsInfo(mcpLspSectionWidth, max(1, remainingHeightArea.Dy()), false)
116− content = lipgloss.JoinHorizontal(lipgloss.Left, lspSection, " ", mcpSection, " ", skillsSection)
117− }
42+ mcpLspSectionWidth := min(30, (width-2)/3)
43+
44+ lspSection := m.lspInfo(mcpLspSectionWidth, max(1, remainingHeightArea.Dy()), false)
45+ mcpSection := m.mcpInfo(mcpLspSectionWidth, max(1, remainingHeightArea.Dy()), false)
46+ skillsSection := m.skillsInfo(mcpLspSectionWidth, max(1, remainingHeightArea.Dy()), false)
47+
48+ content := lipgloss.JoinHorizontal(lipgloss.Left, lspSection, " ", mcpSection, " ", skillsSection)
11849
11950 return lipgloss.NewStyle().
12051 Width(width).
internal/ui/model/layout_test.go+1−2
@@ -6,7 +6,6 @@ import (
66 "testing"
77
88 "charm.land/bubbles/v2/textarea"
9− "github.com/charmbracelet/crush/internal/config"
109 "github.com/charmbracelet/crush/internal/session"
1110 "github.com/charmbracelet/crush/internal/ui/chat"
1211 "github.com/charmbracelet/crush/internal/ui/common"
@@ -46,7 +45,7 @@ func newTestUI() *UI {
4645 u := &UI{
4746 com: com,
4847 status: NewStatus(com, nil),
49− chat: NewChat(com, config.ScrollbarDefault),
48+ chat: NewChat(com),
5049 textarea: ta,
5150 state: uiChat,
5251 focus: uiFocusEditor,
internal/ui/model/lsp.go+2−23
@@ -20,23 +20,6 @@ type LSPInfo struct {
2020 Diagnostics map[protocol.DiagnosticSeverity]int
2121}
2222
23−// refreshLSPState re-fetches the LSP client states and their per-severity
24−// diagnostic counts into the local mirrors (m.lspStates / m.lspDiagnostics).
25−// It is called from the LSP event handlers (state change / diagnostics
26−// change) so the sidebar fingerprint — computed every frame — can read
27−// fresh values from local state instead of issuing per-LSP diagnostics
28−// RPCs on the render hot path. Every diagnostics change publishes an LSP
29−// event (see internal/app/lsp_events.go), so the mirror always changes
30−// whenever the rendered sidebar would.
31−func (m *UI) refreshLSPState() {
32− m.lspStates = m.com.Workspace.LSPGetStates()
33− counts := make(map[string]lsp.DiagnosticCounts, len(m.lspStates))
34− for name := range m.lspStates {
35− counts[name] = m.com.Workspace.LSPGetDiagnosticCounts(name)
36− }
37− m.lspDiagnostics = counts
38−}
39−
4023// lspInfo renders the LSP status section showing active LSP clients and their
4124// diagnostic counts.
4225func (m *UI) lspInfo(width, maxItems int, isSection bool) string {
@@ -49,11 +32,7 @@ func (m *UI) lspInfo(width, maxItems int, isSection bool) string {
4932 var lsps []LSPInfo
5033 for _, state := range states {
5134 lspErrs := map[protocol.DiagnosticSeverity]int{}
52− // Read from the local mirror (refreshed on every LSP event) rather
53− // than issuing a per-LSP diagnostics RPC on the render path. This
54− // also keeps the rendered counts consistent with the sidebar
55− // fingerprint, which reads the same mirror.
56− counts := m.lspDiagnostics[state.Name]
35+ counts := m.com.Workspace.LSPGetDiagnosticCounts(state.Name)
5736 lspErrs[protocol.SeverityError] = counts.Error
5837 lspErrs[protocol.SeverityWarning] = counts.Warning
5938 lspErrs[protocol.SeverityHint] = counts.Hint
@@ -139,7 +118,7 @@ func lspList(t *styles.Styles, lsps []LSPInfo, width, maxItems int) string {
139118
140119 if len(renderedLsps) > maxItems {
141120 visibleItems := renderedLsps[:maxItems-1]
142− remaining := len(renderedLsps) - (maxItems - 1)
121+ remaining := len(renderedLsps) - maxItems
143122 visibleItems = append(visibleItems, t.Resource.AdditionalText.Render(fmt.Sprintf("…and %d more", remaining)))
144123 return lipgloss.JoinVertical(lipgloss.Left, visibleItems...)
145124 }
internal/ui/model/mcp.go+2−9
@@ -14,17 +14,10 @@ import (
1414// mcpInfo renders the MCP status section showing active MCP clients and their
1515// tool/prompt counts.
1616func (m *UI) mcpInfo(width, maxItems int, isSection bool) string {
17− return m.mcpInfoSorted(m.com.Config().MCP.Sorted(), width, maxItems, isSection)
18−}
19−
20−// mcpInfoSorted renders the MCP status section from an already-sorted MCP
21−// config list, letting callers on the sidebar render path reuse a single
22−// MCP.Sorted() result instead of recomputing it.
23−func (m *UI) mcpInfoSorted(mcpSorted []config.MCP, width, maxItems int, isSection bool) string {
2417 var mcps []mcp.ClientInfo
2518 t := m.com.Styles
2619
27− for _, mcp := range mcpSorted {
20+ for _, mcp := range m.com.Config().MCP.Sorted() {
2821 if state, ok := m.mcpStates[mcp.Name]; ok {
2922 mcps = append(mcps, state)
3023 }
@@ -106,7 +99,7 @@ func mcpList(t *styles.Styles, mcps []mcp.ClientInfo, width, maxItems int) strin
10699
107100 if len(renderedMcps) > maxItems {
108101 visibleItems := renderedMcps[:maxItems-1]
109− remaining := len(renderedMcps) - (maxItems - 1)
102+ remaining := len(renderedMcps) - maxItems
110103 visibleItems = append(visibleItems, t.Resource.AdditionalText.Render(fmt.Sprintf("…and %d more", remaining)))
111104 return lipgloss.JoinVertical(lipgloss.Left, visibleItems...)
112105 }
internal/ui/model/onboarding.go+2−1
@@ -72,6 +72,7 @@ func (m *UI) initializeProject() tea.Cmd {
7272
7373// skipInitializeProject skips project initialization and transitions to the landing view.
7474func (m *UI) skipInitializeProject() tea.Cmd {
75+ // TODO: initialize the project
7576 m.setState(uiLanding, uiFocusEditor)
7677 // mark the project as initialized
7778 return m.markProjectInitializedCmd()
@@ -85,7 +86,7 @@ func (m *UI) initializeView() string {
8586
8687 header := s.Header.Render("Would you like to initialize this project?")
8788 path := s.Accent.PaddingLeft(2).Render(cwd)
88− desc := s.Content.Render(fmt.Sprintf("When I initialize your codebase I examine the project and put the result into a %s file which serves as general context.", initFile))
89+ desc := s.Content.Render(fmt.Sprintf("When I initialize your codebase I examine the project and put the result into an %s file which serves as general context.", initFile))
8990 hint := s.Content.Render("You can also initialize anytime via ") + s.Accent.Render("ctrl+p") + s.Content.Render(".")
9091 prompt := s.Content.Render("Would you like to initialize now?")
9192
internal/ui/model/pills.go+4−49
@@ -88,7 +88,7 @@ func todoPill(todos []session.Todo, spinnerView string, focused, panelFocused bo
8888
8989 total := len(todos)
9090
91− label := t.Pills.TodoLabel.Render("Tasks")
91+ label := t.Pills.TodoLabel.Render("To-Do")
9292 progress := t.Pills.TodoProgress.Render(fmt.Sprintf("%d/%d", completed, total))
9393
9494 var content string
@@ -192,8 +192,6 @@ func (m *UI) togglePillsExpanded() tea.Cmd {
192192 m.updateLayoutAndSize()
193193
194194 // Make sure to follow scroll if follow is enabled when toggling pills.
195− // Note: uses ScrollToBottom (no scrollbar) since this is layout adjustment,
196− // not user-initiated scrolling.
197195 if m.chat.Follow() {
198196 m.chat.ScrollToBottom()
199197 }
@@ -245,52 +243,9 @@ func (m *UI) pillsAreaHeight() int {
245243 return pillsAreaHeight
246244}
247245
248−// pillsCacheState builds a fingerprint of every input that affects the
249−// rendered pills. Returned alongside the width for reuse by renderPills.
250−func (m *UI) pillsCacheState() string {
251− if !m.hasSession() {
252− return "no-session"
253− }
254− width := m.layout.pills.Dx()
255− hasQueue := m.promptQueue > 0
256−
257− var kb strings.Builder
258− fmt.Fprintf(&kb, "w=%d;theme=%s;q=%d;exp=%t;sec=%d;",
259− width, m.themeKey, m.promptQueue, m.pillsExpanded, m.focusedPillSection)
260− // The in-progress spinner icon changes every animation frame, so fold
261− // its current view in only while spinning.
262− if m.todoIsSpinning {
263− kb.WriteString("spin=")
264− kb.WriteString(m.todoSpinner.View())
265− kb.WriteByte(';')
266− }
267− for i := range m.session.Todos {
268− td := &m.session.Todos[i]
269− fmt.Fprintf(&kb, "t%d=%v|%s|%s;", i, td.Status, td.Content, td.ActiveForm)
270− }
271− // The expanded queue list content only affects output when the queue
272− // section is expanded and focused.
273− if m.pillsExpanded && m.focusedPillSection == pillSectionQueue && hasQueue {
274− if m.com != nil && m.com.Workspace != nil && m.com.Workspace.AgentIsReady() {
275− for _, it := range m.com.Workspace.AgentQueuedPromptsList(m.session.ID) {
276− kb.WriteString(it)
277− kb.WriteByte(';')
278− }
279− }
280− }
281− return kb.String()
282−}
283−
284−// renderPills renders the pills panel and stores it in m.renderCache.pillsView.
246+// renderPills renders the pills panel and stores it in m.pillsView.
285247func (m *UI) renderPills() {
286− // Skip re-rendering when nothing that affects the output changed.
287− key := m.pillsCacheState()
288− if key == m.renderCache.pillsCacheKey {
289− return
290− }
291− m.renderCache.pillsCacheKey = key
292−
293− m.renderCache.pillsView = ""
248+ m.pillsView = ""
294249 if !m.hasSession() {
295250 return
296251 }
@@ -359,5 +314,5 @@ func (m *UI) renderPills() {
359314 pillsArea = lipgloss.JoinVertical(lipgloss.Left, pillsRow, expandedList)
360315 }
361316
362− m.renderCache.pillsView = t.Pills.Area.MaxWidth(width).PaddingLeft(paddingLeft).Render(pillsArea)
317+ m.pillsView = t.Pills.Area.MaxWidth(width).PaddingLeft(paddingLeft).Render(pillsArea)
363318}
internal/ui/model/session.go+6−5
@@ -1,6 +1,7 @@
11package model
22
33import (
4+ "context"
45 "fmt"
56 "log/slog"
67 "path/filepath"
@@ -70,7 +71,7 @@ type SessionFile struct {
7071// UI never blocks on the call.
7172func (m *UI) loadSession(sessionID string) tea.Cmd {
7273 load := func() tea.Msg {
73− session, err := m.com.Workspace.GetSession(m.ctx, sessionID)
74+ session, err := m.com.Workspace.GetSession(context.Background(), sessionID)
7475 if err != nil {
7576 return util.ReportError(err)
7677 }
@@ -80,7 +81,7 @@ func (m *UI) loadSession(sessionID string) tea.Cmd {
8081 return util.ReportError(err)
8182 }
8283
83− readFiles, err := m.com.Workspace.FileTrackerListReadFiles(m.ctx, sessionID)
84+ readFiles, err := m.com.Workspace.FileTrackerListReadFiles(context.Background(), sessionID)
8485 if err != nil {
8586 slog.Error("Failed to load read files for session", "error", err)
8687 }
@@ -101,7 +102,7 @@ func (m *UI) loadSession(sessionID string) tea.Cmd {
101102// state.
102103func (m *UI) reportCurrentSession(sessionID string) tea.Cmd {
103104 return func() tea.Msg {
104− if err := m.com.Workspace.SetCurrentSession(m.ctx, sessionID); err != nil {
105+ if err := m.com.Workspace.SetCurrentSession(context.Background(), sessionID); err != nil {
105106 slog.Debug("Failed to report current session", "session_id", sessionID, "error", err)
106107 }
107108 return nil
@@ -109,7 +110,7 @@ func (m *UI) reportCurrentSession(sessionID string) tea.Cmd {
109110}
110111
111112func (m *UI) loadSessionFiles(sessionID string) ([]SessionFile, error) {
112− files, err := m.com.Workspace.ListSessionHistory(m.ctx, sessionID)
113+ files, err := m.com.Workspace.ListSessionHistory(context.Background(), sessionID)
113114 if err != nil {
114115 return nil, err
115116 }
@@ -263,7 +264,7 @@ func (m *UI) startLSPs(paths []string) tea.Cmd {
263264 }
264265
265266 return func() tea.Msg {
266− ctx := m.ctx
267+ ctx := context.Background()
267268 for _, path := range paths {
268269 m.com.Workspace.LSPStart(ctx, path)
269270 }
internal/ui/model/sidebar.go+21−175
@@ -3,13 +3,9 @@ package model
33import (
44 "cmp"
55 "fmt"
6− "hash/fnv"
76 "image"
8− "sort"
9− "strconv"
107
118 "charm.land/lipgloss/v2"
12− "github.com/charmbracelet/crush/internal/config"
139 "github.com/charmbracelet/crush/internal/ui/common"
1410 "github.com/charmbracelet/crush/internal/ui/logo"
1511 uv "github.com/charmbracelet/ultraviolet"
@@ -130,139 +126,6 @@ func getDynamicHeightLimits(availableHeight, fileCount, lspCount, mcpCount, skil
130126 return maxFiles, maxLSPs, maxMCPs, maxSkills
131127}
132128
133−// sidebarFingerprint computes a hash of every input that affects the
134−// rendered sidebar, so drawSidebar can reuse a cached render when nothing
135−// changed. skillItems and mcpSorted are computed once per render by
136−// drawSidebar and reused by buildSidebar to avoid recomputing them.
137−func (m *UI) sidebarFingerprint(area uv.Rectangle, skillItems []skillStatusItem, mcpSorted []config.MCP) uint64 {
138− // Build the fingerprint into a reused scratch buffer using strconv
139− // appends instead of fmt.Fprintf. This runs on every frame (before the
140− // sidebar render cache is consulted), so the reflection-based arg boxing
141− // fmt.Fprintf performs was pure per-frame allocation churn. Appending to
142− // a retained buffer and hashing it once is allocation-free after warmup.
143− b := m.renderCache.sidebarFPBuf[:0]
144− putInt := func(k string, v int64) { b = append(b, k...); b = strconv.AppendInt(b, v, 10) }
145− putStr := func(k, v string) { b = append(b, k...); b = append(b, v...) }
146− putBool := func(k string, v bool) { b = append(b, k...); b = strconv.AppendBool(b, v) }
147−
148− putInt("w=", int64(area.Dx()))
149− putInt(";h=", int64(area.Dy()))
150− putStr(";theme=", m.themeKey)
151− b = append(b, ';')
152−
153− if m.session != nil {
154− putStr("sid=", m.session.ID)
155− putStr(";title=", m.session.Title)
156− putInt(";ct=", m.session.CompletionTokens)
157− putInt(";pt=", m.session.PromptTokens)
158− b = append(b, ";cost="...)
159− b = strconv.AppendFloat(b, m.session.Cost, 'f', -1, 64)
160− putBool(";eu=", m.session.EstimatedUsage)
161− b = append(b, ';')
162− }
163−
164− putStr("cwd=", m.com.Workspace.WorkingDir())
165− putBool(";hyper=", m.com.IsHyper())
166− b = append(b, ';')
167− if m.hyperCredits != nil {
168− putInt("hc=", int64(*m.hyperCredits))
169− b = append(b, ';')
170− }
171− // The logo string is already cached; fold it in so any change to it (or
172− // the small-render breakpoint) invalidates the sidebar.
173− putStr("logo=", m.sidebarLogo)
174− b = append(b, ';')
175−
176− if model := m.selectedLargeModel(); model != nil {
177− putStr("model=", model.ModelCfg.Provider)
178− putStr("|", model.CatwalkCfg.Name)
179− putBool("|reason=", model.CatwalkCfg.CanReason)
180− putInt("|lvls=", int64(len(model.CatwalkCfg.ReasoningLevels)))
181− putStr("|def=", model.CatwalkCfg.DefaultReasoningEffort)
182− putBool("|think=", model.ModelCfg.Think)
183− putStr("|eff=", model.ModelCfg.ReasoningEffort)
184− putInt("|ctx=", int64(model.CatwalkCfg.ContextWindow))
185− b = append(b, ';')
186− } else {
187− b = append(b, "model=nil;"...)
188− }
189−
190− // Files.
191− for i := range m.sessionFiles {
192− f := &m.sessionFiles[i]
193− putInt("f", int64(i))
194− putStr("=", f.LatestVersion.Path)
195− putInt("|", int64(f.Additions))
196− putInt("|", int64(f.Deletions))
197− putInt("|", f.LatestVersion.UpdatedAt)
198− b = append(b, ';')
199− }
200−
201− // LSPs (sorted by name for a stable fingerprint), with diagnostic counts.
202− lspNames := make([]string, 0, len(m.lspStates))
203− for name := range m.lspStates {
204− lspNames = append(lspNames, name)
205− }
206− sort.Strings(lspNames)
207− for _, name := range lspNames {
208− st := m.lspStates[name]
209− // Read the split diagnostic counts from the local mirror instead
210− // of issuing a per-LSP diagnostics RPC every frame. The mirror is
211− // refreshed on every LSP event (state or diagnostics change), the
212− // same events that keep m.lspStates fresh, so this changes exactly
213− // when the rendered LSP section would.
214− counts := m.lspDiagnostics[name]
215− // Hash the error text, not just its presence: the render prints
216− // the message, so a changed message within the same state must
217− // invalidate the cache.
218− putStr("l=", st.Name)
219− putInt("|", int64(st.State))
220− putStr("|err=", errText(st.Error))
221− putInt("|", int64(counts.Error))
222− putInt("/", int64(counts.Warning))
223− putInt("/", int64(counts.Hint))
224− putInt("/", int64(counts.Information))
225− b = append(b, ';')
226− }
227−
228− // MCPs (config order, matching the render).
229− for _, mcpCfg := range mcpSorted {
230− state, ok := m.mcpStates[mcpCfg.Name]
231− if !ok {
232− continue
233− }
234− putStr("m=", state.Name)
235− putInt("|", int64(state.State))
236− putStr("|err=", errText(state.Error))
237− putInt("|", int64(state.Counts.Tools))
238− putInt("/", int64(state.Counts.Prompts))
239− putInt("/", int64(state.Counts.Resources))
240− b = append(b, ';')
241− }
242−
243− // Skills.
244− for _, it := range skillItems {
245− putStr("s=", it.icon)
246− putStr("|", it.name)
247− putStr("|", it.title)
248− b = append(b, ';')
249− }
250−
251− m.renderCache.sidebarFPBuf = b // retain the grown buffer for reuse next frame
252− h := fnv.New64a()
253− h.Write(b)
254− return h.Sum64()
255−}
256−
257−// errText returns err's message, or "" when nil. Used to fold error text
258−// into cache fingerprints.
259−func errText(err error) string {
260− if err == nil {
261− return ""
262− }
263− return err.Error()
264−}
265−
266129// sidebar renders the chat sidebar containing session title, working
267130// directory, model info, file list, LSP status, and MCP status.
268131func (m *UI) drawSidebar(scr uv.Screen, area uv.Rectangle) {
@@ -270,25 +133,6 @@ func (m *UI) drawSidebar(scr uv.Screen, area uv.Rectangle) {
270133 return
271134 }
272135
273− // Compute the skill items and sorted MCP list once per render and
274− // thread them through both the fingerprint and buildSidebar, instead
275− // of recomputing (clone+sort / map build) them several times.
276− skillItems := m.skillStatusItems()
277− mcpSorted := m.com.Config().MCP.Sorted()
278−
279− key := m.sidebarFingerprint(area, skillItems, mcpSorted)
280− if !m.renderCache.hasSidebarCache || key != m.renderCache.sidebarCacheKey {
281− m.renderCache.sidebarView = m.buildSidebar(area, skillItems, mcpSorted)
282− m.renderCache.sidebarCacheKey = key
283− m.renderCache.hasSidebarCache = true
284− }
285−
286− uv.NewStyledString(m.renderCache.sidebarView).Draw(scr, area)
287−}
288−
289−// buildSidebar renders the sidebar content block as a string. skillItems
290−// and mcpSorted are precomputed by drawSidebar and reused here.
291−func (m *UI) buildSidebar(area uv.Rectangle, skillItems []skillStatusItem, mcpSorted []config.MCP) string {
292136 const logoHeightBreakpoint = 30
293137
294138 t := m.com.Styles
@@ -335,35 +179,37 @@ func (m *UI) buildSidebar(area uv.Rectangle, skillItems []skillStatusItem, mcpSo
335179 lspsCount := len(m.lspStates)
336180
337181 mcpsCount := 0
338− for _, mcpCfg := range mcpSorted {
182+ for _, mcpCfg := range m.com.Config().MCP.Sorted() {
339183 if _, ok := m.mcpStates[mcpCfg.Name]; ok {
340184 mcpsCount++
341185 }
342186 }
343187
344− skillsCount := len(skillItems)
188+ skillsCount := len(m.skillStatusItems())
345189
346190 maxFiles, maxLSPs, maxMCPs, maxSkills := getDynamicHeightLimits(remainingHeight, filesCount, lspsCount, mcpsCount, skillsCount)
347191
348192 lspSection := m.lspInfo(width, maxLSPs, true)
349− mcpSection := m.mcpInfoSorted(mcpSorted, width, maxMCPs, true)
350− skillsSection := m.skillsInfoItems(skillItems, width, maxSkills, true)
193+ mcpSection := m.mcpInfo(width, maxMCPs, true)
194+ skillsSection := m.skillsInfo(width, maxSkills, true)
351195 filesSection := m.filesInfo(m.com.Workspace.WorkingDir(), width, maxFiles, true)
352196
353− return lipgloss.NewStyle().
354− MaxWidth(width).
355− MaxHeight(height).
356− Render(
357− lipgloss.JoinVertical(
358− lipgloss.Left,
359− sidebarHeader,
360− filesSection,
361− "",
362− lspSection,
363− "",
364− mcpSection,
365− "",
366− skillsSection,
197+ uv.NewStyledString(
198+ lipgloss.NewStyle().
199+ MaxWidth(width).
200+ MaxHeight(height).
201+ Render(
202+ lipgloss.JoinVertical(
203+ lipgloss.Left,
204+ sidebarHeader,
205+ filesSection,
206+ "",
207+ lspSection,
208+ "",
209+ mcpSection,
210+ "",
211+ skillsSection,
212+ ),
367213 ),
368− )
214+ ).Draw(scr, area)
369215}
internal/ui/model/skills.go+5−15
@@ -28,14 +28,7 @@ var builtinSkillsCache struct {
2828
2929func cachedBuiltinSkills() []*skills.Skill {
3030 builtinSkillsCache.once.Do(func() {
31− builtin := skills.DiscoverBuiltin()
32− // Sort once here rather than on every skillStatusItems call: the
33− // returned slice is shared, so sorting it in place repeatedly is
34− // wasted work (and mutates shared state on the render hot path).
35− slices.SortStableFunc(builtin, func(a, b *skills.Skill) int {
36− return strings.Compare(a.Name, b.Name)
37− })
38− builtinSkillsCache.skills = builtin
31+ builtinSkillsCache.skills = skills.DiscoverBuiltin()
3932 })
4033 return builtinSkillsCache.skills
4134}
@@ -43,13 +36,6 @@ func cachedBuiltinSkills() []*skills.Skill {
4336// skillsInfo renders the skill discovery status section showing loaded and
4437// invalid skills.
4538func (m *UI) skillsInfo(width, maxItems int, isSection bool) string {
46− return m.skillsInfoItems(m.skillStatusItems(), width, maxItems, isSection)
47−}
48−
49−// skillsInfoItems renders the skill status section from an already-computed
50−// item list, letting callers on the sidebar render path reuse a single
51−// skillStatusItems() result instead of recomputing it.
52−func (m *UI) skillsInfoItems(items []skillStatusItem, width, maxItems int, isSection bool) string {
5339 t := m.com.Styles
5440
5541 title := t.Resource.Heading.Render("Skills")
@@ -57,6 +43,7 @@ func (m *UI) skillsInfoItems(items []skillStatusItem, width, maxItems int, isSec
5743 title = common.Section(t, title, width)
5844 }
5945
46+ items := m.skillStatusItems()
6047 if len(items) == 0 {
6148 list := t.Resource.AdditionalText.Render("None")
6249 return lipgloss.NewStyle().Width(width).Render(fmt.Sprintf("%s\n\n%s", title, list))
@@ -108,6 +95,9 @@ func (m *UI) skillStatusItems() []skillStatusItem {
10895 }
10996
11097 builtin := cachedBuiltinSkills()
98+ slices.SortStableFunc(builtin, func(a, b *skills.Skill) int {
99+ return strings.Compare(a.Name, b.Name)
100+ })
111101 for _, skill := range builtin {
112102 if _, ok := stateNames[skill.Name]; ok {
113103 continue
internal/ui/model/ui.go+89−316
@@ -38,7 +38,6 @@ import (
3838 "github.com/charmbracelet/crush/internal/fsext"
3939 "github.com/charmbracelet/crush/internal/history"
4040 "github.com/charmbracelet/crush/internal/home"
41− "github.com/charmbracelet/crush/internal/lsp"
4241 "github.com/charmbracelet/crush/internal/message"
4342 "github.com/charmbracelet/crush/internal/permission"
4443 "github.com/charmbracelet/crush/internal/pubsub"
@@ -175,38 +174,7 @@ type (
175174)
176175
177176// UI represents the main user interface model.
178−// renderCache holds the per-frame render memoization the UI consults on
179−// every draw: the sidebar block (drawSidebar re-renders fully each frame),
180−// the resolved large model (an RPC in client mode), and the pills block.
181−// Grouped out of the UI struct so the memoization layer is separable from
182−// model state.
183−type renderCache struct {
184− // sidebarView is the memoized sidebar block, keyed by sidebarCacheKey
185− // (a fingerprint of every input affecting its output).
186− sidebarView string
187− sidebarCacheKey uint64
188− hasSidebarCache bool
189− // sidebarFPBuf is a scratch buffer reused across frames to build the
190− // sidebar fingerprint without allocating (see sidebarFingerprint).
191− sidebarFPBuf []byte
192−
193− // largeModel caches the resolved selected large model — resolving it
194− // hits the agent coordinator (an RPC in client mode) — keyed by
195− // largeModelCacheKey. A nil result is never cached so the
196− // not-ready → ready transition is picked up on the next frame.
197− largeModel *workspace.AgentModel
198− largeModelCacheKey string
199−
200− // pillsView is the memoized pills block, keyed by pillsCacheKey.
201− pillsView string
202− pillsCacheKey string
203−}
204−
205177type UI struct {
206− // ctx is the program's lifecycle context (cancelled on SIGINT / program
207− // exit). Async workspace operations launched from tea.Cmd closures use
208− // it so they can be cancelled at teardown instead of orphaning.
209− ctx context.Context
210178 com *common.Common
211179 session *session.Session
212180 sessionFiles []SessionFile
@@ -230,15 +198,9 @@ type UI struct {
230198
231199 // themeKey identifies the currently applied theme so applyTheme can
232200 // skip the expensive style rebuild when switching to a provider that
233− // resolves to the same theme. It encodes both the provider theme family
234− // and the light/dark background mode.
201+ // resolves to the same theme.
235202 themeKey string
236203
237− // dark tracks the most recently detected terminal background mode (from
238− // tea.BackgroundColorMsg). It defaults to true and is only consulted
239− // when the configured theme is "auto"; see effectiveDark.
240− dark bool
241−
242204 focus uiFocusState
243205 state uiState
244206
@@ -251,12 +213,6 @@ type UI struct {
251213 // isCanceling tracks whether the user has pressed escape once to cancel.
252214 isCanceling bool
253215
254− // lastError holds the full text of the most recent error/warning status
255− // message. Error/warning notifications are truncated to a single line in
256− // the status bar and are not auto-expired; this retains the untruncated
257− // text so it can be copied to the clipboard (keyMap.CopyError).
258− lastError string
259−
260216 // bangMode tracks whether the editor is in bang (!) shell mode.
261217 bangMode bool
262218 bangWasEmpty bool // true when bang prompt became empty on last keystroke
@@ -309,13 +265,6 @@ type UI struct {
309265
310266 // lsp
311267 lspStates map[string]workspace.LSPClientInfo
312− // lspDiagnostics mirrors the per-LSP diagnostic severity counts
313− // locally. It is refreshed only when an LSP event arrives (the same
314− // events that already refresh lspStates), so the sidebar fingerprint
315− // — computed every frame — can read the split counts without issuing
316− // a per-LSP diagnostics RPC (which, in client mode, fetches and
317− // decodes every diagnostic).
318− lspDiagnostics map[string]lsp.DiagnosticCounts
319268
320269 // mcp
321270 mcpStates map[string]mcp.ClientInfo
@@ -326,10 +275,6 @@ type UI struct {
326275 // sidebarLogo keeps a cached version of the sidebar sidebarLogo.
327276 sidebarLogo string
328277
329− // renderCache holds the per-frame render memoization (sidebar, pills,
330− // and resolved large-model caches); see the renderCache type.
331− renderCache renderCache
332−
333278 // Notification state
334279 notifyBackend notification.Backend
335280 notifyWindowFocused bool
@@ -352,6 +297,7 @@ type UI struct {
352297 pillsAutoExpanded bool
353298 focusedPillSection pillSection
354299 promptQueue int
300+ pillsView string
355301
356302 // Todo spinner
357303 todoSpinner spinner.Model
@@ -372,7 +318,7 @@ type UI struct {
372318}
373319
374320// New creates a new instance of the [UI] model.
375−func New(ctx context.Context, com *common.Common, initialSessionID string, continueLast bool) *UI {
321+func New(com *common.Common, initialSessionID string, continueLast bool) *UI {
376322 // Editor components
377323 ta := textarea.New()
378324 ta.SetStyles(com.Styles.Editor.Textarea)
@@ -384,11 +330,7 @@ func New(ctx context.Context, com *common.Common, initialSessionID string, conti
384330 ta.MaxHeight = TextareaMaxHeight
385331 ta.Focus()
386332
387− scrollbarMode := config.ScrollbarDefault
388− if cfg := com.Config(); cfg.Options.TUI != nil && cfg.Options.TUI.Scrollbar != "" {
389− scrollbarMode = cfg.Options.TUI.Scrollbar
390− }
391− ch := NewChat(com, scrollbarMode)
333+ ch := NewChat(com)
392334
393335 keyMap := DefaultKeyMap()
394336
@@ -423,7 +365,6 @@ func New(ctx context.Context, com *common.Common, initialSessionID string, conti
423365 header := newHeader(com)
424366
425367 ui := &UI{
426− ctx: ctx,
427368 com: com,
428369 dialog: dialog.NewOverlay(),
429370 keyMap: keyMap,
@@ -434,7 +375,6 @@ func New(ctx context.Context, com *common.Common, initialSessionID string, conti
434375 attachments: attachments,
435376 todoSpinner: todoSpinner,
436377 lspStates: make(map[string]workspace.LSPClientInfo),
437− lspDiagnostics: make(map[string]lsp.DiagnosticCounts),
438378 mcpStates: make(map[string]mcp.ClientInfo),
439379 notifyBackend: notification.NoopBackend{},
440380 notifyWindowFocused: true,
@@ -447,17 +387,8 @@ func New(ctx context.Context, com *common.Common, initialSessionID string, conti
447387
448388 // Seed the active theme key from the large model provider so the
449389 // first model selection can correctly skip a redundant theme swap.
450− // DefaultCommon builds the dark theme at startup, so the mode is seeded
451− // dark; a tea.BackgroundColorMsg (or a forced "light" config) reconciles
452− // it to the light palette on the first frame.
453− ui.dark = true
454390 if cfg := com.Config(); cfg != nil {
455− // Seed with the dark key because DefaultCommon builds the dark theme;
456− // reconcileTheme applies the light palette on the first frame when
457− // the terminal reports a light background or "light" is forced.
458− ui.themeKey = styles.ThemeKeyForProviderMode(
459− cfg.Models[config.SelectedModelTypeLarge].Provider, true,
460− )
391+ ui.themeKey = styles.ThemeKeyForProvider(cfg.Models[config.SelectedModelTypeLarge].Provider)
461392 }
462393
463394 ui.setEditorPrompt(com.Workspace.PermissionSkipRequests())
@@ -495,9 +426,6 @@ func New(ctx context.Context, com *common.Common, initialSessionID string, conti
495426// Init initializes the UI model.
496427func (m *UI) Init() tea.Cmd {
497428 var cmds []tea.Cmd
498− // Ask the terminal for its background color so we can pick the light or
499− // dark palette (see the tea.BackgroundColorMsg handler in Update).
500− cmds = append(cmds, tea.RequestBackgroundColor)
501429 if m.state == uiOnboarding {
502430 if cmd := m.openModelsDialog(); cmd != nil {
503431 cmds = append(cmds, cmd)
@@ -527,7 +455,7 @@ func (m *UI) loadInitialSession() tea.Cmd {
527455 return m.loadSession(m.initialSessionID)
528456 case m.continueLastSession:
529457 return func() tea.Msg {
530− sessions, err := m.com.Workspace.ListSessions(m.ctx)
458+ sessions, err := m.com.Workspace.ListSessions(context.Background())
531459 if err != nil || len(sessions) == 0 {
532460 return nil
533461 }
@@ -609,32 +537,14 @@ func (m *UI) updateNotificationBackend() {
609537}
610538
611539// shouldSendNotification returns true if notifications should be sent based on
612−// current state. The window must not be focused and notifications must not be
613−// disabled in config. Focus reporting is only required in auto-detection mode:
614−// when the user explicitly chooses a notification style, we honor it even on
615−// terminals that don't support focus events (e.g. SSH sessions).
540+// current state. Focus reporting must be supported, window must not be
541+// focused, and notifications must not be disabled in config.
616542func (m *UI) shouldSendNotification() bool {
617543 cfg := m.com.Config()
618− var style string
619− if cfg != nil && cfg.Options != nil {
620− style = cfg.Options.NotificationStyle
621− }
622− if style == "disabled" {
544+ if cfg != nil && cfg.Options != nil && cfg.Options.NotificationStyle == "disabled" {
623545 return false
624546 }
625− // Never notify while the window is focused. On terminals without focus
626− // reporting, notifyWindowFocused stays false, so this doesn't suppress.
627− if m.notifyWindowFocused {
628− return false
629− }
630− // An explicit style (anything other than auto/empty) is honored regardless
631− // of focus event support.
632− if style != "" && style != "auto" {
633− return true
634− }
635− // Auto-detection mode: without focus events we can't suppress notifications
636− // while focused, so we disable them entirely to avoid spamming the user.
637− return m.caps.ReportFocusEvents
547+ return m.caps.ReportFocusEvents && !m.notifyWindowFocused
638548}
639549
640550// setState changes the UI state and focus.
@@ -657,7 +567,7 @@ func (m *UI) loadCustomCommands() tea.Cmd {
657567 slog.Error("Failed to load custom commands", "error", err)
658568 }
659569 // Append user-invocable skills as commands.
660− skillEntries, err := m.com.Workspace.ListSkills(m.ctx)
570+ skillEntries, err := m.com.Workspace.ListSkills(context.Background())
661571 if err != nil {
662572 slog.Error("Failed to load skill commands", "error", err)
663573 }
@@ -718,7 +628,7 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
718628 m.session = msg.session
719629 m.sessionFiles = msg.files
720630 cmds = append(cmds, m.startLSPs(msg.lspFilePaths()))
721− msgs, err := m.com.Workspace.ListMessages(m.ctx, m.session.ID)
631+ msgs, err := m.com.Workspace.ListMessages(context.Background(), m.session.ID)
722632 if err != nil {
723633 cmds = append(cmds, util.ReportError(err))
724634 break
@@ -858,9 +768,9 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
858768 case pubsub.Event[history.File]:
859769 cmds = append(cmds, m.handleFileEvent(msg.Payload))
860770 case pubsub.Event[app.LSPEvent]:
861− m.refreshLSPState()
771+ m.lspStates = m.com.Workspace.LSPGetStates()
862772 case pubsub.Event[workspace.LSPEvent]:
863− m.refreshLSPState()
773+ m.lspStates = m.com.Workspace.LSPGetStates()
864774 case pubsub.Event[skills.Event]:
865775 m.skillStates = msg.Payload.States
866776 case pubsub.Event[mcp.Event]:
@@ -882,7 +792,7 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
882792 cmds = append(cmds, cmd)
883793 }
884794 if cmd := m.sendNotification(notification.Notification{
885− Title: "Crush needs permission",
795+ Title: "Crush is waiting...",
886796 Message: fmt.Sprintf("Permission required to execute \"%s\"", msg.Payload.ToolName),
887797 }); cmd != nil {
888798 cmds = append(cmds, cmd)
@@ -898,17 +808,9 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
898808 m.sendProgressBar = xstrings.ContainsAnyOf(termVersion, "ghostty", "iterm2", "rio")
899809 }
900810 return m, nil
901− case tea.BackgroundColorMsg:
902− // The terminal reported its background color; switch to the light
903− // or dark palette to match (unless the theme is forced via config).
904− m.dark = msg.IsDark()
905− m.reconcileTheme()
906811 case tea.WindowSizeMsg:
907812 m.width, m.height = msg.Width, msg.Height
908813 m.updateLayoutAndSize()
909− // Fallback for terminals that never answer the background-color
910− // query: a forced "light"/"dark" config still takes effect here.
911− m.reconcileTheme()
912814 if m.state == uiChat && m.chat.Follow() {
913815 if cmd := m.chat.ScrollToBottomAndAnimate(); cmd != nil {
914816 cmds = append(cmds, cmd)
@@ -1059,10 +961,6 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
1059961 }
1060962 }
1061963 }
1062− case scrollbarHideMsg:
1063− if m.state == uiChat {
1064− m.chat.HideScrollbar(msg.seq)
1065− }
1066964 case spinner.TickMsg:
1067965 if m.dialog.HasDialogs() {
1068966 // route to dialog
@@ -1152,18 +1050,11 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
11521050 slog.Error("Error reported", "error", msg.Msg)
11531051 }
11541052 m.status.SetInfoMsg(msg)
1155− // Errors and warnings persist until replaced or dismissed (rather than
1156− // auto-expiring like info/success/update) so the user can read them and
1157− // copy the full text (ctrl+e). Retain the untruncated message for that.
1158− if msg.Type == util.InfoTypeError || msg.Type == util.InfoTypeWarn {
1159− m.lastError = msg.Msg
1160− } else {
1161− ttl := msg.TTL
1162− if ttl <= 0 {
1163− ttl = DefaultStatusTTL
1164− }
1165− cmds = append(cmds, clearInfoMsgCmd(ttl))
1053+ ttl := msg.TTL
1054+ if ttl <= 0 {
1055+ ttl = DefaultStatusTTL
11661056 }
1057+ cmds = append(cmds, clearInfoMsgCmd(ttl))
11671058 case app.UpdateAvailableMsg:
11681059 text := fmt.Sprintf("Crush update available: v%s → v%s.", msg.CurrentVersion, msg.LatestVersion)
11691060 if msg.IsDevelopment {
@@ -1228,23 +1119,25 @@ func (m *UI) setSessionMessages(msgs []message.Message) tea.Cmd {
12281119 msgPtrs[i] = &msgs[i]
12291120 }
12301121 toolResultMap := chat.BuildToolResultMap(msgPtrs)
1122+ if len(msgPtrs) > 0 {
1123+ m.lastUserMessageTime = msgPtrs[0].CreatedAt
1124+ }
12311125
12321126 // Add messages to chat with linked tool results
1233− diffTool := m.com.Config().Options.TUI.DiffTool
12341127 items := make([]chat.MessageItem, 0, len(msgs)*2)
12351128 for _, msg := range msgPtrs {
12361129 switch msg.Role {
12371130 case message.User:
12381131 m.lastUserMessageTime = msg.CreatedAt
1239− items = append(items, chat.ExtractMessageItems(m.com.Styles, msg, toolResultMap, diffTool)...)
1132+ items = append(items, chat.ExtractMessageItems(m.com.Styles, msg, toolResultMap)...)
12401133 case message.Assistant:
1241− items = append(items, chat.ExtractMessageItems(m.com.Styles, msg, toolResultMap, diffTool)...)
1134+ items = append(items, chat.ExtractMessageItems(m.com.Styles, msg, toolResultMap)...)
12421135 if msg.FinishPart() != nil && msg.FinishPart().Reason == message.FinishReasonEndTurn {
12431136 infoItem := chat.NewAssistantInfoItem(m.com.Styles, msg, m.com.Config(), time.Unix(m.lastUserMessageTime, 0))
12441137 items = append(items, infoItem)
12451138 }
12461139 default:
1247− items = append(items, chat.ExtractMessageItems(m.com.Styles, msg, toolResultMap, diffTool)...)
1140+ items = append(items, chat.ExtractMessageItems(m.com.Styles, msg, toolResultMap)...)
12481141 }
12491142 }
12501143
@@ -1261,10 +1154,8 @@ func (m *UI) setSessionMessages(msgs []message.Message) tea.Cmd {
12611154 }
12621155 }
12631156
1264− if cmd := m.chat.SetMessages(items...); cmd != nil {
1265− cmds = append(cmds, cmd)
1266− }
1267− if cmd := m.chat.RestartPausedVisibleAnimations(); cmd != nil {
1157+ m.chat.SetMessages(items...)
1158+ if cmd := m.chat.ScrollToBottomAndAnimate(); cmd != nil {
12681159 cmds = append(cmds, cmd)
12691160 }
12701161 m.chat.SelectLast()
@@ -1290,7 +1181,7 @@ func (m *UI) loadNestedToolCalls(items []chat.MessageItem) {
12901181 agentSessionID := m.com.Workspace.CreateAgentToolSessionID(messageID, tc.ID)
12911182
12921183 // Fetch nested messages.
1293− nestedMsgs, err := m.com.Workspace.ListMessages(m.ctx, agentSessionID)
1184+ nestedMsgs, err := m.com.Workspace.ListMessages(context.Background(), agentSessionID)
12941185 if err != nil || len(nestedMsgs) == 0 {
12951186 continue
12961187 }
@@ -1305,7 +1196,7 @@ func (m *UI) loadNestedToolCalls(items []chat.MessageItem) {
13051196 // Extract nested tool items.
13061197 var nestedTools []chat.ToolMessageItem
13071198 for _, nestedMsg := range nestedMsgPtrs {
1308− nestedItems := chat.ExtractMessageItems(m.com.Styles, nestedMsg, nestedToolResultMap, m.com.Config().Options.TUI.DiffTool)
1199+ nestedItems := chat.ExtractMessageItems(m.com.Styles, nestedMsg, nestedToolResultMap)
13091200 for _, nestedItem := range nestedItems {
13101201 if nestedToolItem, ok := nestedItem.(chat.ToolMessageItem); ok {
13111202 // Mark nested tools as simple (compact) rendering.
@@ -1355,7 +1246,7 @@ func (m *UI) appendSessionMessage(msg message.Message) tea.Cmd {
13551246 return nil
13561247 }
13571248 m.lastUserMessageTime = msg.CreatedAt
1358− items := chat.ExtractMessageItems(m.com.Styles, &msg, nil, m.com.Config().Options.TUI.DiffTool)
1249+ items := chat.ExtractMessageItems(m.com.Styles, &msg, nil)
13591250 for _, item := range items {
13601251 if animatable, ok := item.(chat.Animatable); ok {
13611252 if cmd := animatable.StartAnimation(); cmd != nil {
@@ -1368,7 +1259,7 @@ func (m *UI) appendSessionMessage(msg message.Message) tea.Cmd {
13681259 cmds = append(cmds, cmd)
13691260 }
13701261 case message.Assistant:
1371− items := chat.ExtractMessageItems(m.com.Styles, &msg, nil, m.com.Config().Options.TUI.DiffTool)
1262+ items := chat.ExtractMessageItems(m.com.Styles, &msg, nil)
13721263 for _, item := range items {
13731264 if animatable, ok := item.(chat.Animatable); ok {
13741265 if cmd := animatable.StartAnimation(); cmd != nil {
@@ -1477,7 +1368,7 @@ func (m *UI) updateSessionMessage(msg message.Message) tea.Cmd {
14771368 }
14781369 }
14791370 if existingToolItem == nil {
1480− items = append(items, chat.NewToolMessageItem(m.com.Styles, msg.ID, tc, nil, false, m.com.Config().Options.TUI.DiffTool))
1371+ items = append(items, chat.NewToolMessageItem(m.com.Styles, msg.ID, tc, nil, false))
14811372 }
14821373 }
14831374
@@ -1518,13 +1409,18 @@ func (m *UI) handleChildSessionMessage(event pubsub.Event[message.Message]) tea.
15181409
15191410 // Find the parent agent tool item.
15201411 var agentItem chat.NestedToolContainer
1521− if item := m.chat.MessageItem(toolCallID); item != nil {
1412+ for i := 0; i < m.chat.Len(); i++ {
1413+ item := m.chat.MessageItem(toolCallID)
1414+ if item == nil {
1415+ continue
1416+ }
15221417 if agent, ok := item.(chat.NestedToolContainer); ok {
15231418 if toolMessageItem, ok := item.(chat.ToolMessageItem); ok {
15241419 if toolMessageItem.ToolCall().ID == toolCallID {
15251420 // Verify this agent belongs to the correct parent message.
15261421 // We can't directly check parentMessageID on the item, so we trust the session parsing.
15271422 agentItem = agent
1423+ break
15281424 }
15291425 }
15301426 }
@@ -1549,7 +1445,7 @@ func (m *UI) handleChildSessionMessage(event pubsub.Event[message.Message]) tea.
15491445 }
15501446 if !found {
15511447 // Create a new nested tool item.
1552− nestedItem := chat.NewToolMessageItem(m.com.Styles, event.Payload.ID, tc, nil, false, m.com.Config().Options.TUI.DiffTool)
1448+ nestedItem := chat.NewToolMessageItem(m.com.Styles, event.Payload.ID, tc, nil, false)
15531449 if simplifiable, ok := nestedItem.(chat.Compactable); ok {
15541450 simplifiable.SetCompact(true)
15551451 }
@@ -1670,7 +1566,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
16701566 break
16711567 }
16721568 cmds = append(cmds, func() tea.Msg {
1673− err := m.com.Workspace.AgentSummarize(m.ctx, msg.SessionID)
1569+ err := m.com.Workspace.AgentSummarize(context.Background(), msg.SessionID)
16741570 if err != nil {
16751571 return util.ReportError(err)()
16761572 }
@@ -1716,9 +1612,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
17161612 if err := m.com.Workspace.UpdatePreferredModel(config.ScopeGlobal, agentCfg.Model, currentModel); err != nil {
17171613 return util.ReportError(err)()
17181614 }
1719− if err := m.com.Workspace.UpdateAgentModel(m.ctx); err != nil {
1720− slog.Warn("failed to update agent model", "error", err)
1721− }
1615+ m.com.Workspace.UpdateAgentModel(context.TODO())
17221616 status := "disabled"
17231617 if currentModel.Think {
17241618 status = "enabled"
@@ -1757,7 +1651,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
17571651 cmds = append(cmds, m.disableDockerMCP)
17581652 case dialog.ActionInitializeProject:
17591653 if m.isAgentBusy() {
1760− cmds = append(cmds, util.ReportWarn("Agent is busy, please wait before initializing project..."))
1654+ cmds = append(cmds, util.ReportWarn("Agent is busy, please wait before summarizing session..."))
17611655 break
17621656 }
17631657 cmds = append(cmds, m.initializeProject())
@@ -1793,9 +1687,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
17931687 }
17941688
17951689 cmds = append(cmds, func() tea.Msg {
1796− if err := m.com.Workspace.UpdateAgentModel(m.ctx); err != nil {
1797− slog.Warn("failed to update agent model", "error", err)
1798− }
1690+ m.com.Workspace.UpdateAgentModel(context.TODO())
17991691 return util.NewInfoMsg("Reasoning effort set to " + msg.Effort)
18001692 })
18011693 m.dialog.CloseDialog(dialog.ReasoningID)
@@ -1886,7 +1778,7 @@ func substituteArgs(content string, args map[string]string) string {
18861778// OAuth dialog opens.
18871779func (m *UI) refreshHyperAndRetrySelect(msg dialog.ActionSelectModel) tea.Cmd {
18881780 return func() tea.Msg {
1889− ctx, cancel := context.WithTimeout(m.ctx, 15*time.Second)
1781+ ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
18901782 defer cancel()
18911783 if err := m.com.Workspace.RefreshOAuthToken(ctx, config.ScopeGlobal, "hyper"); err != nil {
18921784 slog.Warn("Hyper OAuth refresh failed, requesting re-auth", "error", err)
@@ -1921,7 +1813,7 @@ func (m *UI) fetchHyperCredits() tea.Cmd {
19211813 }
19221814
19231815 if providerCfg.OAuthToken != nil && providerCfg.OAuthToken.IsExpired() {
1924− ctxRefresh, cancelRefresh := context.WithTimeout(m.ctx, 15*time.Second)
1816+ ctxRefresh, cancelRefresh := context.WithTimeout(context.Background(), 15*time.Second)
19251817 defer cancelRefresh()
19261818 if err := m.com.Workspace.RefreshOAuthToken(ctxRefresh, config.ScopeGlobal, hyper.Name); err != nil {
19271819 slog.Warn("Hyper OAuth refresh failed before fetching credits, trying with existing token", "error", err)
@@ -1930,7 +1822,7 @@ func (m *UI) fetchHyperCredits() tea.Cmd {
19301822 }
19311823 }
19321824
1933− ctx, cancel := context.WithTimeout(m.ctx, 10*time.Second)
1825+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
19341826 defer cancel()
19351827 credits, err := hyper.FetchCredits(ctx, apiKey)
19361828 if err != nil {
@@ -2006,7 +1898,7 @@ func (m *UI) handleSelectModel(msg dialog.ActionSelectModel) tea.Cmd {
20061898 }
20071899
20081900 cmds = append(cmds, func() tea.Msg {
2009− if err := m.com.Workspace.UpdateAgentModel(m.ctx); err != nil {
1901+ if err := m.com.Workspace.UpdateAgentModel(context.TODO()); err != nil {
20101902 return util.ReportError(err)
20111903 }
20121904
@@ -2029,7 +1921,7 @@ func (m *UI) handleSelectModel(msg dialog.ActionSelectModel) tea.Cmd {
20291921 if isOnboarding {
20301922 m.setState(uiLanding, uiFocusEditor)
20311923 m.com.Config().SetupAgents()
2032− if err := m.com.Workspace.InitCoderAgent(m.ctx); err != nil {
1924+ if err := m.com.Workspace.InitCoderAgent(context.TODO()); err != nil {
20331925 cmds = append(cmds, util.ReportError(err))
20341926 }
20351927 } else if m.com.IsHyper() {
@@ -2131,12 +2023,6 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
21312023 }
21322024 cmds = append(cmds, util.ReportInfo("Yolo mode "+status))
21332025 return true
2134− case key.Matches(msg, m.keyMap.CopyError):
2135− if m.lastError == "" {
2136− return false
2137− }
2138− cmds = append(cmds, common.CopyToClipboard(m.lastError, "Error copied to clipboard"))
2139− return true
21402026 }
21412027 return false
21422028 }
@@ -2503,13 +2389,6 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
25032389
25042390// drawHeader draws the header section of the UI.
25052391func (m *UI) drawHeader(scr uv.Screen, area uv.Rectangle) {
2506− // Sum diagnostics from the local LSP mirror instead of an
2507− // LSPGetStates RPC per frame; m.lspStates is refreshed on every LSP
2508− // event, so the total changes exactly when the header would.
2509− lspErrorCount := 0
2510− for _, info := range m.lspStates {
2511− lspErrorCount += info.DiagnosticCount
2512− }
25132392 m.header.drawHeader(
25142393 scr,
25152394 area,
@@ -2518,7 +2397,6 @@ func (m *UI) drawHeader(scr uv.Screen, area uv.Rectangle) {
25182397 m.detailsOpen,
25192398 area.Dx(),
25202399 m.hyperCredits,
2521− lspErrorCount,
25222400 )
25232401}
25242402
@@ -2570,8 +2448,8 @@ func (m *UI) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
25702448 }
25712449
25722450 m.chat.Draw(scr, layout.main)
2573− if layout.pills.Dy() > 0 && m.renderCache.pillsView != "" {
2574− uv.NewStyledString(m.renderCache.pillsView).Draw(scr, layout.pills)
2451+ if layout.pills.Dy() > 0 && m.pillsView != "" {
2452+ uv.NewStyledString(m.pillsView).Draw(scr, layout.pills)
25752453 }
25762454
25772455 editorWidth := scr.Bounds().Dx()
@@ -2665,36 +2543,16 @@ func (m *UI) View() tea.View {
26652543 canvas := uv.NewScreenBuffer(m.width, m.height)
26662544 v.Cursor = m.Draw(canvas, canvas.Bounds())
26672545
2668− // Single pass over the rendered screen: normalize \r\n → \n and trim
2669− // trailing spaces per line, writing straight into one builder instead
2670− // of allocating a replaced copy, a line slice, and a joined copy.
2671− rendered := canvas.Render()
2672− var sb strings.Builder
2673− sb.Grow(len(rendered))
2674− start := 0
2675− for i := 0; i <= len(rendered); i++ {
2676− if i < len(rendered) && rendered[i] != '\n' {
2677− continue
2678− }
2679− line := rendered[start:i]
2680− // Drop a single trailing '\r' immediately before the newline
2681− // (the \r\n → \n normalization).
2682− if len(line) > 0 && line[len(line)-1] == '\r' {
2683− line = line[:len(line)-1]
2684− }
2685− // Trim trailing spaces only (matches strings.TrimRight(line, " ")).
2686− end := len(line)
2687− for end > 0 && line[end-1] == ' ' {
2688− end--
2689− }
2690− sb.WriteString(line[:end])
2691− if i < len(rendered) {
2692− sb.WriteByte('\n')
2693− }
2694− start = i + 1
2546+ content := strings.ReplaceAll(canvas.Render(), "\r\n", "\n") // normalize newlines
2547+ contentLines := strings.Split(content, "\n")
2548+ for i, line := range contentLines {
2549+ // Trim trailing spaces for concise rendering
2550+ contentLines[i] = strings.TrimRight(line, " ")
26952551 }
26962552
2697− v.Content = sb.String()
2553+ content = strings.Join(contentLines, "\n")
2554+
2555+ v.Content = content
26982556 if m.progressBarEnabled && m.sendProgressBar && m.isAgentBusy() {
26992557 // HACK: use a random percentage to prevent ghostty from hiding it
27002558 // after a timeout.
@@ -2748,9 +2606,6 @@ func (m *UI) ShortHelp() []key.Binding {
27482606 binds,
27492607 k.Editor.Newline,
27502608 )
2751− if m.textarea.Value() == "" {
2752− binds = append(binds, k.Editor.ShellCommand)
2753− }
27542609 case uiFocusMain:
27552610 binds = append(
27562611 binds,
@@ -2764,16 +2619,8 @@ func (m *UI) ShortHelp() []key.Binding {
27642619 binds = append(binds, k.Chat.PillLeft)
27652620 }
27662621 }
2767− case uiLanding:
2768− binds = append(
2769− binds,
2770− tab,
2771− commands,
2772− k.Models,
2773− k.Editor.Newline,
2774− k.Chat.NewSession,
2775− )
27762622 default:
2623+ // TODO: other states
27772624 // if m.session == nil {
27782625 // no session selected
27792626 binds = append(
@@ -2784,10 +2631,6 @@ func (m *UI) ShortHelp() []key.Binding {
27842631 )
27852632 }
27862633
2787− if m.lastError != "" {
2788− binds = append(binds, k.CopyError)
2789− }
2790−
27912634 binds = append(
27922635 binds,
27932636 k.Quit,
@@ -2860,9 +2703,6 @@ func (m *UI) FullHelp() [][]key.Binding {
28602703 if m.currentModelSupportsImages() {
28612704 editorBinds = append(editorBinds, k.Editor.AddImage, k.Editor.PasteImage)
28622705 }
2863− if m.textarea.Value() == "" {
2864− editorBinds = append(editorBinds, k.Editor.ShellCommand)
2865− }
28662706 binds = append(binds, editorBinds)
28672707 if hasAttachments {
28682708 binds = append(
@@ -2898,38 +2738,6 @@ func (m *UI) FullHelp() [][]key.Binding {
28982738 binds = append(binds, []key.Binding{k.Chat.PillLeft})
28992739 }
29002740 }
2901− case uiLanding:
2902− binds = append(
2903− binds,
2904− []key.Binding{
2905− commands,
2906− k.Models,
2907− k.Sessions,
2908− k.ToggleYolo,
2909− },
2910− )
2911− editorBinds := []key.Binding{
2912− k.Editor.Newline,
2913− k.Editor.MentionFile,
2914− k.Editor.OpenEditor,
2915− }
2916− if m.currentModelSupportsImages() {
2917− editorBinds = append(editorBinds, k.Editor.AddImage, k.Editor.PasteImage)
2918− }
2919− if m.textarea.Value() == "" {
2920− editorBinds = append(editorBinds, k.Editor.ShellCommand)
2921− }
2922− binds = append(binds, editorBinds)
2923− if hasAttachments {
2924− binds = append(
2925− binds,
2926− []key.Binding{
2927− k.Editor.AttachmentDeleteMode,
2928− k.Editor.DeleteAllAttachments,
2929− k.Editor.Escape,
2930− },
2931− )
2932− }
29332741 default:
29342742 if m.session == nil {
29352743 // no session selected
@@ -2950,9 +2758,6 @@ func (m *UI) FullHelp() [][]key.Binding {
29502758 if m.currentModelSupportsImages() {
29512759 editorBinds = append(editorBinds, k.Editor.AddImage, k.Editor.PasteImage)
29522760 }
2953− if m.textarea.Value() == "" {
2954− editorBinds = append(editorBinds, k.Editor.ShellCommand)
2955− }
29562761 binds = append(binds, editorBinds)
29572762 if hasAttachments {
29582763 binds = append(
@@ -2967,11 +2772,13 @@ func (m *UI) FullHelp() [][]key.Binding {
29672772 }
29682773 }
29692774
2970− lastRow := []key.Binding{help, k.Quit}
2971− if m.lastError != "" {
2972− lastRow = append([]key.Binding{k.CopyError}, lastRow...)
2973− }
2974− binds = append(binds, lastRow)
2775+ binds = append(
2776+ binds,
2777+ []key.Binding{
2778+ help,
2779+ k.Quit,
2780+ },
2781+ )
29752782
29762783 return binds
29772784}
@@ -3117,8 +2924,7 @@ func (m *UI) generateLayout(w, h int) uiLayout {
31172924 appRect.Min.X += 1
31182925 appRect.Max.X -= 1
31192926
3120− switch m.state {
3121− case uiOnboarding, uiInitialize, uiLanding:
2927+ if slices.Contains([]uiState{uiOnboarding, uiInitialize, uiLanding}, m.state) {
31222928 // extra padding on left and right for these states
31232929 appRect.Min.X += 1
31242930 appRect.Max.X -= 1
@@ -3307,9 +3113,8 @@ func (m *UI) openEditor(value string) tea.Cmd {
33073113 return util.ReportError(err)
33083114 }
33093115 tmpPath := tmpfile.Name()
3310− defer tmpfile.Close()
3116+ defer tmpfile.Close() //nolint:errcheck
33113117 if _, err := tmpfile.WriteString(value); err != nil {
3312− _ = os.Remove(tmpPath)
33133118 return util.ReportError(err)
33143119 }
33153120 cmd, err := editor.Command(
@@ -3321,7 +3126,6 @@ func (m *UI) openEditor(value string) tea.Cmd {
33213126 ),
33223127 )
33233128 if err != nil {
3324− _ = os.Remove(tmpPath)
33253129 return util.ReportError(err)
33263130 }
33273131 return tea.ExecProcess(cmd, func(err error) tea.Msg {
@@ -3447,9 +3251,9 @@ func (m *UI) insertFileCompletion(path string) tea.Cmd {
34473251
34483252 if m.hasSession() {
34493253 // Skip attachment if file was already read and hasn't been modified.
3450− lastRead := m.com.Workspace.FileTrackerLastReadTime(m.ctx, m.session.ID, absPath)
3254+ lastRead := m.com.Workspace.FileTrackerLastReadTime(context.Background(), m.session.ID, absPath)
34513255 if !lastRead.IsZero() {
3452− if info, err := os.Stat(absPath); err == nil && !info.ModTime().After(lastRead) {
3256+ if info, err := os.Stat(path); err == nil && !info.ModTime().After(lastRead) {
34533257 return nil
34543258 }
34553259 }
@@ -3460,14 +3264,14 @@ func (m *UI) insertFileCompletion(path string) tea.Cmd {
34603264 m.sessionFileReads = append(m.sessionFileReads, absPath)
34613265
34623266 // Add file as attachment.
3463− content, err := os.ReadFile(absPath)
3267+ content, err := os.ReadFile(path)
34643268 if err != nil {
34653269 // If it fails, let the LLM handle it later.
34663270 return nil
34673271 }
34683272
34693273 return message.Attachment{
3470− FilePath: absPath,
3274+ FilePath: path,
34713275 FileName: filepath.Base(path),
34723276 MimeType: mimeOf(content),
34733277 Content: content,
@@ -3489,7 +3293,7 @@ func (m *UI) insertMCPResourceCompletion(item completions.ResourceCompletionValu
34893293
34903294 resourceCmd := func() tea.Msg {
34913295 contents, err := m.com.Workspace.ReadMCPResource(
3492− m.ctx,
3296+ context.Background(),
34933297 item.MCPName,
34943298 item.URI,
34953299 )
@@ -3624,40 +3428,12 @@ func (m *UI) cacheSidebarLogo(width int) {
36243428// invalidating the markdown renderer cache and re-rendering the entire
36253429// transcript for no visible change.
36263430func (m *UI) applyThemeForProvider(providerID string) {
3627− dark := m.effectiveDark()
3628− key := styles.ThemeKeyForProviderMode(providerID, dark)
3431+ key := styles.ThemeKeyForProvider(providerID)
36293432 if key == m.themeKey {
36303433 return
36313434 }
36323435 m.themeKey = key
3633− m.applyTheme(styles.ThemeForProviderMode(providerID, dark))
3634−}
3635−
3636−// effectiveDark reports whether the dark palette should be used. A "dark"
3637−// or "light" theme setting forces the mode; "auto" (the default) follows
3638−// the detected terminal background (m.dark).
3639−func (m *UI) effectiveDark() bool {
3640− if cfg := m.com.Config(); cfg != nil && cfg.Options != nil && cfg.Options.TUI != nil {
3641− switch cfg.Options.TUI.Theme {
3642− case "light":
3643− return false
3644− case "dark":
3645− return true
3646− }
3647− }
3648− return m.dark
3649−}
3650−
3651−// reconcileTheme re-selects the active theme for the current large-model
3652−// provider and background mode, applying it only when it differs from the
3653−// theme already in effect. It is safe to call on every background-color or
3654−// window-size event.
3655−func (m *UI) reconcileTheme() {
3656− providerID := ""
3657− if cfg := m.com.Config(); cfg != nil {
3658− providerID = cfg.Models[config.SelectedModelTypeLarge].Provider
3659− }
3660− m.applyThemeForProvider(providerID)
3436+ m.applyTheme(styles.ThemeForProvider(providerID))
36613437}
36623438
36633439// applyTheme replaces the active styles with the given theme, drops the
@@ -3698,7 +3474,7 @@ func (m *UI) refreshStyles() {
36983474// return one.
36993475func (m *UI) attachSkill(skillID, name string) tea.Cmd {
37003476 return func() tea.Msg {
3701− content, result, err := m.com.Workspace.ReadSkill(m.ctx, skillID)
3477+ content, result, err := m.com.Workspace.ReadSkill(context.Background(), skillID)
37023478 if err != nil {
37033479 return util.NewErrorMsg(err)
37043480 }
@@ -3723,7 +3499,7 @@ func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea.
37233499
37243500 var cmds []tea.Cmd
37253501 if !m.hasSession() {
3726− newSession, err := m.com.Workspace.CreateSession(m.ctx, "New Session")
3502+ newSession, err := m.com.Workspace.CreateSession(context.Background(), "New Session")
37273503 if err != nil {
37283504 return util.ReportError(err)
37293505 }
@@ -3737,7 +3513,7 @@ func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea.
37373513 m.setState(uiChat, m.focus)
37383514 }
37393515
3740− ctx := m.ctx
3516+ ctx := context.Background()
37413517 cmds = append(cmds, func() tea.Msg {
37423518 for _, path := range m.sessionFileReads {
37433519 m.com.Workspace.FileTrackerRecordRead(ctx, m.session.ID, path)
@@ -3753,7 +3529,7 @@ func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea.
37533529 // been accepted (HTTP 202) or synchronously with a validation
37543530 // or transport error. Run failures and cancellation surface
37553531 // through SSE-derived events, not this return value.
3756− err := m.com.Workspace.AgentRun(m.ctx, sessionID, content, attachments...)
3532+ err := m.com.Workspace.AgentRun(context.Background(), sessionID, content, attachments...)
37573533 if err != nil {
37583534 return util.InfoMsg{
37593535 Type: util.InfoTypeError,
@@ -3777,7 +3553,7 @@ func (m *UI) runShellCommand(command string) tea.Cmd {
37773553func (m *UI) runShellCommandInternal(command string, isFirstMessage bool) tea.Cmd {
37783554 var cmds []tea.Cmd
37793555 if !m.hasSession() {
3780− newSession, err := m.com.Workspace.CreateSession(m.ctx, "New Session")
3556+ newSession, err := m.com.Workspace.CreateSession(context.Background(), "New Session")
37813557 if err != nil {
37823558 return util.ReportError(err)
37833559 }
@@ -3830,7 +3606,7 @@ func (m *UI) runShellCommandInternal(command string, isFirstMessage bool) tea.Cm
38303606 return shellStreamMsg{PendingID: pendingID, Chunk: chunk, streamCh: streamCh}
38313607 })
38323608
3833− ctx, cancel := context.WithCancel(m.ctx)
3609+ ctx, cancel := context.WithCancel(context.Background())
38343610 m.bangCancel = cancel
38353611
38363612 cmds = append(cmds, func() tea.Msg {
@@ -4117,8 +3893,8 @@ func (m *UI) handleAgentNotification(n notify.Notification) tea.Cmd {
41173893 case notify.TypeAgentFinished:
41183894 var cmds []tea.Cmd
41193895 cmds = append(cmds, m.sendNotification(notification.Notification{
4120− Title: "Crush finished",
4121− Message: fmt.Sprintf("Turn complete — %s", n.SessionTitle),
3896+ Title: "Crush is waiting...",
3897+ Message: fmt.Sprintf("Agent's turn completed in \"%s\"", n.SessionTitle),
41223898 }))
41233899 if m.com.IsHyper() {
41243900 cmds = append(cmds, m.fetchHyperCredits())
@@ -4165,14 +3941,12 @@ func (m *UI) newSession() tea.Cmd {
41653941 m.pillsExpanded = false
41663942 m.pillsAutoExpanded = false
41673943 m.promptQueue = 0
4168− m.renderCache.pillsView = ""
4169− m.renderCache.pillsCacheKey = ""
4170− m.renderCache.hasSidebarCache = false
3944+ m.pillsView = ""
41713945 m.historyReset()
41723946 agenttools.ResetCache()
41733947 return tea.Batch(
41743948 func() tea.Msg {
4175− m.com.Workspace.LSPStopAll(m.ctx)
3949+ m.com.Workspace.LSPStopAll(context.Background())
41763950 return nil
41773951 },
41783952 m.loadPromptHistory(),
@@ -4467,7 +4241,8 @@ func (m *UI) runMCPPrompt(clientID, promptID string, arguments map[string]string
44674241 load := func() tea.Msg {
44684242 prompt, err := m.com.Workspace.GetMCPPrompt(clientID, promptID, arguments)
44694243 if err != nil {
4470− return util.ReportError(fmt.Errorf("failed to load MCP prompt %q from %q: %w", promptID, clientID, err))()
4244+ // TODO: make this better
4245+ return util.ReportError(err)()
44714246 }
44724247
44734248 if prompt == "" {
@@ -4491,9 +4266,7 @@ func (m *UI) runMCPPrompt(clientID, promptID string, arguments map[string]string
44914266
44924267func (m *UI) handleStateChanged() tea.Cmd {
44934268 return func() tea.Msg {
4494− if err := m.com.Workspace.UpdateAgentModel(m.ctx); err != nil {
4495− slog.Warn("failed to update agent model", "error", err)
4496− }
4269+ m.com.Workspace.UpdateAgentModel(context.Background())
44974270 return mcpStateChangedMsg{
44984271 states: m.com.Workspace.MCPGetStates(),
44994272 }
@@ -4534,7 +4307,7 @@ func (m *UI) copyChatHighlight() tea.Cmd {
45344307}
45354308
45364309func (m *UI) enableDockerMCP() tea.Msg {
4537− ctx := m.ctx
4310+ ctx := context.Background()
45384311 if err := m.com.Workspace.EnableDockerMCP(ctx); err != nil {
45394312 return util.ReportError(err)()
45404313 }
internal/ui/styles/grad.go+2−51
@@ -2,52 +2,13 @@ package styles
22
33import (
44 "fmt"
5− "hash/fnv"
65 "image/color"
76 "strings"
8− "sync"
97
108 "charm.land/lipgloss/v2"
119 "github.com/rivo/uniseg"
1210)
1311
14−// gradCache memoizes the per-cluster rendered output of ForegroundGrad. The
15−// function walks grapheme clusters (uniseg), blends a color ramp and renders
16−// each cluster on every call, and it is invoked every frame by dialog titles
17−// and similar constant text. Results are keyed by a fingerprint of every input
18−// that affects the output.
19−var gradCache sync.Map // map[uint64][]string
20−
21−// gradKey derives a stable cache key from all inputs that affect the rendered
22−// gradient output.
23−func gradKey(base lipgloss.Style, input string, bold bool, color1, color2 color.Color) uint64 {
24− h := fnv.New64a()
25− h.Write([]byte(input))
26− h.Write([]byte{0})
27− writeColor := func(c color.Color) {
28− var buf [8]byte
29− if c != nil {
30− r, g, b, a := c.RGBA()
31− buf[0] = byte(r >> 8)
32− buf[1] = byte(g >> 8)
33− buf[2] = byte(b >> 8)
34− buf[3] = byte(a >> 8)
35− }
36− h.Write(buf[:4])
37− }
38− writeColor(color1)
39− writeColor(color2)
40− if bold {
41− h.Write([]byte{1})
42− } else {
43− h.Write([]byte{0})
44− }
45− // base carries the remaining style attributes (e.g. background); include a
46− // fingerprint so distinct bases never collide.
47− fmt.Fprintf(h, "%v", base)
48− return h.Sum64()
49−}
50−
5112// ForegroundGrad returns a slice of strings representing the input string
5213// rendered with a horizontal gradient foreground from color1 to color2. Each
5314// string in the returned slice corresponds to a grapheme cluster in the input
@@ -56,20 +17,10 @@ func ForegroundGrad(base lipgloss.Style, input string, bold bool, color1, color2
5617 if input == "" {
5718 return []string{""}
5819 }
59− key := gradKey(base, input, bold, color1, color2)
60− if cached, ok := gradCache.Load(key); ok {
61− return cached.([]string)
62− }
63− result := foregroundGrad(base, input, bold, color1, color2)
64− gradCache.Store(key, result)
65− return result
66−}
67−
68−func foregroundGrad(base lipgloss.Style, input string, bold bool, color1, color2 color.Color) []string {
6920 if len(input) == 1 {
7021 style := base.Foreground(color1)
7122 if bold {
72− style = style.Bold(true)
23+ style.Bold(true)
7324 }
7425 return []string{style.Render(input)}
7526 }
@@ -83,7 +34,7 @@ func foregroundGrad(base lipgloss.Style, input string, bold bool, color1, color2
8334 for i, c := range ramp {
8435 style := base.Foreground(c)
8536 if bold {
86− style = style.Bold(true)
37+ style.Bold(true)
8738 }
8839 clusters[i] = style.Render(clusters[i])
8940 }
internal/ui/styles/quickstyle.go+21−7
@@ -550,12 +550,26 @@ func quickStyle(o quickStyleOpts) Styles {
550550 Foreground(o.fgMoreSubtle).
551551 Background(o.bgBase),
552552 },
553− // Insert/delete colors are derived from the semantic palette (green
554− // for inserts, red for deletes) washed over the base background, so a
555− // future light theme adapts without hardcoded hex. Code foreground is
556− // left unset to inherit the syntax highlighting.
557− InsertLine: diffview.ChangeLineStyle(o.success, o.bgBase, nil),
558− DeleteLine: diffview.ChangeLineStyle(o.error, o.bgBase, nil),
553+ InsertLine: diffview.LineStyle{
554+ LineNumber: lipgloss.NewStyle().
555+ Foreground(lipgloss.Color("#629657")).
556+ Background(lipgloss.Color("#2b322a")),
557+ Symbol: lipgloss.NewStyle().
558+ Foreground(lipgloss.Color("#629657")).
559+ Background(lipgloss.Color("#323931")),
560+ Code: lipgloss.NewStyle().
561+ Background(lipgloss.Color("#323931")),
562+ },
563+ DeleteLine: diffview.LineStyle{
564+ LineNumber: lipgloss.NewStyle().
565+ Foreground(lipgloss.Color("#a45c59")).
566+ Background(lipgloss.Color("#312929")),
567+ Symbol: lipgloss.NewStyle().
568+ Foreground(lipgloss.Color("#a45c59")).
569+ Background(lipgloss.Color("#383030")),
570+ Code: lipgloss.NewStyle().
571+ Background(lipgloss.Color("#383030")),
572+ },
559573 Filename: diffview.LineStyle{
560574 LineNumber: lipgloss.NewStyle().
561575 Foreground(o.fgSubtle).
@@ -604,7 +618,7 @@ func quickStyle(o quickStyleOpts) Styles {
604618 s.Tool.IconPending = base.Foreground(o.successMostSubtle).SetString(ToolPending)
605619 s.Tool.IconSuccess = base.Foreground(o.success).SetString(ToolSuccess)
606620 s.Tool.IconError = base.Foreground(o.error).SetString(ToolError)
607− s.Tool.IconCancelled = muted.SetString(ToolCancelled)
621+ s.Tool.IconCancelled = muted.SetString(ToolPending)
608622
609623 s.Tool.NameNormal = base.Foreground(o.info)
610624 s.Tool.NameNested = base.Foreground(o.info)
internal/ui/styles/styles.go+3−4
@@ -25,10 +25,9 @@ const (
2525
2626 ArrowRightIcon string = "→"
2727
28− ToolPending string = "●"
29− ToolSuccess string = "✓"
30− ToolError string = "×"
31− ToolCancelled string = "⊘"
28+ ToolPending string = "●"
29+ ToolSuccess string = "✓"
30+ ToolError string = "×"
3231
3332 RadioOn string = "◉"
3433 RadioOff string = "○"
internal/ui/styles/themes.go+12−131
@@ -19,76 +19,22 @@ func ThemeKeyForProvider(providerID string) string {
1919 }
2020}
2121
22−// ThemeForProvider returns the dark Styles associated with the given
23−// provider ID. Unknown or empty provider IDs yield the default Charmtone
24−// Pantera theme. Retained for callers that always want the dark theme
25−// (e.g. the non-interactive CLI); the interactive TUI uses the
26−// background-aware [ThemeForProviderMode].
22+// ThemeForProvider returns the Styles associated with the given provider
23+// ID. Unknown or empty provider IDs yield the default Charmtone Pantera
24+// theme.
2725func ThemeForProvider(providerID string) Styles {
28− return ThemeForProviderMode(providerID, true)
29−}
30−
31−// ThemeKeyForProviderMode returns a stable identifier for the theme
32−// associated with the given provider ID and background mode (dark/light).
33−// The key incorporates the mode so callers can detect when a background
34−// change would actually alter the active theme and skip the rebuild
35−// otherwise.
36−func ThemeKeyForProviderMode(providerID string, dark bool) string {
37− base := ThemeKeyForProvider(providerID)
38− if dark {
39− return base + "-dark"
40− }
41− return base + "-light"
42−}
43−
44−// ThemeForProviderMode returns the Styles for the given provider ID in the
45−// requested background mode. When dark is false a light-background palette
46−// is returned so the UI stays legible on light terminals.
47−func ThemeForProviderMode(providerID string, dark bool) Styles {
4826 switch ThemeKeyForProvider(providerID) {
4927 case "hyper":
50− if dark {
51− return HypercrushObsidiana()
52− }
53− return HypercrushLatte()
28+ return HypercrushObsidiana()
5429 default:
55− if dark {
56− return CharmtonePantera()
57− }
58− return CharmtoneLatte()
30+ return CharmtonePantera()
5931 }
6032}
6133
6234// CharmtonePantera returns the Charmtone dark theme. It's the default style
6335// for the UI.
6436func CharmtonePantera() Styles {
65− return buildCharmtone(charmtoneDarkOpts())
66−}
67−
68−// CharmtoneLatte returns the Charmtone light theme, used on light-background
69−// terminals. It mirrors the dark theme's structure but inverts the neutral
70−// ramp and swaps the neon accent/semantic colors for darker shades that stay
71−// legible on a light background.
72−//
73−// NOTE: this palette is a first pass tuned by hand; the exact accent/semantic
74−// color choices are worth a visual review on real light terminals.
75−func CharmtoneLatte() Styles {
76− return buildCharmtone(charmtoneLightOpts())
77−}
78−
79−// HypercrushObsidiana returns the Hypercrush dark theme.
80−func HypercrushObsidiana() Styles {
81− return CharmtonePantera()
82−}
83−
84−// HypercrushLatte returns the Hypercrush light theme.
85−func HypercrushLatte() Styles {
86− return CharmtoneLatte()
87−}
88−
89−// charmtoneDarkOpts returns the color options for the Charmtone dark theme.
90−func charmtoneDarkOpts() quickStyleOpts {
91− return quickStyleOpts{
37+ s := quickStyle(quickStyleOpts{
9238 primary: charmtone.Charple,
9339 secondary: charmtone.Dolly,
9440 accent: charmtone.Bok,
@@ -140,77 +86,7 @@ func charmtoneDarkOpts() quickStyleOpts {
14086 ansiBrightMagenta: charmtone.Blush,
14187 ansiBrightCyan: charmtone.Sardine,
14288 ansiBrightWhite: charmtone.Salt,
143− }
144−}
145−
146−// charmtoneLightOpts returns the color options for the Charmtone light theme.
147−// The neutral ramp is inverted (light backgrounds, dark foregrounds) and the
148−// neon accents/semantics from the dark theme are replaced with darker,
149−// higher-contrast shades so text stays readable on a light background.
150−func charmtoneLightOpts() quickStyleOpts {
151− return quickStyleOpts{
152− primary: charmtone.Charple, // saturated purple reads on white
153− secondary: charmtone.Grape,
154− accent: charmtone.Zinc,
155− keyword: charmtone.Prince,
156−
157− // Foregrounds: dark on light, decreasing visibility.
158− fgBase: charmtone.Pepper,
159− fgSubtle: charmtone.Iron,
160− fgMoreSubtle: charmtone.Oyster,
161− fgMostSubtle: charmtone.Steam,
162−
163− onPrimary: charmtone.Butter, // light text on the purple primary
164−
165− // Backgrounds: light base, increasingly visible (darker) greys.
166− bgBase: charmtone.Salt,
167− bgLeastVisible: charmtone.Sash,
168− bgLessVisible: charmtone.Steep,
169− bgMostVisible: charmtone.Smoke,
170−
171− separator: charmtone.Steep,
172−
173− destructive: charmtone.Coral,
174− error: charmtone.Sriracha,
175− warningSubtle: charmtone.Yam,
176− warning: charmtone.Tang,
177− denied: charmtone.Paprika,
178− busy: charmtone.Cumin,
179− info: charmtone.Damson,
180− infoMoreSubtle: charmtone.Malibu,
181− infoMostSubtle: charmtone.Anchovy,
182− success: charmtone.Pickle,
183− successMoreSubtle: charmtone.Guac,
184− successMostSubtle: charmtone.Zinc,
185−
186− // ANSI 16-color palette tuned for legibility on a light background:
187− // normal colors are darker/saturated, brights slightly lighter.
188− ansiBlack: charmtone.Pepper,
189− ansiRed: charmtone.Pom,
190− ansiGreen: charmtone.Pickle,
191− ansiYellow: charmtone.Cumin,
192− ansiBlue: charmtone.Oceania,
193− ansiMagenta: charmtone.Grape,
194− ansiCyan: charmtone.Damson,
195− ansiWhite: charmtone.Oyster,
196−
197− ansiBrightBlack: charmtone.Steam,
198− ansiBrightRed: charmtone.Sriracha,
199− ansiBrightGreen: charmtone.Guac,
200− ansiBrightYellow: charmtone.Tang,
201− ansiBrightBlue: charmtone.Sapphire,
202− ansiBrightMagenta: charmtone.Urchin,
203− ansiBrightCyan: charmtone.Zinc,
204− ansiBrightWhite: charmtone.Iron,
205− }
206−}
207−
208−// buildCharmtone builds a Styles value from the given color options and
209−// applies the shared bang-prompt and shell-bar accent overrides. These
210−// overrides use saturated purple accents that read on both light and dark
211−// backgrounds.
212−func buildCharmtone(opts quickStyleOpts) Styles {
213− s := quickStyle(opts)
89+ })
21490
21591 // Bang ! prompt overrides - use Salt/Hazy/Larple colors.
21692 s.Editor.PromptBangIconFocused = s.Editor.PromptBangIconFocused.
@@ -233,3 +109,8 @@ func buildCharmtone(opts quickStyleOpts) Styles {
233109
234110 return s
235111}
112+
113+// HypercrushObsidiana returns the Hypercrush dark theme.
114+func HypercrushObsidiana() Styles {
115+ return CharmtonePantera()
116+}
internal/ui/xchroma/chroma.go+19−37
@@ -13,55 +13,37 @@ import (
1313// Lip Gloss for foreground styling, while keeping a forced background color.
1414func Formatter(bgColor color.Color, processValue func(string) string) chroma.Formatter {
1515 return chroma.FormatterFunc(func(w io.Writer, style *chroma.Style, it chroma.Iterator) error {
16− // Precompute the base background style once, then memoize the
17− // resolved lipgloss.Style per token type so the per-token work is
18− // just a map lookup + one Render.
19− baseStyle := lipgloss.NewStyle().Background(bgColor)
20−
21− type tokenEntry struct {
22− style lipgloss.Style
23− zero bool
24− }
25− palette := make(map[chroma.TokenType]tokenEntry)
26−
2716 for token := it(); token != chroma.EOF; token = it() {
2817 value := token.Value
2918 if processValue != nil {
3019 value = processValue(value)
3120 }
3221
33− te, ok := palette[token.Type]
34− if !ok {
35− entry := style.Get(token.Type)
36− if entry.IsZero() {
37− te = tokenEntry{zero: true}
38− } else {
39− s := baseStyle
40− if entry.Bold == chroma.Yes {
41− s = s.Bold(true)
42− }
43− if entry.Underline == chroma.Yes {
44− s = s.Underline(true)
45− }
46− if entry.Italic == chroma.Yes {
47− s = s.Italic(true)
48− }
49− if entry.Colour.IsSet() {
50− s = s.Foreground(lipgloss.Color(entry.Colour.String()))
51− }
52− te = tokenEntry{style: s}
53− }
54− palette[token.Type] = te
55− }
56−
57− if te.zero {
22+ entry := style.Get(token.Type)
23+ if entry.IsZero() {
5824 if _, err := fmt.Fprint(w, value); err != nil {
5925 return err
6026 }
6127 continue
6228 }
6329
64− if _, err := fmt.Fprint(w, te.style.Render(value)); err != nil {
30+ s := lipgloss.NewStyle().
31+ Background(bgColor)
32+
33+ if entry.Bold == chroma.Yes {
34+ s = s.Bold(true)
35+ }
36+ if entry.Underline == chroma.Yes {
37+ s = s.Underline(true)
38+ }
39+ if entry.Italic == chroma.Yes {
40+ s = s.Italic(true)
41+ }
42+ if entry.Colour.IsSet() {
43+ s = s.Foreground(lipgloss.Color(entry.Colour.String()))
44+ }
45+
46+ if _, err := fmt.Fprint(w, s.Render(value)); err != nil {
6547 return err
6648 }
6749 }
internal/workspace/app_workspace.go+12−6
@@ -71,12 +71,10 @@ func (w *AppWorkspace) ParseAgentToolSessionID(sessionID string) (string, string
7171 return w.app.Sessions.ParseAgentToolSessionID(sessionID)
7272}
7373
74−// SetCurrentSession reports the active session to herdr so the pane
75−// can persist a resumable reference. Multi-client presence tracking
76−// is irrelevant in single-client local mode, but herdr still needs
77−// to know which session is live to support agent resume.
74+// SetCurrentSession is a no-op in single-client local mode. The
75+// presence concept only matters when multiple clients can share a
76+// workspace via the HTTP server.
7877func (w *AppWorkspace) SetCurrentSession(ctx context.Context, sessionID string) error {
79− w.app.ReportCurrentSession(sessionID)
8078 return nil
8179}
8280
@@ -114,7 +112,15 @@ func (w *AppWorkspace) AgentRunShellCommand(ctx context.Context, sessionID, comm
114112 var persist shell.PersistFunc
115113 if sessionID != "" {
116114 persist = func(cmd, output string, exitCode int) error {
117− return shell.PersistOutput(ctx, w.app.Messages, sessionID, cmd, output, exitCode)
115+ _, err := w.app.Messages.Create(ctx, sessionID, message.CreateMessageParams{
116+ Role: message.User,
117+ Parts: []message.ContentPart{message.ShellCommand{
118+ Command: cmd,
119+ Output: output,
120+ ExitCode: exitCode,
121+ }},
122+ })
123+ return err
118124 }
119125 }
120126
internal/workspace/client_workspace.go+3−16
@@ -14,7 +14,6 @@ import (
1414 "github.com/charmbracelet/crush/internal/agent/tools/mcp"
1515 "github.com/charmbracelet/crush/internal/client"
1616 "github.com/charmbracelet/crush/internal/config"
17− "github.com/charmbracelet/crush/internal/herdr"
1817 "github.com/charmbracelet/crush/internal/history"
1918 "github.com/charmbracelet/crush/internal/log"
2019 "github.com/charmbracelet/crush/internal/lsp"
@@ -38,10 +37,6 @@ type ClientWorkspace struct {
3837 mu sync.RWMutex
3938 ws proto.Workspace
4039 skills *skills.Manager
41−
42− // herdrClient reports agent state to herdr when running inside
43− // a herdr-managed pane. Nil when not in a herdr environment.
44− herdrClient *herdr.Client
4540}
4641
4742// NewClientWorkspace creates a new ClientWorkspace that proxies all
@@ -59,10 +54,9 @@ func NewClientWorkspace(c *client.Client, ws proto.Workspace) *ClientWorkspace {
5954 states := protoToSkillStates(ws.Skills)
6055 mgr := skills.NewManager(nil, nil, states, skills.WithGlobalMirror())
6156 return &ClientWorkspace{
62− client: c,
63− ws: ws,
64− skills: mgr,
65− herdrClient: herdr.Init(),
57+ client: c,
58+ ws: ws,
59+ skills: mgr,
6660 }
6761}
6862
@@ -153,7 +147,6 @@ func (w *ClientWorkspace) ParseAgentToolSessionID(sessionID string) (string, str
153147// are propagated to the caller; the TUI logs and ignores them since
154148// the presence record is a hint, not correctness-critical state.
155149func (w *ClientWorkspace) SetCurrentSession(ctx context.Context, sessionID string) error {
156− w.herdrClient.SetSessionID(sessionID)
157150 return w.client.SetCurrentSession(ctx, w.workspaceID(), sessionID)
158151}
159152
@@ -629,11 +622,6 @@ func (w *ClientWorkspace) Subscribe(program *tea.Program) {
629622// are translated into domain types and forwarded to send.
630623func (w *ClientWorkspace) consumeEvents(evc <-chan any, send func(tea.Msg)) {
631624 for ev := range evc {
632− // Forward events to herdr if running inside a herdr pane.
633− if hev := herdr.Translate(ev); hev != nil {
634− w.herdrClient.HandleEvent(hev)
635− }
636−
637625 if _, ok := ev.(pubsub.Event[proto.ConfigChanged]); ok {
638626 w.refreshWorkspace()
639627 continue
@@ -646,7 +634,6 @@ func (w *ClientWorkspace) consumeEvents(evc <-chan any, send func(tea.Msg)) {
646634}
647635
648636func (w *ClientWorkspace) Shutdown() {
649− w.herdrClient.Close()
650637 _ = w.client.DeleteWorkspace(context.Background(), w.workspaceID())
651638}
652639
schema.json−20
@@ -748,16 +748,6 @@
748748 ],
749749 "description": "Diff mode for the TUI interface"
750750 },
751− "theme": {
752− "type": "string",
753− "enum": [
754− "auto",
755− "dark",
756− "light"
757− ],
758− "description": "TUI color scheme",
759− "default": "auto"
760− },
761751 "completions": {
762752 "$ref": "#/$defs/Completions",
763753 "description": "Completions UI options"
@@ -766,16 +756,6 @@
766756 "type": "boolean",
767757 "description": "Enable transparent background for the TUI interface",
768758 "default": false
769− },
770− "scrollbar": {
771− "type": "string",
772− "enum": [
773− "default",
774− "always",
775− "never"
776− ],
777− "description": "Chat scrollbar visibility",
778− "default": "default"
779759 }
780760 },
781761 "additionalProperties": false,
vendor/charm.land/bubbles/v2/LICENSEdeleted−21
@@ -1,21 +0,0 @@
1−MIT License
2−
3−Copyright (c) 2020-2026 Charmbracelet, Inc.
4−
5−Permission is hereby granted, free of charge, to any person obtaining a copy
6−of this software and associated documentation files (the "Software"), to deal
7−in the Software without restriction, including without limitation the rights
8−to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9−copies of the Software, and to permit persons to whom the Software is
10−furnished to do so, subject to the following conditions:
11−
12−The above copyright notice and this permission notice shall be included in all
13−copies or substantial portions of the Software.
14−
15−THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16−IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17−FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18−AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19−LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20−OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21−SOFTWARE.
vendor/charm.land/bubbles/v2/cursor/cursor.godeleted−241
@@ -1,241 +0,0 @@
1−// Package cursor provides a virtual cursor to support the textinput and
2−// textarea elements.
3−package cursor
4−
5−import (
6− "context"
7− "sync/atomic"
8− "time"
9−
10− tea "charm.land/bubbletea/v2"
11− "charm.land/lipgloss/v2"
12−)
13−
14−const defaultBlinkSpeed = time.Millisecond * 530
15−
16−// Internal ID management. Used during animating to ensure that frame messages
17−// are received only by spinner components that sent them.
18−var lastID int64
19−
20−func nextID() int {
21− return int(atomic.AddInt64(&lastID, 1))
22−}
23−
24−// initialBlinkMsg initializes cursor blinking.
25−type initialBlinkMsg struct{}
26−
27−// BlinkMsg signals that the cursor should blink. It contains metadata that
28−// allows us to tell if the blink message is the one we're expecting.
29−type BlinkMsg struct {
30− id int
31− tag int
32−}
33−
34−// blinkCanceled is sent when a blink operation is canceled.
35−type blinkCanceled struct{}
36−
37−// blinkCtx manages cursor blinking.
38−type blinkCtx struct {
39− ctx context.Context
40− cancel context.CancelFunc
41−}
42−
43−// Mode describes the behavior of the cursor.
44−type Mode int
45−
46−// Available cursor modes.
47−const (
48− CursorBlink Mode = iota
49− CursorStatic
50− CursorHide
51−)
52−
53−// String returns the cursor mode in a human-readable format. This method is
54−// provisional and for informational purposes only.
55−func (c Mode) String() string {
56− return [...]string{
57− "blink",
58− "static",
59− "hidden",
60− }[c]
61−}
62−
63−// Model is the Bubble Tea model for this cursor element.
64−type Model struct {
65− // Style styles the cursor block.
66− Style lipgloss.Style
67−
68− // TextStyle is the style used for the cursor when it is blinking
69− // (hidden), i.e. displaying normal text.
70− TextStyle lipgloss.Style
71−
72− // BlinkSpeed is the speed at which the cursor blinks. This has no effect
73− // unless [CursorMode] is not set to [CursorBlink].
74− BlinkSpeed time.Duration
75−
76− // IsBlinked is the state of the cursor blink. When true, the cursor is
77− // hidden.
78− IsBlinked bool
79−
80− // char is the character under the cursor
81− char string
82−
83− // The ID of this Model as it relates to other cursors
84− id int
85−
86− // focus indicates whether the containing input is focused
87− focus bool
88−
89− // Used to manage cursor blink
90− blinkCtx *blinkCtx
91−
92− // The ID of the blink message we're expecting to receive.
93− blinkTag int
94−
95− // mode determines the behavior of the cursor
96− mode Mode
97−}
98−
99−// New creates a new model with default settings.
100−func New() Model {
101− return Model{
102− id: nextID(),
103− BlinkSpeed: defaultBlinkSpeed,
104− IsBlinked: true,
105− mode: CursorBlink,
106−
107− blinkCtx: &blinkCtx{
108− ctx: context.Background(),
109− },
110− }
111−}
112−
113−// Update updates the cursor.
114−func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
115− switch msg := msg.(type) {
116− case initialBlinkMsg:
117− // We accept all initialBlinkMsgs generated by the Blink command.
118−
119− if m.mode != CursorBlink || !m.focus {
120− return m, nil
121− }
122−
123− cmd := m.Blink()
124− return m, cmd
125−
126− case tea.FocusMsg:
127− return m, m.Focus()
128−
129− case tea.BlurMsg:
130− m.Blur()
131− return m, nil
132−
133− case BlinkMsg:
134− // We're choosy about whether to accept blinkMsgs so that our cursor
135− // only exactly when it should.
136−
137− // Is this model blink-able?
138− if m.mode != CursorBlink || !m.focus {
139− return m, nil
140− }
141−
142− // Were we expecting this blink message?
143− if msg.id != m.id || msg.tag != m.blinkTag {
144− return m, nil
145− }
146−
147− var cmd tea.Cmd
148− if m.mode == CursorBlink {
149− m.IsBlinked = !m.IsBlinked
150− cmd = m.Blink()
151− }
152− return m, cmd
153−
154− case blinkCanceled: // no-op
155− return m, nil
156− }
157− return m, nil
158−}
159−
160−// Mode returns the model's cursor mode. For available cursor modes, see
161−// type Mode.
162−func (m Model) Mode() Mode {
163− return m.mode
164−}
165−
166−// SetMode sets the model's cursor mode. This method returns a command.
167−//
168−// For available cursor modes, see type CursorMode.
169−func (m *Model) SetMode(mode Mode) tea.Cmd {
170− // Adjust the mode value if it's value is out of range
171− if mode < CursorBlink || mode > CursorHide {
172− return nil
173− }
174− m.mode = mode
175− m.IsBlinked = m.mode == CursorHide || !m.focus
176− if mode == CursorBlink {
177− return Blink
178− }
179− return nil
180−}
181−
182−// Blink is a command used to manage cursor blinking.
183−func (m *Model) Blink() tea.Cmd {
184− if m.mode != CursorBlink {
185− return nil
186− }
187−
188− if m.blinkCtx != nil && m.blinkCtx.cancel != nil {
189− m.blinkCtx.cancel()
190− }
191−
192− ctx, cancel := context.WithTimeout(m.blinkCtx.ctx, m.BlinkSpeed)
193− m.blinkCtx.cancel = cancel
194−
195− m.blinkTag++
196− blinkMsg := BlinkMsg{id: m.id, tag: m.blinkTag}
197−
198− return func() tea.Msg {
199− defer cancel()
200− <-ctx.Done()
201− if ctx.Err() == context.DeadlineExceeded {
202− return blinkMsg
203− }
204− return blinkCanceled{}
205− }
206−}
207−
208−// Blink is a command used to initialize cursor blinking.
209−func Blink() tea.Msg {
210− return initialBlinkMsg{}
211−}
212−
213−// Focus focuses the cursor to allow it to blink if desired.
214−func (m *Model) Focus() tea.Cmd {
215− m.focus = true
216− m.IsBlinked = m.mode == CursorHide // show the cursor unless we've explicitly hidden it
217−
218− if m.mode == CursorBlink && m.focus {
219− return m.Blink()
220− }
221− return nil
222−}
223−
224−// Blur blurs the cursor.
225−func (m *Model) Blur() {
226− m.focus = false
227− m.IsBlinked = true
228−}
229−
230−// SetChar sets the character under the cursor.
231−func (m *Model) SetChar(char string) {
232− m.char = char
233−}
234−
235−// View displays the cursor.
236−func (m Model) View() string {
237− if m.IsBlinked {
238− return m.TextStyle.Inline(true).Render(m.char)
239− }
240− return m.Style.Inline(true).Reverse(true).Render(m.char)
241−}
vendor/charm.land/bubbles/v2/filepicker/filepicker.godeleted−529
@@ -1,529 +0,0 @@
1−// Package filepicker provides a file picker component for Bubble Tea
2−// applications.
3−package filepicker
4−
5−import (
6− "fmt"
7− "os"
8− "path/filepath"
9− "sort"
10− "strconv"
11− "strings"
12− "sync/atomic"
13−
14− "charm.land/bubbles/v2/key"
15− tea "charm.land/bubbletea/v2"
16− "charm.land/lipgloss/v2"
17− "github.com/dustin/go-humanize"
18−)
19−
20−var lastID int64
21−
22−func nextID() int {
23− return int(atomic.AddInt64(&lastID, 1))
24−}
25−
26−// New returns a new filepicker model with default styling and key bindings.
27−func New() Model {
28− return Model{
29− id: nextID(),
30− CurrentDirectory: ".",
31− Cursor: ">",
32− AllowedTypes: []string{},
33− selected: 0,
34− ShowPermissions: true,
35− ShowSize: true,
36− ShowHidden: false,
37− DirAllowed: false,
38− FileAllowed: true,
39− AutoHeight: true,
40− height: 0,
41− maxIdx: 0,
42− minIdx: 0,
43− selectedStack: newStack(),
44− minStack: newStack(),
45− maxStack: newStack(),
46− KeyMap: DefaultKeyMap(),
47− Styles: DefaultStyles(),
48− }
49−}
50−
51−type errorMsg struct {
52− err error
53−}
54−
55−type readDirMsg struct {
56− id int
57− entries []os.DirEntry
58−}
59−
60−const (
61− marginBottom = 5
62− fileSizeWidth = 7
63− paddingLeft = 2
64−)
65−
66−// KeyMap defines key bindings for each user action.
67−type KeyMap struct {
68− GoToTop key.Binding
69− GoToLast key.Binding
70− Down key.Binding
71− Up key.Binding
72− PageUp key.Binding
73− PageDown key.Binding
74− Back key.Binding
75− Open key.Binding
76− Select key.Binding
77−}
78−
79−// DefaultKeyMap defines the default keybindings.
80−func DefaultKeyMap() KeyMap {
81− return KeyMap{
82− GoToTop: key.NewBinding(key.WithKeys("g"), key.WithHelp("g", "first")),
83− GoToLast: key.NewBinding(key.WithKeys("G"), key.WithHelp("G", "last")),
84− Down: key.NewBinding(key.WithKeys("j", "down", "ctrl+n"), key.WithHelp("j", "down")),
85− Up: key.NewBinding(key.WithKeys("k", "up", "ctrl+p"), key.WithHelp("k", "up")),
86− PageUp: key.NewBinding(key.WithKeys("K", "pgup"), key.WithHelp("pgup", "page up")),
87− PageDown: key.NewBinding(key.WithKeys("J", "pgdown"), key.WithHelp("pgdown", "page down")),
88− Back: key.NewBinding(key.WithKeys("h", "backspace", "left", "esc"), key.WithHelp("h", "back")),
89− Open: key.NewBinding(key.WithKeys("l", "right", "enter"), key.WithHelp("l", "open")),
90− Select: key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
91− }
92−}
93−
94−// Styles defines the possible customizations for styles in the file picker.
95−type Styles struct {
96− DisabledCursor lipgloss.Style
97− Cursor lipgloss.Style
98− Symlink lipgloss.Style
99− Directory lipgloss.Style
100− File lipgloss.Style
101− DisabledFile lipgloss.Style
102− Permission lipgloss.Style
103− Selected lipgloss.Style
104− DisabledSelected lipgloss.Style
105− FileSize lipgloss.Style
106− EmptyDirectory lipgloss.Style
107−}
108−
109−// DefaultStyles defines the default styling for the file picker.
110−func DefaultStyles() Styles {
111− return Styles{
112− DisabledCursor: lipgloss.NewStyle().Foreground(lipgloss.Color("247")),
113− Cursor: lipgloss.NewStyle().Foreground(lipgloss.Color("212")),
114− Symlink: lipgloss.NewStyle().Foreground(lipgloss.Color("36")),
115− Directory: lipgloss.NewStyle().Foreground(lipgloss.Color("99")),
116− File: lipgloss.NewStyle(),
117− DisabledFile: lipgloss.NewStyle().Foreground(lipgloss.Color("243")),
118− DisabledSelected: lipgloss.NewStyle().Foreground(lipgloss.Color("247")),
119− Permission: lipgloss.NewStyle().Foreground(lipgloss.Color("244")),
120− Selected: lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Bold(true),
121− FileSize: lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Width(fileSizeWidth).Align(lipgloss.Right),
122− EmptyDirectory: lipgloss.NewStyle().Foreground(lipgloss.Color("240")).PaddingLeft(paddingLeft).SetString("Bummer. No Files Found."),
123− }
124−}
125−
126−// Model represents a file picker.
127−type Model struct {
128− id int
129−
130− // Path is the path which the user has selected with the file picker.
131− Path string
132−
133− // CurrentDirectory is the directory that the user is currently in.
134− CurrentDirectory string
135−
136− // AllowedTypes specifies which file types the user may select.
137− // If empty the user may select any file.
138− AllowedTypes []string
139−
140− KeyMap KeyMap
141− files []os.DirEntry
142− ShowPermissions bool
143− ShowSize bool
144− ShowHidden bool
145− DirAllowed bool
146− FileAllowed bool
147−
148− FileSelected string
149− selected int
150− selectedStack stack
151−
152− minIdx int
153− maxIdx int
154− maxStack stack
155− minStack stack
156−
157− height int
158− AutoHeight bool
159−
160− Cursor string
161− Styles Styles
162−}
163−
164−type stack struct {
165− Push func(int)
166− Pop func() int
167− Length func() int
168−}
169−
170−func newStack() stack {
171− slice := make([]int, 0)
172− return stack{
173− Push: func(i int) {
174− slice = append(slice, i)
175− },
176− Pop: func() int {
177− res := slice[len(slice)-1]
178− slice = slice[:len(slice)-1]
179− return res
180− },
181− Length: func() int {
182− return len(slice)
183− },
184− }
185−}
186−
187−func (m *Model) pushView(selected, minimum, maximum int) {
188− m.selectedStack.Push(selected)
189− m.minStack.Push(minimum)
190− m.maxStack.Push(maximum)
191−}
192−
193−func (m *Model) popView() (int, int, int) {
194− return m.selectedStack.Pop(), m.minStack.Pop(), m.maxStack.Pop()
195−}
196−
197−func (m Model) readDir(path string, showHidden bool) tea.Cmd {
198− return func() tea.Msg {
199− dirEntries, err := os.ReadDir(path)
200− if err != nil {
201− return errorMsg{err}
202− }
203−
204− sort.Slice(dirEntries, func(i, j int) bool {
205− if dirEntries[i].IsDir() == dirEntries[j].IsDir() {
206− return dirEntries[i].Name() < dirEntries[j].Name()
207− }
208− return dirEntries[i].IsDir()
209− })
210−
211− if showHidden {
212− return readDirMsg{id: m.id, entries: dirEntries}
213− }
214−
215− var sanitizedDirEntries []os.DirEntry
216− for _, dirEntry := range dirEntries {
217− isHidden, _ := IsHidden(dirEntry.Name())
218− if isHidden {
219− continue
220− }
221− sanitizedDirEntries = append(sanitizedDirEntries, dirEntry)
222− }
223− return readDirMsg{id: m.id, entries: sanitizedDirEntries}
224− }
225−}
226−
227−// SetHeight sets the height of the file picker.
228−func (m *Model) SetHeight(h int) {
229− m.height = h
230− if m.maxIdx > m.height-1 {
231− m.maxIdx = m.minIdx + m.height - 1
232− }
233−}
234−
235−// Height returns the height of the file picker.
236−func (m Model) Height() int {
237− return m.height
238−}
239−
240−// Init initializes the file picker model.
241−func (m Model) Init() tea.Cmd {
242− return m.readDir(m.CurrentDirectory, m.ShowHidden)
243−}
244−
245−// Update handles user interactions within the file picker model.
246−func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
247− switch msg := msg.(type) {
248− case readDirMsg:
249− if msg.id != m.id {
250− break
251− }
252− m.files = msg.entries
253− m.maxIdx = max(m.maxIdx, m.Height()-1)
254− case tea.WindowSizeMsg:
255− if m.AutoHeight {
256− m.SetHeight(msg.Height - marginBottom)
257− }
258− m.maxIdx = m.Height() - 1
259− case tea.KeyPressMsg:
260− switch {
261− case key.Matches(msg, m.KeyMap.GoToTop):
262− m.selected = 0
263− m.minIdx = 0
264− m.maxIdx = m.Height() - 1
265− case key.Matches(msg, m.KeyMap.GoToLast):
266− m.selected = len(m.files) - 1
267− m.minIdx = len(m.files) - m.Height()
268− m.maxIdx = len(m.files) - 1
269− case key.Matches(msg, m.KeyMap.Down):
270− m.selected++
271− if m.selected >= len(m.files) {
272− m.selected = len(m.files) - 1
273− }
274− if m.selected > m.maxIdx {
275− m.minIdx++
276− m.maxIdx++
277− }
278− case key.Matches(msg, m.KeyMap.Up):
279− m.selected--
280− if m.selected < 0 {
281− m.selected = 0
282− }
283− if m.selected < m.minIdx {
284− m.minIdx--
285− m.maxIdx--
286− }
287− case key.Matches(msg, m.KeyMap.PageDown):
288− m.selected += m.Height()
289− if m.selected >= len(m.files) {
290− m.selected = len(m.files) - 1
291− }
292− m.minIdx += m.Height()
293− m.maxIdx += m.Height()
294−
295− if m.maxIdx >= len(m.files) {
296− m.maxIdx = len(m.files) - 1
297− m.minIdx = m.maxIdx - m.Height()
298− }
299− case key.Matches(msg, m.KeyMap.PageUp):
300− m.selected -= m.Height()
301− if m.selected < 0 {
302− m.selected = 0
303− }
304− m.minIdx -= m.Height()
305− m.maxIdx -= m.Height()
306−
307− if m.minIdx < 0 {
308− m.minIdx = 0
309− m.maxIdx = m.minIdx + m.Height()
310− }
311− case key.Matches(msg, m.KeyMap.Back):
312− m.CurrentDirectory = filepath.Dir(m.CurrentDirectory)
313− if m.selectedStack.Length() > 0 {
314− m.selected, m.minIdx, m.maxIdx = m.popView()
315− } else {
316− m.selected = 0
317− m.minIdx = 0
318− m.maxIdx = m.Height() - 1
319− }
320− return m, m.readDir(m.CurrentDirectory, m.ShowHidden)
321− case key.Matches(msg, m.KeyMap.Open):
322− if len(m.files) == 0 {
323− break
324− }
325−
326− f := m.files[m.selected]
327− info, err := f.Info()
328− if err != nil {
329− break
330− }
331− isSymlink := info.Mode()&os.ModeSymlink != 0
332− isDir := f.IsDir()
333−
334− if isSymlink {
335− symlinkPath, _ := filepath.EvalSymlinks(filepath.Join(m.CurrentDirectory, f.Name()))
336− info, err := os.Stat(symlinkPath)
337− if err != nil {
338− break
339− }
340− if info.IsDir() {
341− isDir = true
342− }
343− }
344−
345− if (!isDir && m.FileAllowed) || (isDir && m.DirAllowed) {
346− if key.Matches(msg, m.KeyMap.Select) {
347− // Select the current path as the selection
348− m.Path = filepath.Join(m.CurrentDirectory, f.Name())
349− }
350− }
351−
352− if !isDir {
353− break
354− }
355−
356− m.CurrentDirectory = filepath.Join(m.CurrentDirectory, f.Name())
357− m.pushView(m.selected, m.minIdx, m.maxIdx)
358− m.selected = 0
359− m.minIdx = 0
360− m.maxIdx = m.Height() - 1
361− return m, m.readDir(m.CurrentDirectory, m.ShowHidden)
362− }
363− }
364− return m, nil
365−}
366−
367−// View returns the view of the file picker.
368−func (m Model) View() string {
369− if len(m.files) == 0 {
370− return m.Styles.EmptyDirectory.Height(m.Height()).MaxHeight(m.Height()).String()
371− }
372− var s strings.Builder
373−
374− for i, f := range m.files {
375− if i < m.minIdx || i > m.maxIdx {
376− continue
377− }
378−
379− var symlinkPath string
380− info, err := f.Info()
381− if err != nil {
382− continue
383− }
384− isSymlink := info.Mode()&os.ModeSymlink != 0
385− size := strings.Replace(humanize.Bytes(uint64(info.Size())), " ", "", 1) //nolint:gosec
386− name := f.Name()
387−
388− if isSymlink {
389− symlinkPath, _ = filepath.EvalSymlinks(filepath.Join(m.CurrentDirectory, name))
390− }
391−
392− disabled := !m.canSelect(name) && !f.IsDir()
393−
394− if m.selected == i { //nolint:nestif
395− selected := ""
396− if m.ShowPermissions {
397− selected += " " + info.Mode().String()
398− }
399− if m.ShowSize {
400− selected += fmt.Sprintf("%"+strconv.Itoa(m.Styles.FileSize.GetWidth())+"s", size)
401− }
402− selected += " " + name
403− if isSymlink {
404− selected += " → " + symlinkPath
405− }
406− if disabled {
407− s.WriteString(m.Styles.DisabledCursor.Render(m.Cursor) + m.Styles.DisabledSelected.Render(selected))
408− } else {
409− s.WriteString(m.Styles.Cursor.Render(m.Cursor) + m.Styles.Selected.Render(selected))
410− }
411− s.WriteRune('\n')
412− continue
413− }
414−
415− style := m.Styles.File
416− if f.IsDir() {
417− style = m.Styles.Directory
418− } else if isSymlink {
419− style = m.Styles.Symlink
420− } else if disabled {
421− style = m.Styles.DisabledFile
422− }
423−
424− fileName := style.Render(name)
425− s.WriteString(m.Styles.Cursor.Render(" "))
426− if isSymlink {
427− fileName += " → " + symlinkPath
428− }
429− if m.ShowPermissions {
430− s.WriteString(" " + m.Styles.Permission.Render(info.Mode().String()))
431− }
432− if m.ShowSize {
433− s.WriteString(m.Styles.FileSize.Render(size))
434− }
435− s.WriteString(" " + fileName)
436− s.WriteRune('\n')
437− }
438−
439− for i := lipgloss.Height(s.String()); i <= m.Height(); i++ {
440− s.WriteRune('\n')
441− }
442−
443− return s.String()
444−}
445−
446−// DidSelectFile returns whether a user has selected a file (on this msg).
447−func (m Model) DidSelectFile(msg tea.Msg) (bool, string) {
448− didSelect, path := m.didSelectFile(msg)
449− if didSelect && m.canSelect(path) {
450− return true, path
451− }
452− return false, ""
453−}
454−
455−// DidSelectDisabledFile returns whether a user tried to select a disabled file
456−// (on this msg). This is necessary only if you would like to warn the user that
457−// they tried to select a disabled file.
458−func (m Model) DidSelectDisabledFile(msg tea.Msg) (bool, string) {
459− didSelect, path := m.didSelectFile(msg)
460− if didSelect && !m.canSelect(path) {
461− return true, path
462− }
463− return false, ""
464−}
465−
466−func (m Model) didSelectFile(msg tea.Msg) (bool, string) {
467− if len(m.files) == 0 {
468− return false, ""
469− }
470− switch msg := msg.(type) {
471− case tea.KeyPressMsg:
472− // If the msg does not match the Select keymap then this could not have been a selection.
473− if !key.Matches(msg, m.KeyMap.Select) {
474− return false, ""
475− }
476−
477− // The key press was a selection, let's confirm whether the current file could
478− // be selected or used for navigating deeper into the stack.
479− f := m.files[m.selected]
480− info, err := f.Info()
481− if err != nil {
482− return false, ""
483− }
484− isSymlink := info.Mode()&os.ModeSymlink != 0
485− isDir := f.IsDir()
486−
487− if isSymlink {
488− symlinkPath, _ := filepath.EvalSymlinks(filepath.Join(m.CurrentDirectory, f.Name()))
489− info, err := os.Stat(symlinkPath)
490− if err != nil {
491− break
492− }
493− if info.IsDir() {
494− isDir = true
495− }
496− }
497−
498− if (!isDir && m.FileAllowed) || (isDir && m.DirAllowed) && m.Path != "" {
499− return true, m.Path
500− }
501−
502− // If the msg was not a KeyPressMsg, then the file could not have been selected this iteration.
503− // Only a KeyPressMsg can select a file.
504− default:
505− return false, ""
506− }
507− return false, ""
508−}
509−
510−func (m Model) canSelect(file string) bool {
511− if len(m.AllowedTypes) <= 0 {
512− return true
513− }
514−
515− for _, ext := range m.AllowedTypes {
516− if strings.HasSuffix(file, ext) {
517− return true
518− }
519− }
520− return false
521−}
522−
523−// HighlightedPath returns the path of the currently highlighted file or directory.
524−func (m Model) HighlightedPath() string {
525− if len(m.files) == 0 || m.selected < 0 || m.selected >= len(m.files) {
526− return ""
527− }
528− return filepath.Join(m.CurrentDirectory, m.files[m.selected].Name())
529−}
vendor/charm.land/bubbles/v2/filepicker/hidden_unix.godeleted−11
@@ -1,11 +0,0 @@
1−//go:build !windows
2−// +build !windows
3−
4−package filepicker
5−
6−import "strings"
7−
8−// IsHidden reports whether a file is hidden or not.
9−func IsHidden(file string) (bool, error) {
10− return strings.HasPrefix(file, "."), nil
11−}
vendor/charm.land/bubbles/v2/filepicker/hidden_windows.godeleted−21
@@ -1,21 +0,0 @@
1−//go:build windows
2−// +build windows
3−
4−package filepicker
5−
6−import (
7− "syscall"
8−)
9−
10−// IsHidden reports whether a file is hidden or not.
11−func IsHidden(file string) (bool, error) {
12− pointer, err := syscall.UTF16PtrFromString(file)
13− if err != nil {
14− return false, err //nolint:wrapcheck
15− }
16− attributes, err := syscall.GetFileAttributes(pointer)
17− if err != nil {
18− return false, err //nolint:wrapcheck
19− }
20− return attributes&syscall.FILE_ATTRIBUTE_HIDDEN != 0, nil
21−}
vendor/charm.land/bubbles/v2/help/help.godeleted−253
@@ -1,253 +0,0 @@
1−// Package help provides a simple help view for Bubble Tea applications.
2−package help
3−
4−import (
5− "strings"
6−
7− "charm.land/bubbles/v2/key"
8− tea "charm.land/bubbletea/v2"
9− "charm.land/lipgloss/v2"
10−)
11−
12−// KeyMap is a map of keybindings used to generate help. Since it's an
13−// interface it can be any type, though struct or a map[string][]key.Binding
14−// are likely candidates.
15−//
16−// Note that if a key is disabled (via key.Binding.SetEnabled) it will not be
17−// rendered in the help view, so in theory generated help should self-manage.
18−type KeyMap interface {
19− // ShortHelp returns a slice of bindings to be displayed in the short
20− // version of the help. The help bubble will render help in the order in
21− // which the help items are returned here.
22− ShortHelp() []key.Binding
23−
24− // FullHelp returns an extended group of help items, grouped by columns.
25− // The help bubble will render the help in the order in which the help
26− // items are returned here.
27− FullHelp() [][]key.Binding
28−}
29−
30−// Styles is a set of available style definitions for the Help bubble.
31−type Styles struct {
32− Ellipsis lipgloss.Style
33−
34− // Styling for the short help
35− ShortKey lipgloss.Style
36− ShortDesc lipgloss.Style
37− ShortSeparator lipgloss.Style
38−
39− // Styling for the full help
40− FullKey lipgloss.Style
41− FullDesc lipgloss.Style
42− FullSeparator lipgloss.Style
43−}
44−
45−// DefaultStyles returns a set of default styles for the help bubble. Light or
46−// dark styles can be selected by passing true or false to the isDark
47−// parameter.
48−func DefaultStyles(isDark bool) Styles {
49− lightDark := lipgloss.LightDark(isDark)
50−
51− keyStyle := lipgloss.NewStyle().Foreground(lightDark(lipgloss.Color("#909090"), lipgloss.Color("#626262")))
52− descStyle := lipgloss.NewStyle().Foreground(lightDark(lipgloss.Color("#B2B2B2"), lipgloss.Color("#4A4A4A")))
53− sepStyle := lipgloss.NewStyle().Foreground(lightDark(lipgloss.Color("#DADADA"), lipgloss.Color("#3C3C3C")))
54−
55− return Styles{
56− ShortKey: keyStyle,
57− ShortDesc: descStyle,
58− ShortSeparator: sepStyle,
59− Ellipsis: sepStyle,
60− FullKey: keyStyle,
61− FullDesc: descStyle,
62− FullSeparator: sepStyle,
63− }
64−}
65−
66−// DefaultDarkStyles returns a set of default styles for dark backgrounds.
67−func DefaultDarkStyles() Styles {
68− return DefaultStyles(true)
69−}
70−
71−// DefaultLightStyles returns a set of default styles for light backgrounds.
72−func DefaultLightStyles() Styles {
73− return DefaultStyles(false)
74−}
75−
76−// Model contains the state of the help view.
77−type Model struct {
78− ShowAll bool // if true, render the "full" help menu
79−
80− ShortSeparator string
81− FullSeparator string
82−
83− // The symbol we use in the short help when help items have been truncated
84− // due to width. Periods of ellipsis by default.
85− Ellipsis string
86−
87− Styles Styles
88−
89− width int
90−}
91−
92−// New creates a new help view with some useful defaults.
93−func New() Model {
94− return Model{
95− ShortSeparator: " • ",
96− FullSeparator: " ",
97− Ellipsis: "…",
98− Styles: DefaultDarkStyles(),
99− }
100−}
101−
102−// Update helps satisfy the Bubble Tea Model interface. It's a no-op.
103−func (m Model) Update(_ tea.Msg) (Model, tea.Cmd) {
104− return m, nil
105−}
106−
107−// View renders the help view's current state.
108−func (m Model) View(k KeyMap) string {
109− if m.ShowAll {
110− return m.FullHelpView(k.FullHelp())
111− }
112− return m.ShortHelpView(k.ShortHelp())
113−}
114−
115−// SetWidth sets the maximum width for the help view.
116−func (m *Model) SetWidth(w int) {
117− m.width = w
118−}
119−
120−// Width returns the maximum width for the help view.
121−func (m Model) Width() int {
122− return m.width
123−}
124−
125−// ShortHelpView renders a single line help view from a slice of keybindings.
126−// If the line is longer than the maximum width it will be gracefully
127−// truncated, showing only as many help items as possible.
128−func (m Model) ShortHelpView(bindings []key.Binding) string {
129− if len(bindings) == 0 {
130− return ""
131− }
132−
133− var b strings.Builder
134− var totalWidth int
135− separator := m.Styles.ShortSeparator.Inline(true).Render(m.ShortSeparator)
136−
137− for i, kb := range bindings {
138− if !kb.Enabled() {
139− continue
140− }
141−
142− // Sep
143− var sep string
144− if totalWidth > 0 && i < len(bindings) {
145− sep = separator
146− }
147−
148− // Item
149− str := sep +
150− m.Styles.ShortKey.Inline(true).Render(kb.Help().Key) + " " +
151− m.Styles.ShortDesc.Inline(true).Render(kb.Help().Desc)
152− w := lipgloss.Width(str)
153−
154− // Tail
155− if tail, ok := m.shouldAddItem(totalWidth, w); !ok {
156− if tail != "" {
157− b.WriteString(tail)
158− }
159− break
160− }
161−
162− totalWidth += w
163− b.WriteString(str)
164− }
165−
166− return b.String()
167−}
168−
169−// FullHelpView renders help columns from a slice of key binding slices. Each
170−// top level slice entry renders into a column.
171−func (m Model) FullHelpView(groups [][]key.Binding) string {
172− if len(groups) == 0 {
173− return ""
174− }
175−
176− // Linter note: at this time we don't think it's worth the additional
177− // code complexity involved in preallocating this slice.
178− var (
179− out []string
180−
181− totalWidth int
182− separator = m.Styles.FullSeparator.Inline(true).Render(m.FullSeparator)
183− )
184−
185− // Iterate over groups to build columns
186− for i, group := range groups {
187− if group == nil || !shouldRenderColumn(group) {
188− continue
189− }
190− var (
191− sep string
192− keys []string
193− descriptions []string
194− )
195−
196− // Sep
197− if totalWidth > 0 && i < len(groups) {
198− sep = separator
199− }
200−
201− // Separate keys and descriptions into different slices
202− for _, kb := range group {
203− if !kb.Enabled() {
204− continue
205− }
206− keys = append(keys, kb.Help().Key)
207− descriptions = append(descriptions, kb.Help().Desc)
208− }
209−
210− // Column
211− col := lipgloss.JoinHorizontal(lipgloss.Top,
212− sep,
213− m.Styles.FullKey.Render(strings.Join(keys, "\n")),
214− " ",
215− m.Styles.FullDesc.Render(strings.Join(descriptions, "\n")),
216− )
217− w := lipgloss.Width(col)
218−
219− // Tail
220− if tail, ok := m.shouldAddItem(totalWidth, w); !ok {
221− if tail != "" {
222− out = append(out, tail)
223− }
224− break
225− }
226−
227− totalWidth += w
228− out = append(out, col)
229− }
230−
231− return lipgloss.JoinHorizontal(lipgloss.Top, out...)
232−}
233−
234−func (m Model) shouldAddItem(totalWidth, width int) (tail string, ok bool) {
235− // If there's room for an ellipsis, print that.
236− if m.width > 0 && totalWidth+width > m.width {
237− tail = " " + m.Styles.Ellipsis.Inline(true).Render(m.Ellipsis)
238−
239− if totalWidth+lipgloss.Width(tail) < m.width {
240− return tail, false
241− }
242− }
243− return "", true
244−}
245−
246−func shouldRenderColumn(b []key.Binding) (ok bool) {
247− for _, v := range b {
248− if v.Enabled() {
249− return true
250− }
251− }
252− return false
253−}
vendor/charm.land/bubbles/v2/internal/memoization/memoization.godeleted−125
@@ -1,125 +0,0 @@
1−// Package memoization implement a simple memoization cache. It's designed to
2−// improve performance in textarea.
3−package memoization
4−
5−import (
6− "container/list"
7− "crypto/sha256"
8− "fmt"
9− "sync"
10−)
11−
12−// Hasher is an interface that requires a Hash method. The Hash method is
13−// expected to return a string representation of the hash of the object.
14−type Hasher interface {
15− Hash() string
16−}
17−
18−// entry is a struct that holds a key-value pair. It is used as an element
19−// in the evictionList of the MemoCache.
20−type entry[T any] struct {
21− key string
22− value T
23−}
24−
25−// MemoCache is a struct that represents a cache with a set capacity. It
26−// uses an LRU (Least Recently Used) eviction policy. It is safe for
27−// concurrent use.
28−type MemoCache[H Hasher, T any] struct {
29− capacity int
30− mutex sync.Mutex
31− cache map[string]*list.Element // The cache holding the results
32− evictionList *list.List // A list to keep track of the order for LRU
33− hashableItems map[string]T // This map keeps track of the original hashable items (optional)
34−}
35−
36−// NewMemoCache is a function that creates a new MemoCache with a given
37−// capacity. It returns a pointer to the created MemoCache.
38−func NewMemoCache[H Hasher, T any](capacity int) *MemoCache[H, T] {
39− return &MemoCache[H, T]{
40− capacity: capacity,
41− cache: make(map[string]*list.Element),
42− evictionList: list.New(),
43− hashableItems: make(map[string]T),
44− }
45−}
46−
47−// Capacity is a method that returns the capacity of the MemoCache.
48−func (m *MemoCache[H, T]) Capacity() int {
49− return m.capacity
50−}
51−
52−// Size is a method that returns the current size of the MemoCache. It is
53−// the number of items currently stored in the cache.
54−func (m *MemoCache[H, T]) Size() int {
55− m.mutex.Lock()
56− defer m.mutex.Unlock()
57− return m.evictionList.Len()
58−}
59−
60−// Get is a method that returns the value associated with the given
61−// hashable item in the MemoCache. If there is no corresponding value, the
62−// method returns nil.
63−func (m *MemoCache[H, T]) Get(h H) (T, bool) {
64− m.mutex.Lock()
65− defer m.mutex.Unlock()
66−
67− hashedKey := h.Hash()
68− if element, found := m.cache[hashedKey]; found {
69− m.evictionList.MoveToFront(element)
70− return element.Value.(*entry[T]).value, true
71− }
72− var result T
73− return result, false
74−}
75−
76−// Set is a method that sets the value for the given hashable item in the
77−// MemoCache. If the cache is at capacity, it evicts the least recently
78−// used item before adding the new item.
79−func (m *MemoCache[H, T]) Set(h H, value T) {
80− m.mutex.Lock()
81− defer m.mutex.Unlock()
82−
83− hashedKey := h.Hash()
84− if element, found := m.cache[hashedKey]; found {
85− m.evictionList.MoveToFront(element)
86− element.Value.(*entry[T]).value = value
87− return
88− }
89−
90− // Check if the cache is at capacity
91− if m.evictionList.Len() >= m.capacity {
92− // Evict the least recently used item from the cache
93− toEvict := m.evictionList.Back()
94− if toEvict != nil {
95− evictedEntry := m.evictionList.Remove(toEvict).(*entry[T])
96− delete(m.cache, evictedEntry.key)
97− delete(m.hashableItems, evictedEntry.key) // if you're keeping track of original items
98− }
99− }
100−
101− // Add the value to the cache and the evictionList
102− newEntry := &entry[T]{
103− key: hashedKey,
104− value: value,
105− }
106− element := m.evictionList.PushFront(newEntry)
107− m.cache[hashedKey] = element
108− m.hashableItems[hashedKey] = value // if you're keeping track of original items
109−}
110−
111−// HString is a type that implements the Hasher interface for strings.
112−type HString string
113−
114−// Hash is a method that returns the hash of the string.
115−func (h HString) Hash() string {
116− return fmt.Sprintf("%x", sha256.Sum256([]byte(h)))
117−}
118−
119−// HInt is a type that implements the Hasher interface for integers.
120−type HInt int
121−
122−// Hash is a method that returns the hash of the integer.
123−func (h HInt) Hash() string {
124− return fmt.Sprintf("%x", sha256.Sum256(fmt.Appendf(nil, "%d", h)))
125−}
vendor/charm.land/bubbles/v2/internal/runeutil/runeutil.godeleted−102
@@ -1,102 +0,0 @@
1−// Package runeutil provides utility functions for tidying up incoming runes
2−// from Key messages.
3−package runeutil
4−
5−import (
6− "unicode"
7− "unicode/utf8"
8−)
9−
10−// Sanitizer is a helper for bubble widgets that want to process
11−// Runes from input key messages.
12−type Sanitizer interface {
13− // Sanitize removes control characters from runes in a KeyRunes
14− // message, and optionally replaces newline/carriage return/tabs by a
15− // specified character.
16− //
17− // The rune array is modified in-place if possible. In that case, the
18− // returned slice is the original slice shortened after the control
19− // characters have been removed/translated.
20− Sanitize(runes []rune) []rune
21−}
22−
23−// NewSanitizer constructs a rune sanitizer.
24−func NewSanitizer(opts ...Option) Sanitizer {
25− s := sanitizer{
26− replaceNewLine: []rune("\n"),
27− replaceTab: []rune(" "),
28− }
29− for _, o := range opts {
30− s = o(s)
31− }
32− return &s
33−}
34−
35−// Option is the type of option that can be passed to Sanitize().
36−type Option func(sanitizer) sanitizer
37−
38−// ReplaceTabs replaces tabs by the specified string.
39−func ReplaceTabs(tabRepl string) Option {
40− return func(s sanitizer) sanitizer {
41− s.replaceTab = []rune(tabRepl)
42− return s
43− }
44−}
45−
46−// ReplaceNewlines replaces newline characters by the specified string.
47−func ReplaceNewlines(nlRepl string) Option {
48− return func(s sanitizer) sanitizer {
49− s.replaceNewLine = []rune(nlRepl)
50− return s
51− }
52−}
53−
54−func (s *sanitizer) Sanitize(runes []rune) []rune {
55− // dstrunes are where we are storing the result.
56− dstrunes := runes[:0:len(runes)]
57− // copied indicates whether dstrunes is an alias of runes
58− // or a copy. We need a copy when dst moves past src.
59− // We use this as an optimization to avoid allocating
60− // a new rune slice in the common case where the output
61− // is smaller or equal to the input.
62− copied := false
63−
64− for src := range runes {
65− r := runes[src]
66− switch {
67− case r == utf8.RuneError:
68− // skip
69−
70− case r == '\r' || r == '\n':
71− if len(dstrunes)+len(s.replaceNewLine) > src && !copied {
72− dst := len(dstrunes)
73− dstrunes = make([]rune, dst, len(runes)+len(s.replaceNewLine))
74− copy(dstrunes, runes[:dst])
75− copied = true
76− }
77− dstrunes = append(dstrunes, s.replaceNewLine...)
78−
79− case r == '\t':
80− if len(dstrunes)+len(s.replaceTab) > src && !copied {
81− dst := len(dstrunes)
82− dstrunes = make([]rune, dst, len(runes)+len(s.replaceTab))
83− copy(dstrunes, runes[:dst])
84− copied = true
85− }
86− dstrunes = append(dstrunes, s.replaceTab...)
87−
88− case unicode.IsControl(r):
89− // Other control characters: skip.
90−
91− default:
92− // Keep the character.
93− dstrunes = append(dstrunes, runes[src])
94− }
95− }
96− return dstrunes
97−}
98−
99−type sanitizer struct {
100− replaceNewLine []rune
101− replaceTab []rune
102−}
vendor/charm.land/bubbles/v2/key/key.godeleted−140
@@ -1,140 +0,0 @@
1−// Package key provides some types and functions for generating user-definable
2−// keymappings useful in Bubble Tea components. There are a few different ways
3−// you can define a keymapping with this package. Here's one example:
4−//
5−// type KeyMap struct {
6−// Up key.Binding
7−// Down key.Binding
8−// }
9−//
10−// var DefaultKeyMap = KeyMap{
11−// Up: key.NewBinding(
12−// key.WithKeys("k", "up"), // actual keybindings
13−// key.WithHelp("↑/k", "move up"), // corresponding help text
14−// ),
15−// Down: key.NewBinding(
16−// key.WithKeys("j", "down"),
17−// key.WithHelp("↓/j", "move down"),
18−// ),
19−// }
20−//
21−// func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
22−// switch msg := msg.(type) {
23−// case tea.KeyPressMsg:
24−// switch {
25−// case key.Matches(msg, DefaultKeyMap.Up):
26−// // The user pressed up
27−// case key.Matches(msg, DefaultKeyMap.Down):
28−// // The user pressed down
29−// }
30−// }
31−//
32−// // ...
33−// }
34−//
35−// The help information, which is not used in the example above, can be used
36−// to render help text for keystrokes in your views.
37−package key
38−
39−import "fmt"
40−
41−// Binding describes a set of keybindings and, optionally, their associated
42−// help text.
43−type Binding struct {
44− keys []string
45− help Help
46− disabled bool
47−}
48−
49−// BindingOpt is an initialization option for a keybinding. It's used as an
50−// argument to NewBinding.
51−type BindingOpt func(*Binding)
52−
53−// NewBinding returns a new keybinding from a set of BindingOpt options.
54−func NewBinding(opts ...BindingOpt) Binding {
55− b := &Binding{}
56− for _, opt := range opts {
57− opt(b)
58− }
59− return *b
60−}
61−
62−// WithKeys initializes a keybinding with the given keystrokes.
63−func WithKeys(keys ...string) BindingOpt {
64− return func(b *Binding) {
65− b.keys = keys
66− }
67−}
68−
69−// WithHelp initializes a keybinding with the given help text.
70−func WithHelp(key, desc string) BindingOpt {
71− return func(b *Binding) {
72− b.help = Help{Key: key, Desc: desc}
73− }
74−}
75−
76−// WithDisabled initializes a disabled keybinding.
77−func WithDisabled() BindingOpt {
78− return func(b *Binding) {
79− b.disabled = true
80− }
81−}
82−
83−// SetKeys sets the keys for the keybinding.
84−func (b *Binding) SetKeys(keys ...string) {
85− b.keys = keys
86−}
87−
88−// Keys returns the keys for the keybinding.
89−func (b Binding) Keys() []string {
90− return b.keys
91−}
92−
93−// SetHelp sets the help text for the keybinding.
94−func (b *Binding) SetHelp(key, desc string) {
95− b.help = Help{Key: key, Desc: desc}
96−}
97−
98−// Help returns the Help information for the keybinding.
99−func (b Binding) Help() Help {
100− return b.help
101−}
102−
103−// Enabled returns whether or not the keybinding is enabled. Disabled
104−// keybindings won't be activated and won't show up in help. Keybindings are
105−// enabled by default.
106−func (b Binding) Enabled() bool {
107− return !b.disabled && b.keys != nil
108−}
109−
110−// SetEnabled enables or disables the keybinding.
111−func (b *Binding) SetEnabled(v bool) {
112− b.disabled = !v
113−}
114−
115−// Unbind removes the keys and help from this binding, effectively nullifying
116−// it. This is a step beyond disabling it, since applications can enable
117−// or disable key bindings based on application state.
118−func (b *Binding) Unbind() {
119− b.keys = nil
120− b.help = Help{}
121−}
122−
123−// Help is help information for a given keybinding.
124−type Help struct {
125− Key string
126− Desc string
127−}
128−
129−// Matches checks if the given key matches the given bindings.
130−func Matches[Key fmt.Stringer](k Key, b ...Binding) bool {
131− keys := k.String()
132− for _, binding := range b {
133− for _, v := range binding.keys {
134− if keys == v && binding.Enabled() {
135− return true
136− }
137− }
138− }
139− return false
140−}
vendor/charm.land/bubbles/v2/spinner/spinner.godeleted−211
@@ -1,211 +0,0 @@
1−// Package spinner provides a spinner component for Bubble Tea applications.
2−package spinner
3−
4−import (
5− "sync/atomic"
6− "time"
7−
8− tea "charm.land/bubbletea/v2"
9− "charm.land/lipgloss/v2"
10−)
11−
12−// Internal ID management. Used during animating to ensure that frame messages
13−// are received only by spinner components that sent them.
14−var lastID int64
15−
16−func nextID() int {
17− return int(atomic.AddInt64(&lastID, 1))
18−}
19−
20−// Spinner is a set of frames used in animating the spinner.
21−type Spinner struct {
22− Frames []string
23− FPS time.Duration
24−}
25−
26−// Some spinners to choose from. You could also make your own.
27−var (
28− Line = Spinner{
29− Frames: []string{"|", "/", "-", "\\"},
30− FPS: time.Second / 10, //nolint:mnd
31− }
32− Dot = Spinner{
33− Frames: []string{"⣾ ", "⣽ ", "⣻ ", "⢿ ", "⡿ ", "⣟ ", "⣯ ", "⣷ "},
34− FPS: time.Second / 10, //nolint:mnd
35− }
36− MiniDot = Spinner{
37− Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
38− FPS: time.Second / 12, //nolint:mnd
39− }
40− Jump = Spinner{
41− Frames: []string{"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"},
42− FPS: time.Second / 10, //nolint:mnd
43− }
44− Pulse = Spinner{
45− Frames: []string{"█", "▓", "▒", "░"},
46− FPS: time.Second / 8, //nolint:mnd
47− }
48− Points = Spinner{
49− Frames: []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●"},
50− FPS: time.Second / 7, //nolint:mnd
51− }
52− Globe = Spinner{
53− Frames: []string{"🌍", "🌎", "🌏"},
54− FPS: time.Second / 4, //nolint:mnd
55− }
56− Moon = Spinner{
57− Frames: []string{"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"},
58− FPS: time.Second / 8, //nolint:mnd
59− }
60− Monkey = Spinner{
61− Frames: []string{"🙈", "🙉", "🙊"},
62− FPS: time.Second / 3, //nolint:mnd
63− }
64− Meter = Spinner{
65− Frames: []string{
66− "▱▱▱",
67− "▰▱▱",
68− "▰▰▱",
69− "▰▰▰",
70− "▰▰▱",
71− "▰▱▱",
72− "▱▱▱",
73− },
74− FPS: time.Second / 7, //nolint:mnd
75− }
76− Hamburger = Spinner{
77− Frames: []string{"☱", "☲", "☴", "☲"},
78− FPS: time.Second / 3, //nolint:mnd
79− }
80− Ellipsis = Spinner{
81− Frames: []string{"", ".", "..", "..."},
82− FPS: time.Second / 3, //nolint:mnd
83− }
84−)
85−
86−// Model contains the state for the spinner. Use New to create new models
87−// rather than using Model as a struct literal.
88−type Model struct {
89− // Spinner settings to use. See type Spinner.
90− Spinner Spinner
91−
92− // Style sets the styling for the spinner. Most of the time you'll just
93− // want foreground and background coloring, and potentially some padding.
94− //
95− // For an introduction to styling with Lip Gloss see:
96− // https://github.com/charmbracelet/lipgloss
97− Style lipgloss.Style
98−
99− frame int
100− id int
101− tag int
102−}
103−
104−// ID returns the spinner's unique ID.
105−func (m Model) ID() int {
106− return m.id
107−}
108−
109−// New returns a model with default values.
110−func New(opts ...Option) Model {
111− m := Model{
112− Spinner: Line,
113− id: nextID(),
114− }
115−
116− for _, opt := range opts {
117− opt(&m)
118− }
119−
120− return m
121−}
122−
123−// TickMsg indicates that the timer has ticked and we should render a frame.
124−type TickMsg struct {
125− Time time.Time
126− tag int
127− ID int
128−}
129−
130−// Update is the Tea update function.
131−func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
132− switch msg := msg.(type) {
133− case TickMsg:
134− // If an ID is set, and the ID doesn't belong to this spinner, reject
135− // the message.
136− if msg.ID > 0 && msg.ID != m.id {
137− return m, nil
138− }
139−
140− // If a tag is set, and it's not the one we expect, reject the message.
141− // This prevents the spinner from receiving too many messages and
142− // thus spinning too fast.
143− if msg.tag > 0 && msg.tag != m.tag {
144− return m, nil
145− }
146−
147− m.frame++
148− if m.frame >= len(m.Spinner.Frames) {
149− m.frame = 0
150− }
151−
152− m.tag++
153− return m, m.tick(m.id, m.tag)
154− default:
155− return m, nil
156− }
157−}
158−
159−// View renders the model's view.
160−func (m Model) View() string {
161− if m.frame >= len(m.Spinner.Frames) {
162− return "(error)"
163− }
164−
165− return m.Style.Render(m.Spinner.Frames[m.frame])
166−}
167−
168−// Tick is the command used to advance the spinner one frame. Use this command
169−// to effectively start the spinner.
170−func (m Model) Tick() tea.Msg {
171− return TickMsg{
172− // The time at which the tick occurred.
173− Time: time.Now(),
174−
175− // The ID of the spinner that this message belongs to. This can be
176− // helpful when routing messages, however bear in mind that spinners
177− // will ignore messages that don't contain ID by default.
178− ID: m.id,
179−
180− tag: m.tag,
181− }
182−}
183−
184−func (m Model) tick(id, tag int) tea.Cmd {
185− return tea.Tick(m.Spinner.FPS, func(t time.Time) tea.Msg {
186− return TickMsg{
187− Time: t,
188− ID: id,
189− tag: tag,
190− }
191− })
192−}
193−
194−// Option is used to set options in New. For example:
195−//
196−// spinner := New(WithSpinner(Dot))
197−type Option func(*Model)
198−
199−// WithSpinner is an option to set the spinner. Pass this to [Spinner.New].
200−func WithSpinner(spinner Spinner) Option {
201− return func(m *Model) {
202− m.Spinner = spinner
203− }
204−}
205−
206−// WithStyle is an option to set the spinner style. Pass this to [Spinner.New].
207−func WithStyle(style lipgloss.Style) Option {
208− return func(m *Model) {
209− m.Style = style
210− }
211−}
vendor/charm.land/bubbles/v2/textarea/textarea.godeleted−1897

This diff is too big to show here.

vendor/charm.land/bubbles/v2/textinput/styles.godeleted−96

This diff is too big to show here.

vendor/charm.land/bubbles/v2/textinput/textinput.godeleted−968

This diff is too big to show here.

vendor/charm.land/bubbles/v2/viewport/highlight.godeleted−141

This diff is too big to show here.

vendor/charm.land/bubbles/v2/viewport/keymap.godeleted−58

This diff is too big to show here.

vendor/charm.land/bubbles/v2/viewport/viewport.godeleted−765

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/.gitattributesdeleted−1

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/.gitignoredeleted−23

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/.golangci.ymldeleted−47

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/.goreleaser.ymldeleted−5

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/LICENSEdeleted−21

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/README.mddeleted−402

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/Taskfile.yamldeleted−38

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/UPGRADE_GUIDE_V2.mddeleted−573

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/clipboard.godeleted−70

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/color.godeleted−91

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/commands.godeleted−175

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/cursed_renderer.godeleted−860

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/cursor.godeleted−28

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/environ.godeleted−34

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/exec.godeleted−129

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/focus.godeleted−9

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/input.godeleted−54

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/key.godeleted−371

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/keyboard.godeleted−59

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/logging.godeleted−53

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/mod.godeleted−27

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/mouse.godeleted−144

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/nil_renderer.godeleted−53

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/options.godeleted−168

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/paste.godeleted−20

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/profile.godeleted−15

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/raw.godeleted−37

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/renderer.godeleted−104

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/screen.godeleted−68

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/signals_unix.godeleted−33

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/signals_windows.godeleted−10

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/tea.godeleted−1439

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/termcap.godeleted−48

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/termios_bsd.godeleted−13

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/termios_other.godeleted−8

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/termios_unix.godeleted−14

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/termios_windows.godeleted−11

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/tty.godeleted−136

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/tty_unix.godeleted−47

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/tty_windows.godeleted−64

This diff is too big to show here.

vendor/charm.land/bubbletea/v2/xterm.godeleted−22

This diff is too big to show here.

vendor/charm.land/catwalk/LICENSEdeleted−21

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/aihubmix.jsondeleted−3746

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/alibaba-singapore.jsondeleted−141

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/alibaba-united-states.jsondeleted−45

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/anthropic.jsondeleted−183

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/avian.jsondeleted−125

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/azure.jsondeleted−225

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/baseten.jsondeleted−187

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/bedrock-europe.jsondeleted−115

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/bedrock-united-states.jsondeleted−143

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/cerebras.jsondeleted−42

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/chutes.jsondeleted−227

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/copilot.jsondeleted−310

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/cortecs.jsondeleted−1697

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/deepseek.jsondeleted−45

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/fireworks.jsondeleted−171

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/gemini.jsondeleted−91

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/groq.jsondeleted−36

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/huggingface.jsondeleted−267

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/ionet.jsondeleted−335

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/kimi.jsondeleted−23

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/minimax-china.jsondeleted−91

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/minimax.jsondeleted−91

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/moonshot.jsondeleted−131

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/nebius.jsondeleted−419

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/neuralwatt.jsondeleted−155

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/openai.jsondeleted−352

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/opencode-go.jsondeleted−351

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/opencode-zen.jsondeleted−908

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/openrouter.jsondeleted−3831

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/qiniucloud.jsondeleted−179

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/scaleway.jsondeleted−141

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/synthetic.jsondeleted−197

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/venice.jsondeleted−1173

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/vercel.jsondeleted−3072

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/vertexai.jsondeleted−170

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/xai.jsondeleted−59

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/zai.jsondeleted−136

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/zhipu-coding.jsondeleted−136

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/configs/zhipu.jsondeleted−120

This diff is too big to show here.

vendor/charm.land/catwalk/internal/providers/providers.godeleted−349

This diff is too big to show here.

vendor/charm.land/catwalk/pkg/catwalk/client.godeleted−79

This diff is too big to show here.

vendor/charm.land/catwalk/pkg/catwalk/pkg.godeleted−2

This diff is too big to show here.

vendor/charm.land/catwalk/pkg/catwalk/provider.godeleted−157

This diff is too big to show here.

vendor/charm.land/catwalk/pkg/embedded/embedded.godeleted−13

This diff is too big to show here.

vendor/charm.land/fang/v2/.gitignoredeleted−3

This diff is too big to show here.

vendor/charm.land/fang/v2/.golangci.ymldeleted−47

This diff is too big to show here.

vendor/charm.land/fang/v2/.goreleaser.ymldeleted−5

This diff is too big to show here.

vendor/charm.land/fang/v2/LICENSE.mddeleted−21

This diff is too big to show here.

vendor/charm.land/fang/v2/README.mddeleted−92

This diff is too big to show here.

vendor/charm.land/fang/v2/Taskfile.ymldeleted−7

This diff is too big to show here.

vendor/charm.land/fang/v2/UPGRADE_GUIDE_V2.mddeleted−127

This diff is too big to show here.

vendor/charm.land/fang/v2/fang.godeleted−208

This diff is too big to show here.

vendor/charm.land/fang/v2/fang_other.godeleted−10

This diff is too big to show here.

vendor/charm.land/fang/v2/fang_windows.godeleted−30

This diff is too big to show here.

vendor/charm.land/fang/v2/help.godeleted−482

This diff is too big to show here.

vendor/charm.land/fang/v2/theme.godeleted−222

This diff is too big to show here.

vendor/charm.land/fantasy/.editorconfigdeleted−18

This diff is too big to show here.

vendor/charm.land/fantasy/.gitattributesdeleted−1

This diff is too big to show here.

vendor/charm.land/fantasy/.gitignoredeleted−42

This diff is too big to show here.

vendor/charm.land/fantasy/.golangci.ymldeleted−44

This diff is too big to show here.

vendor/charm.land/fantasy/.goreleaser.ymldeleted−6

This diff is too big to show here.

vendor/charm.land/fantasy/AGENTS.mddeleted−29

This diff is too big to show here.

vendor/charm.land/fantasy/LICENSEdeleted−177

This diff is too big to show here.

vendor/charm.land/fantasy/NOTICEdeleted−13

This diff is too big to show here.

vendor/charm.land/fantasy/README.mddeleted−95

This diff is too big to show here.

vendor/charm.land/fantasy/Taskfile.yamldeleted−45

This diff is too big to show here.

vendor/charm.land/fantasy/agent.godeleted−1702

This diff is too big to show here.

vendor/charm.land/fantasy/content.godeleted−625

This diff is too big to show here.

vendor/charm.land/fantasy/content_json.godeleted−1046

This diff is too big to show here.

vendor/charm.land/fantasy/crush.jsondeleted−6

This diff is too big to show here.

vendor/charm.land/fantasy/cspell.jsondeleted−1

This diff is too big to show here.

vendor/charm.land/fantasy/doc.godeleted−2

This diff is too big to show here.

vendor/charm.land/fantasy/errors.godeleted−155

This diff is too big to show here.

vendor/charm.land/fantasy/jsonrepair/jsonrepair.godeleted−1648

This diff is too big to show here.

vendor/charm.land/fantasy/model.godeleted−261

This diff is too big to show here.

vendor/charm.land/fantasy/model_json.godeleted−156

This diff is too big to show here.

vendor/charm.land/fantasy/object.godeleted−236

This diff is too big to show here.

vendor/charm.land/fantasy/object/object.godeleted−620

This diff is too big to show here.

vendor/charm.land/fantasy/provider.godeleted−11

This diff is too big to show here.

vendor/charm.land/fantasy/provider_registry.godeleted−106

This diff is too big to show here.

vendor/charm.land/fantasy/providers/anthropic/anthropic.godeleted−1707

This diff is too big to show here.

vendor/charm.land/fantasy/providers/anthropic/bedrock.godeleted−15

This diff is too big to show here.

vendor/charm.land/fantasy/providers/anthropic/call_useragent.godeleted−14

This diff is too big to show here.

vendor/charm.land/fantasy/providers/anthropic/computer_use.godeleted−427

This diff is too big to show here.

vendor/charm.land/fantasy/providers/anthropic/error.godeleted−67

This diff is too big to show here.

vendor/charm.land/fantasy/providers/anthropic/google.godeleted−11

This diff is too big to show here.

vendor/charm.land/fantasy/providers/anthropic/provider_options.godeleted−283

This diff is too big to show here.

vendor/charm.land/fantasy/providers/anthropic/sanitize.godeleted−33

This diff is too big to show here.

vendor/charm.land/fantasy/providers/azure/README.mddeleted−6

This diff is too big to show here.

vendor/charm.land/fantasy/providers/azure/azure.godeleted−125

This diff is too big to show here.

vendor/charm.land/fantasy/providers/bedrock/README.mddeleted−10

This diff is too big to show here.

vendor/charm.land/fantasy/providers/bedrock/bedrock.godeleted−87

This diff is too big to show here.

vendor/charm.land/fantasy/providers/google/README.mddeleted−63

This diff is too big to show here.

vendor/charm.land/fantasy/providers/google/auth.godeleted−15

This diff is too big to show here.

vendor/charm.land/fantasy/providers/google/call_useragent.godeleted−53

This diff is too big to show here.

vendor/charm.land/fantasy/providers/google/error.godeleted−51

This diff is too big to show here.

vendor/charm.land/fantasy/providers/google/google.godeleted−1488

This diff is too big to show here.

vendor/charm.land/fantasy/providers/google/provider_options.godeleted−146

This diff is too big to show here.

vendor/charm.land/fantasy/providers/google/slice.godeleted−11

This diff is too big to show here.

vendor/charm.land/fantasy/providers/internal/httpheaders/httpheaders.godeleted−60

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openai/call_useragent.godeleted−31

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openai/error.godeleted−92

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openai/language_model.godeleted−990

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openai/language_model_hooks.godeleted−668

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openai/openai.godeleted−220

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openai/provider_options.godeleted−217

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openai/responses_language_model.godeleted−1678

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openai/responses_options.godeleted−395

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openaicompat/language_model_hooks.godeleted−516

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openaicompat/openaicompat.godeleted−140

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openaicompat/provider_options.godeleted−161

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openrouter/language_model_hooks.godeleted−1063

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openrouter/openrouter.godeleted−121

This diff is too big to show here.

vendor/charm.land/fantasy/providers/openrouter/provider_options.godeleted−218

This diff is too big to show here.

vendor/charm.land/fantasy/providers/vercel/language_model_hooks.godeleted−1074

This diff is too big to show here.

vendor/charm.land/fantasy/providers/vercel/provider_options.godeleted−195

This diff is too big to show here.

vendor/charm.land/fantasy/providers/vercel/vercel.godeleted−122

This diff is too big to show here.

vendor/charm.land/fantasy/retry.godeleted−155

This diff is too big to show here.

vendor/charm.land/fantasy/schema/schema.godeleted−442

This diff is too big to show here.

vendor/charm.land/fantasy/tool.godeleted−176

This diff is too big to show here.

vendor/charm.land/fantasy/util.godeleted−22

This diff is too big to show here.

vendor/charm.land/fantasy/version.godeleted−12

This diff is too big to show here.

vendor/charm.land/fantasy/version.txtdeleted−1

This diff is too big to show here.

vendor/charm.land/glamour/v2/.editorconfigdeleted−18

This diff is too big to show here.

vendor/charm.land/glamour/v2/.gitattributesdeleted−2

This diff is too big to show here.

vendor/charm.land/glamour/v2/.gitignoredeleted−2

This diff is too big to show here.

vendor/charm.land/glamour/v2/.golangci.ymldeleted−44

This diff is too big to show here.

vendor/charm.land/glamour/v2/.goreleaser.ymldeleted−6

This diff is too big to show here.

vendor/charm.land/glamour/v2/CONTRIBUTING.mddeleted−23

This diff is too big to show here.

vendor/charm.land/glamour/v2/LICENSEdeleted−21

This diff is too big to show here.

vendor/charm.land/glamour/v2/README.mddeleted−129

This diff is too big to show here.

vendor/charm.land/glamour/v2/Taskfile.ymldeleted−25

This diff is too big to show here.

vendor/charm.land/glamour/v2/UPGRADE_GUIDE_V2.mddeleted−279

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/ansi.godeleted−3

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/baseelement.godeleted−157

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/blockelement.godeleted−66

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/blockstack.godeleted−95

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/codeblock.godeleted−151

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/codespan.godeleted−15

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/context.godeleted−38

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/elements.godeleted−481

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/emphasis.godeleted−47

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/heading.godeleted−80

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/image.godeleted−57

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/link.godeleted−112

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/listitem.godeleted−29

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/margin.godeleted−231

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/paragraph.godeleted−63

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/renderer.godeleted−166

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/style.godeleted−252

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/table.godeleted−206

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/table_links.godeleted−234

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/task.godeleted−27

This diff is too big to show here.

vendor/charm.land/glamour/v2/ansi/templatehelper.godeleted−86

This diff is too big to show here.

vendor/charm.land/glamour/v2/examples.shdeleted−19

This diff is too big to show here.

vendor/charm.land/glamour/v2/gallery.shdeleted−23

This diff is too big to show here.

vendor/charm.land/glamour/v2/glamour.godeleted−294

This diff is too big to show here.

vendor/charm.land/glamour/v2/internal/autolink/autolink.godeleted−55

This diff is too big to show here.

vendor/charm.land/glamour/v2/styles/README.mddeleted−565

This diff is too big to show here.

vendor/charm.land/glamour/v2/styles/ascii.jsondeleted−87

This diff is too big to show here.

vendor/charm.land/glamour/v2/styles/dark.jsondeleted−191

This diff is too big to show here.

vendor/charm.land/glamour/v2/styles/dracula.godeleted−216

This diff is too big to show here.

vendor/charm.land/glamour/v2/styles/dracula.jsondeleted−188

This diff is too big to show here.

vendor/charm.land/glamour/v2/styles/light.jsondeleted−190

This diff is too big to show here.

vendor/charm.land/glamour/v2/styles/notty.jsondeleted−87

This diff is too big to show here.

vendor/charm.land/glamour/v2/styles/pink.jsondeleted−90

This diff is too big to show here.

vendor/charm.land/glamour/v2/styles/styles.godeleted−691

This diff is too big to show here.

vendor/charm.land/glamour/v2/styles/tokyo-night.godeleted−209

This diff is too big to show here.

vendor/charm.land/glamour/v2/styles/tokyo-night.jsondeleted−182

This diff is too big to show here.

vendor/charm.land/glamour/v2/styles/tokyo_night.jsondeleted−182

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/.editorconfigdeleted−18

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/.gitattributesdeleted−1

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/.gitignoredeleted−3

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/.golangci.ymldeleted−50

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/.goreleaser.ymldeleted−5

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/LICENSEdeleted−21

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/README.mddeleted−996

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/Taskfile.yamldeleted−24

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/UPGRADE_GUIDE_V2.mddeleted−504

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/align.godeleted−82

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/ansi_unix.godeleted−8

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/ansi_windows.godeleted−30

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/blending.godeleted−196

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/borders.godeleted−587

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/canvas.godeleted−88

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/color.godeleted−359

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/get.godeleted−657

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/join.godeleted−175

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/layer.godeleted−327

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/lipgloss.godeleted−3

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/position.godeleted−134

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/query.godeleted−92

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/ranges.godeleted−48

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/runes.godeleted−43

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/set.godeleted−932

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/size.godeleted−40

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/style.godeleted−637

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/table/resizing.godeleted−489

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/table/rows.godeleted−130

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/table/table.godeleted−568

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/table/util.godeleted−44

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/terminal.godeleted−124

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/tree/children.godeleted−100

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/tree/enumerator.godeleted−74

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/tree/renderer.godeleted−165

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/tree/tree.godeleted−407

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/unset.godeleted−359

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/whitespace.godeleted−76

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/wrap.godeleted−113

This diff is too big to show here.

vendor/charm.land/lipgloss/v2/writer.godeleted−160

This diff is too big to show here.

vendor/charm.land/log/v2/.gitignoredeleted−17

This diff is too big to show here.

vendor/charm.land/log/v2/.golangci.ymldeleted−50

This diff is too big to show here.

vendor/charm.land/log/v2/.goreleaser.ymldeleted−6

This diff is too big to show here.

vendor/charm.land/log/v2/LICENSEdeleted−21

This diff is too big to show here.

vendor/charm.land/log/v2/README.mddeleted−367

This diff is too big to show here.

vendor/charm.land/log/v2/UPGRADE_GUIDE_V2.mddeleted−320

This diff is too big to show here.

vendor/charm.land/log/v2/context.godeleted−23

This diff is too big to show here.

vendor/charm.land/log/v2/doc.godeleted−3

This diff is too big to show here.

vendor/charm.land/log/v2/formatter.godeleted−27

This diff is too big to show here.

vendor/charm.land/log/v2/json.godeleted−157

This diff is too big to show here.

vendor/charm.land/log/v2/level.godeleted−65

This diff is too big to show here.

vendor/charm.land/log/v2/logfmt.godeleted−32

This diff is too big to show here.

vendor/charm.land/log/v2/logger.godeleted−413

This diff is too big to show here.

vendor/charm.land/log/v2/logger_121.godeleted−72

This diff is too big to show here.

vendor/charm.land/log/v2/logger_no121.godeleted−69

This diff is too big to show here.

vendor/charm.land/log/v2/options.godeleted−61

This diff is too big to show here.

vendor/charm.land/log/v2/pkg.godeleted−248

This diff is too big to show here.

vendor/charm.land/log/v2/stdlog.godeleted−67

This diff is too big to show here.

vendor/charm.land/log/v2/styles.godeleted−83

This diff is too big to show here.

vendor/charm.land/log/v2/text.godeleted−272

This diff is too big to show here.

vendor/charm.land/x/vcr/LICENSEdeleted−21

This diff is too big to show here.

vendor/charm.land/x/vcr/doc.godeleted−3

This diff is too big to show here.

vendor/charm.land/x/vcr/hooks.godeleted−33

This diff is too big to show here.

vendor/charm.land/x/vcr/marshaler.godeleted−19

This diff is too big to show here.

vendor/charm.land/x/vcr/matcher.godeleted−70

This diff is too big to show here.

vendor/charm.land/x/vcr/recorder.godeleted−70

This diff is too big to show here.

vendor/cloud.google.com/go/LICENSEdeleted−202

This diff is too big to show here.

vendor/cloud.google.com/go/auth/CHANGES.mddeleted−483

This diff is too big to show here.

vendor/cloud.google.com/go/auth/LICENSEdeleted−202

This diff is too big to show here.

vendor/cloud.google.com/go/auth/README.mddeleted−40

This diff is too big to show here.

vendor/cloud.google.com/go/auth/auth.godeleted−618

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/compute.godeleted−102

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/detect.godeleted−471

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/doc.godeleted−45

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/filetypes.godeleted−329

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.godeleted−531

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.godeleted−284

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.godeleted−431

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.godeleted−78

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.godeleted−74

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.godeleted−30

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.godeleted−93

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.godeleted−220

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.godeleted−115

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.godeleted−198

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/impersonate/idtoken.godeleted−105

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.godeleted−168

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.godeleted−167

This diff is too big to show here.

vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.godeleted−89

This diff is too big to show here.

vendor/cloud.google.com/go/auth/grpctransport/dial_socketopt.godeleted−62

This diff is too big to show here.

vendor/cloud.google.com/go/auth/grpctransport/directpath.godeleted−177

This diff is too big to show here.

vendor/cloud.google.com/go/auth/grpctransport/grpctransport.godeleted−675

This diff is too big to show here.

vendor/cloud.google.com/go/auth/grpctransport/pool.godeleted−119

This diff is too big to show here.

vendor/cloud.google.com/go/auth/httptransport/httptransport.godeleted−254

This diff is too big to show here.

vendor/cloud.google.com/go/auth/httptransport/transport.godeleted−548

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/compute/compute.godeleted−65

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/compute/manufacturer.godeleted−22

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/compute/manufacturer_linux.godeleted−23

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/compute/manufacturer_windows.godeleted−46

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/credsfile/credsfile.godeleted−63

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/credsfile/filetype.godeleted−159

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/credsfile/parse.godeleted−99

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/internal.godeleted−285

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/jwt/jwt.godeleted−208

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/retry/retry.godeleted−117

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/transport/cba.godeleted−361

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/transport/cert/default_cert.godeleted−65

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/transport/cert/enterprise_cert.godeleted−54

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/transport/cert/secureconnect_cert.godeleted−124

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/transport/cert/workload_cert.godeleted−138

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/transport/headers/headers.godeleted−61

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/transport/s2a.godeleted−138

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/transport/transport.godeleted−137

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/trustboundary/external_accounts_config_providers.godeleted−100

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/trustboundary/trust_boundary.godeleted−392

This diff is too big to show here.

vendor/cloud.google.com/go/auth/internal/version.godeleted−20

This diff is too big to show here.

vendor/cloud.google.com/go/auth/oauth2adapt/CHANGES.mddeleted−82

This diff is too big to show here.

vendor/cloud.google.com/go/auth/oauth2adapt/LICENSEdeleted−202

This diff is too big to show here.

vendor/cloud.google.com/go/auth/oauth2adapt/oauth2adapt.godeleted−200

This diff is too big to show here.

vendor/cloud.google.com/go/auth/threelegged.godeleted−382

This diff is too big to show here.

vendor/cloud.google.com/go/civil/civil.godeleted−500

This diff is too big to show here.

vendor/cloud.google.com/go/compute/metadata/CHANGES.mddeleted−115

This diff is too big to show here.

vendor/cloud.google.com/go/compute/metadata/LICENSEdeleted−202

This diff is too big to show here.

vendor/cloud.google.com/go/compute/metadata/README.mddeleted−27

This diff is too big to show here.

vendor/cloud.google.com/go/compute/metadata/log.godeleted−149

This diff is too big to show here.

vendor/cloud.google.com/go/compute/metadata/metadata.godeleted−937

This diff is too big to show here.

vendor/cloud.google.com/go/compute/metadata/retry.godeleted−117

This diff is too big to show here.

vendor/cloud.google.com/go/compute/metadata/retry_linux.godeleted−31

This diff is too big to show here.

vendor/cloud.google.com/go/compute/metadata/syscheck.godeleted−28

This diff is too big to show here.

vendor/cloud.google.com/go/compute/metadata/syscheck_linux.godeleted−30

This diff is too big to show here.

vendor/cloud.google.com/go/compute/metadata/syscheck_windows.godeleted−39

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/.gitignoredeleted−1

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/ARCHITECTURE.mddeleted−244

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/LICENSEdeleted−61

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/README.mddeleted−86

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/constants.godeleted−65

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/internal/winrt/data/xml/dom/xmldocument.godeleted−73

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/internal/winrt/ui/notifications/toastnotification.godeleted−88

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/internal/winrt/ui/notifications/toastnotificationmanager.godeleted−53

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/internal/winrt/ui/notifications/toastnotificationmanagerforuser.godeleted−68

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/internal/winrt/ui/notifications/toastnotifier.godeleted−64

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/tmpl/powershell.go.tmpldeleted−14

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/tmpl/tmpl.godeleted−28

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/tmpl/xml.go.tmpldeleted−34

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/toast.godeleted−225

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/wintoast/bind.godeleted−86

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/wintoast/bind_noop.godeleted−19

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/wintoast/bind_windows.godeleted−174

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/wintoast/impl.godeleted−179

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/wintoast/procs.godeleted−37

This diff is too big to show here.

vendor/git.sr.ht/~jackmordaunt/go-toast/wintoast/registry.godeleted−110

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/CHANGELOG.mddeleted−849

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/LICENSE.txtdeleted−21

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/README.mddeleted−39

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/ci.ymldeleted−29

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud/cloud.godeleted−44

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud/doc.godeleted−53

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/core.godeleted−173

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/doc.godeleted−264

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/errors.godeleted−17

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/etag.godeleted−57

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/exported.godeleted−175

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/pipeline.godeleted−77

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/request.godeleted−260

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/response_error.godeleted−201

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/log/log.godeleted−50

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/async/async.godeleted−159

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/body/body.godeleted−135

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/fake/fake.godeleted−133

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/loc/loc.godeleted−123

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/op/op.godeleted−148

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/poller.godeleted−24

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/util.godeleted−212

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/constants.godeleted−44

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/shared.godeleted−149

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/log/doc.godeleted−10

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/log/log.godeleted−55

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/policy/doc.godeleted−10

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/policy/policy.godeleted−198

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/doc.godeleted−10

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/errors.godeleted−27

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pager.godeleted−138

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pipeline.godeleted−94

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_api_version.godeleted−75

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_bearer_token.godeleted−236

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_body_download.godeleted−72

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_http_header.godeleted−40

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_http_trace.godeleted−154

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_include_response.godeleted−35

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_key_credential.godeleted−64

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_logging.godeleted−264

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_request_id.godeleted−34

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_retry.godeleted−276

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_sas_credential.godeleted−55

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_telemetry.godeleted−83

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/poller.godeleted−396

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/request.godeleted−281

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/response.godeleted−109

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_other.godeleted−15

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_wasm.godeleted−15

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_http_client.godeleted−48

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming/doc.godeleted−9

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming/progress.godeleted−89

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing/constants.godeleted−41

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing/tracing.godeleted−191

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/LICENSE.txtdeleted−21

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/diag/diag.godeleted−51

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/diag/doc.godeleted−7

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo/doc.godeleted−7

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo/errorinfo.godeleted−46

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/exported/exported.godeleted−129

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/log/doc.godeleted−7

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/log/log.godeleted−104

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/poller/util.godeleted−155

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/temporal/resource.godeleted−123

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/uuid/doc.godeleted−7

This diff is too big to show here.

vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/uuid/uuid.godeleted−76

This diff is too big to show here.

vendor/github.com/JohannesKaufmann/html-to-markdown/.gitignoredeleted−14

This diff is too big to show here.

vendor/github.com/JohannesKaufmann/html-to-markdown/CONTRIBUTING.mddeleted

No changes to the content.

vendor/github.com/JohannesKaufmann/html-to-markdown/LICENSEdeleted−21

This diff is too big to show here.

vendor/github.com/JohannesKaufmann/html-to-markdown/README.mddeleted−242

This diff is too big to show here.

vendor/github.com/JohannesKaufmann/html-to-markdown/SECURITY.mddeleted−6

This diff is too big to show here.

vendor/github.com/JohannesKaufmann/html-to-markdown/commonmark.godeleted−393

This diff is too big to show here.

vendor/github.com/JohannesKaufmann/html-to-markdown/escape/escape.godeleted−65

This diff is too big to show here.

vendor/github.com/JohannesKaufmann/html-to-markdown/from.godeleted−464

This diff is too big to show here.

vendor/github.com/JohannesKaufmann/html-to-markdown/logo_five_years.pngdeleted

Binary file not shown.

vendor/github.com/JohannesKaufmann/html-to-markdown/markdown.godeleted−212

This diff is too big to show here.

vendor/github.com/JohannesKaufmann/html-to-markdown/utils.godeleted−533

This diff is too big to show here.

vendor/github.com/KyleBanks/depth/.gitignoredeleted−16

This diff is too big to show here.

vendor/github.com/KyleBanks/depth/.travis.ymldeleted−9

This diff is too big to show here.

vendor/github.com/KyleBanks/depth/LICENSEdeleted−21

This diff is too big to show here.

vendor/github.com/KyleBanks/depth/Makefiledeleted−32

This diff is too big to show here.

vendor/github.com/KyleBanks/depth/README.mddeleted−232

This diff is too big to show here.

vendor/github.com/KyleBanks/depth/depth.godeleted−129

This diff is too big to show here.

vendor/github.com/KyleBanks/depth/pkg.godeleted−184

This diff is too big to show here.

vendor/github.com/MakeNowJust/heredoc/LICENSEdeleted−21

This diff is too big to show here.

vendor/github.com/MakeNowJust/heredoc/README.mddeleted−52

This diff is too big to show here.

vendor/github.com/MakeNowJust/heredoc/heredoc.godeleted−105

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/.gitattributesdeleted−1

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/.gitignoredeleted−10

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/.golangci.ymldeleted−147

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/CODEOWNERSdeleted−1

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/LICENSEdeleted−22

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/README.mddeleted−89

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/SECURITY.mddeleted−41

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/backup.godeleted−287

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/doc.godeleted−22

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/ea.godeleted−137

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/file.godeleted−320

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/fileinfo.godeleted−106

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/hvsock.godeleted−582

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/internal/fs/doc.godeleted−2

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/internal/fs/fs.godeleted−262

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/internal/fs/security.godeleted−12

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/internal/fs/zsyscall_windows.godeleted−61

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/internal/socket/rawaddr.godeleted−20

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/internal/socket/socket.godeleted−177

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/internal/socket/zsyscall_windows.godeleted−69

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/internal/stringbuffer/wstring.godeleted−132

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/pipe.godeleted−586

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/pkg/guid/guid.godeleted−232

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.godeleted−16

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.godeleted−13

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/pkg/guid/variant_string.godeleted−27

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/privilege.godeleted−196

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/reparse.godeleted−131

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/sd.godeleted−133

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/syscall.godeleted−5

This diff is too big to show here.

vendor/github.com/Microsoft/go-winio/zsyscall_windows.godeleted−378

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/.gitattributesdeleted−1

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/.gitignoredeleted−16

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/LICENSEdeleted−12

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/README.mddeleted−218

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/array.godeleted−124

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/doc.godeleted−123

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/expand.godeleted−70

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/filter.godeleted−163

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/iteration.godeleted−61

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/manipulation.godeleted−679

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/property.godeleted−275

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/query.godeleted−49

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/traversal.godeleted−704

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/type.godeleted−203

This diff is too big to show here.

vendor/github.com/PuerkitoBio/goquery/utilities.godeleted−177

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/.editorconfigdeleted−17

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/.gitignoredeleted−28

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/.golangci.ymldeleted−122

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/.goreleaser.ymldeleted−34

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/AGENTS.mddeleted−11

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/BUILD.bitdeleted−93

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/COPYINGdeleted−118

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/Dockerfiledeleted−64

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/Justfiledeleted−59

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/README.mddeleted−309

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/biome.jsondeleted−6

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/chroma.jpgdeleted

Binary file not shown.

vendor/github.com/alecthomas/chroma/v2/coalesce.godeleted−35

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/colour.godeleted−192

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/delegate.godeleted−161

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/doc.godeleted−7

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/emitters.godeleted−233

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/formatter.godeleted−43

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/formatters/api.godeleted−57

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/formatters/html/html.godeleted−692

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/formatters/json.godeleted−39

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/formatters/svg/font_liberation_mono.godeleted−51

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/formatters/svg/svg.godeleted−222

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/formatters/tokens.godeleted−18

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/formatters/tty_indexed.godeleted−284

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/formatters/tty_truecolour.godeleted−76

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/iterator.godeleted−93

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexer.godeleted−179

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/README.mddeleted−46

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/caddyfile.godeleted−275

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/cl.godeleted−243

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/dns.godeleted−17

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/emacs.godeleted−533

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abap.xmldeleted−154

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abnf.xmldeleted−66

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript.xmldeleted−68

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript_3.xmldeleted−163

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ada.xmldeleted−321

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/agda.xmldeleted−66

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/al.xmldeleted−75

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/alloy.xmldeleted−58

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ampl.xmldeleted−98

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/angular2.xmldeleted−109

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/antlr.xmldeleted−317

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apacheconf.xmldeleted−74

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apl.xmldeleted−59

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/applescript.xmldeleted−151

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arangodb_aql.xmldeleted−174

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arduino.xmldeleted−322

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/armasm.xmldeleted−126

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arturo.xmldeleted−119

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/atl.xmldeleted−165

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/autohotkey.xmldeleted−78

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/autoit.xmldeleted−70

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/awk.xmldeleted−95

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ballerina.xmldeleted−97

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bash.xmldeleted−222

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bash_session.xmldeleted−25

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/batchfile.xmldeleted−660

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/beef.xmldeleted−120

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bibtex.xmldeleted−152

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bicep.xmldeleted−84

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/blitzbasic.xmldeleted−141

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bnf.xmldeleted−28

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bqn.xmldeleted−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/brainfuck.xmldeleted−51

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c#.xmldeleted−121

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c++.xmldeleted−331

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c.xmldeleted−260

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c3.xmldeleted−374

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cap_n_proto.xmldeleted−122

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cassandra_cql.xmldeleted−137

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ceylon.xmldeleted−151

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cfengine3.xmldeleted−197

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cfstatement.xmldeleted−92

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/chaiscript.xmldeleted−134

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/chapel.xmldeleted−143

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cheetah.xmldeleted−55

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/clojure.xmldeleted−71

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cmake.xmldeleted−90

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cobol.xmldeleted−90

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/coffeescript.xmldeleted−210

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/common_lisp.xmldeleted−184

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/coq.xmldeleted−136

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/core.xmldeleted−79

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/crystal.xmldeleted−762

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/css.xmldeleted−323

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/csv.xmldeleted−53

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cue.xmldeleted−85

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cython.xmldeleted−372

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/d.xmldeleted−133

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dart.xmldeleted−213

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dax.xmldeleted−39

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/desktop_entry.xmldeleted−17

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/devicetree.xmldeleted−251

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/diff.xmldeleted−52

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/django_jinja.xmldeleted−153

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dns.xmldeleted−44

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/docker.xmldeleted−68

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dtd.xmldeleted−168

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dylan.xmldeleted−176

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ebnf.xmldeleted−90

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/elixir.xmldeleted−744

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/elm.xmldeleted−119

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/emacslisp.xmldeleted−132

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/erb.xmldeleted−37

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/erlang.xmldeleted−166

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/factor.xmldeleted−412

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fennel.xmldeleted−68

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fish.xmldeleted−159

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/forth.xmldeleted−78

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fortran.xmldeleted−102

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fortranfixed.xmldeleted−71

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fsharp.xmldeleted−245

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gas.xmldeleted−150

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gdscript.xmldeleted−259

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gdscript3.xmldeleted−270

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gemfile_lock.xmldeleted−81

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gettext.xmldeleted−24

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gherkin.xmldeleted−263

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gleam.xmldeleted−100

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/glsl.xmldeleted−65

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gnuplot.xmldeleted−289

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/go_template.xmldeleted−114

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/graphql.xmldeleted−92

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/groff.xmldeleted−90

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/groovy.xmldeleted−135

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/handlebars.xmldeleted−147

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hare.xmldeleted−98

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/haskell.xmldeleted−275

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hcl.xmldeleted−143

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hexdump.xmldeleted−189

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hlb.xmldeleted−131

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hlsl.xmldeleted−110

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/holyc.xmldeleted−252

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/html.xmldeleted−159

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hy.xmldeleted−104

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/idris.xmldeleted−216

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/igor.xmldeleted−47

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ini.xmldeleted−52

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/io.xmldeleted−71

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/iscdhcpd.xmldeleted−96

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/j.xmldeleted−157

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/janet.xmldeleted−48

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/java.xmldeleted−206

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/javascript.xmldeleted−160

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/json.xmldeleted−117

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jsonata.xmldeleted−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jsonnet.xmldeleted−138

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/julia.xmldeleted−400

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jungle.xmldeleted−98

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kakoune.xmldeleted−96

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kdl.xmldeleted−75

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kotlin.xmldeleted−234

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lateralus.xmldeleted−184

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lean.xmldeleted−56

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lighttpd_configuration_file.xmldeleted−42

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lilypond.xmldeleted−133

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/llvm.xmldeleted−73

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lox.xmldeleted−78

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lua.xmldeleted−158

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/luau.xmldeleted−173

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/makefile.xmldeleted−131

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mako.xmldeleted−120

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mason.xmldeleted−89

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/materialize_sql_dialect.xmldeleted−155

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mathematica.xmldeleted−60

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/matlab.xmldeleted−114

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mcfunction.xmldeleted−138

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/meson.xmldeleted−86

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/metal.xmldeleted−270

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/microcad.xmldeleted−139

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/minizinc.xmldeleted−82

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mlir.xmldeleted−73

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/modelica.xmldeleted−106

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/modula-2.xmldeleted−245

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mojo.xmldeleted−228

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/monkeyc.xmldeleted−153

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/moonbit.xmldeleted−75

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/moonscript.xmldeleted−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/morrowindscript.xmldeleted−90

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/myghty.xmldeleted−77

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mysql.xmldeleted−121

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nasm.xmldeleted−126

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/natural.xmldeleted−143

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ndisasm.xmldeleted−123

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/newspeak.xmldeleted−121

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nginx_configuration_file.xmldeleted−101

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nim.xmldeleted−211

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nix.xmldeleted−258

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nsis.xmldeleted−59

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nu.xmldeleted−126

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/objective-c.xmldeleted−510

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/objectpascal.xmldeleted−142

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ocaml.xmldeleted−153

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/octave.xmldeleted−101

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/odin.xmldeleted−127

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/onesenterprise.xmldeleted−92

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/openedge_abl.xmldeleted−101

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/openscad.xmldeleted−96

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/org_mode.xmldeleted−329

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pacmanconf.xmldeleted−37

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/perl.xmldeleted−400

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/php.xmldeleted−258

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pig.xmldeleted−105

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pkgconfig.xmldeleted−73

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pl_pgsql.xmldeleted−119

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/plaintext.xmldeleted−21

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/plutus_core.xmldeleted−105

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pony.xmldeleted−135

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/postgresql_sql_dialect.xmldeleted−155

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/postscript.xmldeleted−93

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/povray.xmldeleted−58

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/powerquery.xmldeleted−51

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/powershell.xmldeleted−230

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/prolog.xmldeleted−115

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/promela.xmldeleted−119

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/promql.xmldeleted−123

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/properties.xmldeleted−45

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/protocol_buffer.xmldeleted−124

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/prql.xmldeleted−161

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/psl.xmldeleted−213

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/puppet.xmldeleted−100

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/python.xmldeleted−595

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/python_2.xmldeleted−356

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/qbasic.xmldeleted−173

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/qml.xmldeleted−113

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/r.xmldeleted−128

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/racket.xmldeleted−260

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ragel.xmldeleted−149

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/react.xmldeleted−236

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/reasonml.xmldeleted−147

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/reg.xmldeleted−68

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rego.xmldeleted−94

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rexx.xmldeleted−127

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rgbasm.xmldeleted−239

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ring.xmldeleted−74

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rpgle.xmldeleted−176

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rpm_spec.xmldeleted−58

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ruby.xmldeleted−728

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rust.xmldeleted−375

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sas.xmldeleted−191

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sass.xmldeleted−362

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scala.xmldeleted−274

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scdoc.xmldeleted−115

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scheme.xmldeleted−106

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scilab.xmldeleted−98

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scss.xmldeleted−373

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sed.xmldeleted−92

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sieve.xmldeleted−61

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smali.xmldeleted−73

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smalltalk.xmldeleted−294

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smarty.xmldeleted−79

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/snbt.xmldeleted−58

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/snobol.xmldeleted−95

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/solidity.xmldeleted−291

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sourcepawn.xmldeleted−59

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/spade.xmldeleted−292

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sparql.xmldeleted−160

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sql.xmldeleted−90

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/squidconf.xmldeleted−63

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/standard_ml.xmldeleted−548

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/stas.xmldeleted−85

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/stylus.xmldeleted−132

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/swift.xmldeleted−207

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/systemd.xmldeleted−63

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/systemverilog.xmldeleted−181

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tablegen.xmldeleted−69

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tal.xml−43

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tasm.xml−135

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tcl.xml−272

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tcsh.xml−121

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/templ.xml−315

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/termcap.xml−75

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/terminfo.xml−84

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/terraform.xml−149

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tex.xml−113

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/thrift.xml−154

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/toml.xml−45

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tradingview.xml−81

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/transact-sql.xml−137

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/turing.xml−82

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/turtle.xml−170

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/twig.xml−155

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/txtpb.xml−162

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typescript.xml−302

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscript.xml−178

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscriptcssdata.xml−52

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscripthtmldata.xml−52

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typst.xml−108

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ucode.xml−147

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/v.xml−355

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/v_shell.xml−365

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vala.xml−72

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vb_net.xml−162

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/verilog.xml−158

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vhdl.xml−171

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vhs.xml−48

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/viml.xml−85

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vue.xml−295

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/wat.xml−149

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/wdte.xml−43

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/webgpu_shading_language.xml−142

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/webvtt.xml−283

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/whiley.xml−57

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/xml.xml−96

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/xorg.xml−35

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/yaml.xml−132

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/yang.xml−99

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/z80_assembly.xml−74

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/zed.xml−51

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/embedded/zig.xml−187

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/erb.go−29

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/gemtext.go−37

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/genshi.go−118

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/go.go−81

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/haxe.go−647

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/html.go−8

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/http.go−131

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/lexers.go−85

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/markdown.go−107

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/markless.go−168

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/mysql.go−33

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/php.go−37

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/raku.go−1710

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/rst.go−89

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/svelte.go−70

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/typoscript.go−85

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/yaml_jinja.go−20

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/lexers/zed.go−24

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/mutators.go−201

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/pygments-lexers.txt−605

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/quick/quick.go−44

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/regexp.go−533

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/registry.go−228

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/remap.go−94

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/renovate.json5−25

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/serialise.go−483

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/style.go−530

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/abap.xml−11

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/algol.xml−18

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/algol_nu.xml−18

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/api.go−95

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/arduino.xml−18

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/ashen.xml−69

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/aura-theme-dark-soft.xml−107

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/aura-theme-dark.xml−107

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/autumn.xml−36

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/average.xml−74

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/base16-snazzy.xml−74

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/borland.xml−26

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/bw.xml−23

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-frappe.xml−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-latte.xml−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-macchiato.xml−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-mocha.xml−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/colorful.xml−52

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/compat.go−69

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/darcula.xml−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/doom-one.xml−51

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/doom-one2.xml−64

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/dracula.xml−74

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/emacs.xml−44

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/evergarden.xml−33

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/friendly.xml−44

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/fruity.xml−19

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/github-dark.xml−46

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/github.xml−39

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/gruvbox-light.xml−34

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/gruvbox.xml−34

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/hr_high_contrast.xml−12

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/hrdark.xml−10

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/igor.xml−9

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/kanagawa-dragon.xml−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/kanagawa-lotus.xml−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/kanagawa-wave.xml−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/lovelace.xml−53

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/manni.xml−44

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/modus-operandi.xml−13

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/modus-vivendi.xml−13

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/monokai.xml−29

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/monokailight.xml−26

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/murphy.xml−52

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/native.xml−35

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/nord.xml−46

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/nordic.xml−46

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/onedark.xml−25

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/onesenterprise.xml−10

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/paraiso-dark.xml−37

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/paraiso-light.xml−37

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/pastie.xml−45

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/perldoc.xml−37

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/pygments.xml−42

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/rainbow_dash.xml−40

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/rose-pine-dawn.xml−29

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/rose-pine-moon.xml−29

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/rose-pine.xml−29

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/rpgle.xml−30

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/rrt.xml−19

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/solarized-dark.xml−39

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/solarized-dark256.xml−41

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/solarized-light.xml−17

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/swapoff.xml−18

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/tango.xml−72

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-day.xml−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-moon.xml−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-night.xml−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-storm.xml−83

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/trac.xml−35

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/vim.xml−29

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/vs.xml−16

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/vulcan.xml−74

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/witchhazel.xml−31

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/xcode-dark.xml−31

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/styles/xcode.xml−22

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/table.py−31

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/test.jsonl−2

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/tokentype_enumer.go−588

This diff is too big to show here.

vendor/github.com/alecthomas/chroma/v2/types.go−357

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/.gitignore−2

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/LICENSE−19

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/README.md−19

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/backward_references.go−185

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/backward_references_hq.go−796

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/bit_cost.go−436

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/bit_reader.go−266

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/bitwriter.go−56

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/block_splitter.go−144

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/block_splitter_command.go−434

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/block_splitter_distance.go−433

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/block_splitter_literal.go−433

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/brotli_bit_stream.go−1435

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/cluster.go−30

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/cluster_command.go−164

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/cluster_distance.go−326

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/cluster_literal.go−326

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/command.go−254

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/compress_fragment.go−834

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/compress_fragment_two_pass.go−775

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/constants.go−77

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/context.go−2176

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/decode.go−2581

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/dictionary.go−122890

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/dictionary_hash.go−32779

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/encode.go−1220

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/encoder.go−177

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/encoder_dict.go−22

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/encoder_fast.go−197

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/entropy_encode.go−592

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/entropy_encode_static.go−4399

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/fast_log.go−290

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/find_match_length.go−45

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/flate/LICENSE−27

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/flate/README.md−2

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/flate/gzip.go−65

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/flate/huffman_bit_writer.go−517

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/flate/huffman_code.go−346

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/flate/token.go−80

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/flate/writer.go−66

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/h10.go−287

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/h5.go−214

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/h6.go−216

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/hash.go−342

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/hash_composite.go−93

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/hash_forgetful_chain.go−252

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/hash_longest_match_quickly.go−214

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/hash_rolling.go−168

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/histogram.go−226

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/http.go−189

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/huffman.go−653

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/literal_cost.go−182

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/bargain1.go−212

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/bargain2.go−248

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/bargain3.go−278

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/emitter.go−34

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/m0.go−175

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/m4.go−339

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/matchfinder.go−103

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/pathfinder.go−328

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/textencoder.go−53

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/trio.go−326

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/zdfast.go−258

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/zfast.go−206

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/matchfinder/zm.go−301

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/memory.go−66

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/metablock.go−574

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/metablock_command.go−165

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/metablock_distance.go−165

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/metablock_literal.go−165

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/params.go−37

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/platform.go−103

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/prefix.go−30

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/prefix_dec.go−723

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/quality.go−196

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/reader.go−111

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/ringbuffer.go−134

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/state.go−294

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/static_dict.go−662

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/static_dict_lut.go−75094

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/symbol_list.go−22

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/transform.go−641

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/utf8_util.go−70

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/util.go−7

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/write_bits.go−52

This diff is too big to show here.

vendor/github.com/andybalholm/brotli/writer.go−162

This diff is too big to show here.

vendor/github.com/andybalholm/cascadia/.travis.yml−14

This diff is too big to show here.

vendor/github.com/andybalholm/cascadia/LICENSE−24

This diff is too big to show here.

vendor/github.com/andybalholm/cascadia/README.md−144

This diff is too big to show here.

vendor/github.com/andybalholm/cascadia/parser.go−889

This diff is too big to show here.

vendor/github.com/andybalholm/cascadia/pseudo_classes.go−458

This diff is too big to show here.

vendor/github.com/andybalholm/cascadia/selector.go−586

This diff is too big to show here.

vendor/github.com/andybalholm/cascadia/serialize.go−176

This diff is too big to show here.

vendor/github.com/andybalholm/cascadia/specificity.go−26

This diff is too big to show here.

vendor/github.com/atotto/clipboard/.travis.yml−22

This diff is too big to show here.

vendor/github.com/atotto/clipboard/LICENSE−27

This diff is too big to show here.

vendor/github.com/atotto/clipboard/README.md−48

This diff is too big to show here.

vendor/github.com/atotto/clipboard/clipboard.go−20

This diff is too big to show here.

vendor/github.com/atotto/clipboard/clipboard_darwin.go−52

This diff is too big to show here.

vendor/github.com/atotto/clipboard/clipboard_plan9.go−42

This diff is too big to show here.

vendor/github.com/atotto/clipboard/clipboard_unix.go−149

This diff is too big to show here.

vendor/github.com/atotto/clipboard/clipboard_windows.go−157

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/NOTICE.txt−3

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/accountid_endpoint_mode.go−18

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/checksum.go−33

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/config.go−254

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/context.go−22

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/credential_cache.go−235

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/credentials.go−234

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/auto.go−38

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/configuration.go−43

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/defaults.go−50

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/doc.go−2

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/defaultsmode.go−95

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/doc.go−62

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/endpoints.go−247

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/errors.go−9

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/from_ptr.go−365

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/logging.go−119

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/logging_generate.go−95

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/metadata.go−213

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/middleware.go−168

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/osname.go−24

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/osname_go115.go−24

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/recursion_detection.go−94

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/request_id.go−27

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/request_id_retriever.go−57

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/user_agent.go−398

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/CHANGELOG.md−203

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/debug.go−144

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/decode.go−218

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/encode.go−167

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/error.go−23

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi/headers.go−24

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi/middleware.go−71

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi/transport.go−13

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi/transport_go117.go−12

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/header.go−175

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/header_value.go−521

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/message.go−99

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/array.go−61

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/encoder.go−80

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/map.go−78

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/middleware.go−62

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/object.go−68

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/value.go−117

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/restjson/decoder_util.go−85

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/xml/error_utils.go−48

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/ratelimit/none.go−20

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/ratelimit/token_bucket.go−96

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/ratelimit/token_rate_limit.go−83

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/request.go−25

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/restrict_file_permissions.go−21

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/adaptive.go−156

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/adaptive_ratelimit.go−158

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/adaptive_token_bucket.go−83

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/attempt_metrics.go−51

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/doc.go−80

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/errors.go−20

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/jitter_backoff.go−124

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/metadata.go−52

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/middleware.go−487

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/retry.go−103

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/retryable_error.go−228

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/standard.go−343

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/throttle_error.go−60

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retry/timeout_error.go−52

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/retryer.go−127

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/runtime.go−14

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/cache.go−115

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/const.go−40

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/header_rules.go−82

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/headers.go−70

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/hmac.go−13

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/host.go−75

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/scope.go−13

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/time.go−36

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/util.go−80

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/middleware.go−420

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/presign_middleware.go−127

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/stream.go−86

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/v4.go−564

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/to_ptr.go−297

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/client.go−355

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/content_type.go−42

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/response_error.go−33

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/response_error_middleware.go−56

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/timeout_read_closer.go−109

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/types.go−42

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/aws/version.go−8

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/CHANGELOG.md−1097

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/auth_scheme_preference.go−19

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/config.go−237

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/defaultsmode.go−47

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/doc.go−20

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/env_config.go−966

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/generate.go−4

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/errors.go−22

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/ini.go−56

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/parse.go−109

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/sections.go−157

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/strings.go−89

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/token.go−32

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/tokenize.go−92

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/internal/ini/value.go−93

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/load_options.go−1371

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/local.go−51

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/provider.go−802

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/resolve.go−458

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/resolve_bearer_token.go−122

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/resolve_credentials.go−650

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/config/shared_config.go−1704

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/CHANGELOG.md−990

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/doc.go−4

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds/doc.go−58

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds/provider.go−241

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client/auth.go−48

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client/client.go−165

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client/endpoints.go−20

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client/middleware.go−164

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/provider.go−207

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/logincreds/dpop.go−150

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/logincreds/file.go−14

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/logincreds/provider.go−184

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/logincreds/token.go−110

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/processcreds/doc.go−92

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/processcreds/provider.go−296

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/doc.go−81

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/sso_cached_token.go−232

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/sso_credentials_provider.go−165

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/sso_token_provider.go−147

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/static_provider.go−63

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/stscreds/assume_role_provider.go−338

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/credentials/stscreds/web_identity_provider.go−181

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/CHANGELOG.md−584

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_client.go−358

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetDynamicData.go−77

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetIAMInfo.go−103

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetInstanceIdentityDocument.go−110

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetMetadata.go−77

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetRegion.go−73

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetToken.go−119

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetUserData.go−61

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/auth.go−48

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/doc.go−12

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/endpoints.go−20

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/internal/config/resolvers.go−114

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/request_middleware.go−313

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/token_provider.go−261

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/auth/auth.go−45

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/auth/scheme.go−191

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/bearer_token_adapter.go−43

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/bearer_token_signer_adapter.go−35

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/credentials_adapter.go−46

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/smithy.go−2

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/v4signer_adapter.go−57

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/v4signer_adapter_eventstream.go−51

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/CHANGELOG.md−544

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/config.go−65

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/endpoints.go−57

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/context/context.go−65

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/arn.go−94

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/doc.go−3

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/generate.go−7

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/host.go−51

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/partition.go−76

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/partitions.go−496

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/partitions.json−267

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/endpoints.go−201

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/CHANGELOG.md−519

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/endpoints.go−302

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/rand/rand.go−33

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/sdk/interfaces.go−9

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/sdk/time.go−74

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/sdkio/byte.go−12

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/shareddefaults/shared_config.go−47

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/strings/strings.go−11

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight/LICENSE−28

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight/docs.go−7

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight/singleflight.go−210

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/timeconv/duration.go−13

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/CHANGELOG.md−486

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/credentials.go−141

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/error.go−17

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/crypto/compare.go−30

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/crypto/ecc.go−113

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/const.go−36

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/header_rules.go−82

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/headers.go−68

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/hmac.go−13

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/host.go−75

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/time.go−36

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4/util.go−64

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/middleware.go−118

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/presign_middleware.go−117

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/smithy.go−92

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/internal/v4a/v4a.go−520

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/CHANGELOG.md−220

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/accept_encoding_gzip.go−176

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/doc.go−22

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/CHANGELOG.md−571

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/context.go−56

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/doc.go−3

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/middleware.go−110

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/CHANGELOG.md−85

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_client.go−945

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_CreateOAuth2Token.go−212

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_DeleteConsoleAuthorizationConfiguration.go−168

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_DeleteResourcePermissionStatement.go−196

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_GetConsoleAuthorizationConfiguration.go−168

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_GetResourcePolicy.go−155

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_ListResourcePermissionStatements.go−261

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_PutConsoleAuthorizationConfiguration.go−168

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/api_op_PutResourcePermissionStatement.go−216

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/auth.go−361

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/deserializers.go−2474

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/doc.go−9

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/endpoints.go−854

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/generated.json−40

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/internal/endpoints/endpoints.go−333

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/options.go−242

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/serializers.go−724

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/types/enums.go−46

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/types/errors.go−247

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/types/types.go−162

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/signin/validators.go−111

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/CHANGELOG.md−792

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_client.go−929

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_op_GetRoleCredentials.go−165

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_op_ListAccountRoles.go−263

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_op_ListAccounts.go−261

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_op_Logout.go−164

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/auth.go−379

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/deserializers.go−1171

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/doc.go−27

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/endpoints.go−570

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/generated.json−35

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/internal/endpoints/endpoints.go−628

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/options.go−238

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/serializers.go−309

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/types/errors.go−115

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/types/types.go−63

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sso/validators.go−175

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/CHANGELOG.md−790

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_client.go−929

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_op_CreateToken.go−235

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_op_CreateTokenWithIAM.go−282

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_op_RegisterClient.go−206

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_op_StartDeviceAuthorization.go−188

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/auth.go−373

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/deserializers.go−2244

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/doc.go−49

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/endpoints.go−570

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/generated.json−36

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/internal/endpoints/endpoints.go−628

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/options.go−238

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/serializers.go−512

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/types/enums.go−44

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/types/errors.go−430

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/types/types.go−25

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/validators.go−184

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/CHANGELOG.md−832

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/LICENSE.txt−202

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_client.go−1115

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_AssumeRole.go−544

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_AssumeRoleWithSAML.go−455

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_AssumeRoleWithWebIdentity.go−472

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_AssumeRoot.go−229

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_DecodeAuthorizationMessage.go−189

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetAccessKeyInfo.go−180

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetCallerIdentity.go−192

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetDelegatedAccessToken.go−169

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetFederationToken.go−393

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetSessionToken.go−239

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetWebIdentityToken.go−192

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/auth.go−377

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/deserializers.go−3456

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/doc.go−13

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/endpoints.go−683

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/generated.json−45

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/internal/endpoints/endpoints.go−585

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/options.go−286

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/serializers.go−1198

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/types/errors.go−363

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/types/types.go−144

This diff is too big to show here.

vendor/github.com/aws/aws-sdk-go-v2/service/sts/validators.go−433

This diff is too big to show here.

vendor/github.com/aws/smithy-go/.gitignore−29

This diff is too big to show here.

vendor/github.com/aws/smithy-go/.travis.yml−28

This diff is too big to show here.

vendor/github.com/aws/smithy-go/AGENTS.md−172

This diff is too big to show here.

vendor/github.com/aws/smithy-go/CHANGELOG.md−454

This diff is too big to show here.

vendor/github.com/aws/smithy-go/CODE_OF_CONDUCT.md−4

This diff is too big to show here.

vendor/github.com/aws/smithy-go/CONTRIBUTING.md−90

This diff is too big to show here.

vendor/github.com/aws/smithy-go/LICENSE−175

This diff is too big to show here.

vendor/github.com/aws/smithy-go/Makefile−130

This diff is too big to show here.

vendor/github.com/aws/smithy-go/NOTICE−1

This diff is too big to show here.

vendor/github.com/aws/smithy-go/README.md−107

This diff is too big to show here.

vendor/github.com/aws/smithy-go/auth/auth.go−3

This diff is too big to show here.

vendor/github.com/aws/smithy-go/auth/bearer/docs.go−3

This diff is too big to show here.

vendor/github.com/aws/smithy-go/auth/bearer/middleware.go−104

This diff is too big to show here.

vendor/github.com/aws/smithy-go/auth/bearer/token.go−50

This diff is too big to show here.

vendor/github.com/aws/smithy-go/auth/bearer/token_cache.go−208

This diff is too big to show here.

vendor/github.com/aws/smithy-go/auth/identity.go−47

This diff is too big to show here.

vendor/github.com/aws/smithy-go/auth/option.go−25

This diff is too big to show here.

vendor/github.com/aws/smithy-go/auth/scheme_id.go−20

This diff is too big to show here.

vendor/github.com/aws/smithy-go/changelog-template.json−9

This diff is too big to show here.

vendor/github.com/aws/smithy-go/context/suppress_expired.go−81

This diff is too big to show here.

vendor/github.com/aws/smithy-go/doc.go−2

This diff is too big to show here.

vendor/github.com/aws/smithy-go/document.go−10

This diff is too big to show here.

vendor/github.com/aws/smithy-go/document/doc.go−12

This diff is too big to show here.

vendor/github.com/aws/smithy-go/document/document.go−239

This diff is too big to show here.

vendor/github.com/aws/smithy-go/document/errors.go−75

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/doc.go−4

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/encoding.go−40

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/httpbinding/encode.go−123

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/httpbinding/header.go−122

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/httpbinding/path_replace.go−108

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/httpbinding/query.go−107

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/httpbinding/uri.go−111

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/json/array.go−35

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/json/constants.go−15

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/json/decoder_util.go−139

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/json/encoder.go−30

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/json/escape.go−198

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/json/object.go−40

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/json/value.go−154

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/xml/array.go−49

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/xml/constants.go−10

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/xml/doc.go−49

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/xml/element.go−91

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/xml/encoder.go−51

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/xml/error_utils.go−51

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/xml/escape.go−137

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/xml/map.go−53

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/xml/value.go−302

This diff is too big to show here.

vendor/github.com/aws/smithy-go/encoding/xml/xml_decoder.go−154

This diff is too big to show here.

vendor/github.com/aws/smithy-go/endpoints/endpoint.go−23

This diff is too big to show here.

vendor/github.com/aws/smithy-go/endpoints/private/bdd/evaluate.go−35

This diff is too big to show here.

vendor/github.com/aws/smithy-go/endpoints/private/rulesfn/doc.go−4

This diff is too big to show here.

vendor/github.com/aws/smithy-go/endpoints/private/rulesfn/split.go−16

This diff is too big to show here.

vendor/github.com/aws/smithy-go/endpoints/private/rulesfn/string_slice.go−18

This diff is too big to show here.

vendor/github.com/aws/smithy-go/endpoints/private/rulesfn/strings.go−25

This diff is too big to show here.

vendor/github.com/aws/smithy-go/endpoints/private/rulesfn/uri.go−133

This diff is too big to show here.

vendor/github.com/aws/smithy-go/errors.go−137

This diff is too big to show here.

vendor/github.com/aws/smithy-go/eventstream/const.go−24

This diff is too big to show here.

vendor/github.com/aws/smithy-go/eventstream/debug.go−144

This diff is too big to show here.

vendor/github.com/aws/smithy-go/eventstream/decode.go−218

This diff is too big to show here.

vendor/github.com/aws/smithy-go/eventstream/deserializer.go−294

This diff is too big to show here.

vendor/github.com/aws/smithy-go/eventstream/encode.go−167

This diff is too big to show here.

vendor/github.com/aws/smithy-go/eventstream/error.go−23

This diff is too big to show here.

vendor/github.com/aws/smithy-go/eventstream/header.go−175

This diff is too big to show here.

vendor/github.com/aws/smithy-go/eventstream/header_value.go−521

This diff is too big to show here.

vendor/github.com/aws/smithy-go/eventstream/message.go−99

This diff is too big to show here.

vendor/github.com/aws/smithy-go/eventstream/serializer.go−228

This diff is too big to show here.

vendor/github.com/aws/smithy-go/eventstream/signer.go−82

This diff is too big to show here.

vendor/github.com/aws/smithy-go/eventstream/types.go−26

This diff is too big to show here.

vendor/github.com/aws/smithy-go/go_module_metadata.go−6

This diff is too big to show here.

vendor/github.com/aws/smithy-go/internal/sync/singleflight/LICENSE−28

This diff is too big to show here.

vendor/github.com/aws/smithy-go/internal/sync/singleflight/docs.go−8

This diff is too big to show here.

vendor/github.com/aws/smithy-go/internal/sync/singleflight/singleflight.go−210

This diff is too big to show here.

vendor/github.com/aws/smithy-go/io/byte.go−12

This diff is too big to show here.

vendor/github.com/aws/smithy-go/io/doc.go−2

This diff is too big to show here.

vendor/github.com/aws/smithy-go/io/reader.go−16

This diff is too big to show here.

vendor/github.com/aws/smithy-go/io/ringbuffer.go−94

This diff is too big to show here.

vendor/github.com/aws/smithy-go/local-mod-replace.sh−39

This diff is too big to show here.

vendor/github.com/aws/smithy-go/logging/logger.go−82

This diff is too big to show here.

vendor/github.com/aws/smithy-go/metrics/metrics.go−136

This diff is too big to show here.

vendor/github.com/aws/smithy-go/metrics/nop.go−98

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/context.go−41

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/doc.go−67

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/eventstream_middleware.go−21

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/logging.go−46

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/metadata.go−65

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/middleware.go−71

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/ordered_group.go−270

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/stack.go−209

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/stack_values.go−100

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/step_build.go−298

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/step_deserialize.go−300

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/step_finalize.go−298

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/step_initialize.go−299

This diff is too big to show here.

vendor/github.com/aws/smithy-go/middleware/step_serialize.go−305

This diff is too big to show here.

vendor/github.com/aws/smithy-go/modman.toml−9

This diff is too big to show here.

vendor/github.com/aws/smithy-go/private/requestcompression/gzip.go−30

This diff is too big to show here.

vendor/github.com/aws/smithy-go/private/requestcompression/middleware_capture_request_compression.go−52

This diff is too big to show here.

vendor/github.com/aws/smithy-go/private/requestcompression/request_compression.go−103

This diff is too big to show here.

vendor/github.com/aws/smithy-go/properties.go−69

This diff is too big to show here.

vendor/github.com/aws/smithy-go/ptr/doc.go−5

This diff is too big to show here.

vendor/github.com/aws/smithy-go/ptr/from_ptr.go−601

This diff is too big to show here.

vendor/github.com/aws/smithy-go/ptr/gen_scalars.go−83

This diff is too big to show here.

vendor/github.com/aws/smithy-go/ptr/to_ptr.go−499

This diff is too big to show here.

vendor/github.com/aws/smithy-go/rand/doc.go−3

This diff is too big to show here.

vendor/github.com/aws/smithy-go/rand/rand.go−31

This diff is too big to show here.

vendor/github.com/aws/smithy-go/rand/uuid.go−87

This diff is too big to show here.

vendor/github.com/aws/smithy-go/schema.go−328

This diff is too big to show here.

vendor/github.com/aws/smithy-go/schema_ext.go−37

This diff is too big to show here.

vendor/github.com/aws/smithy-go/serde.go−229

This diff is too big to show here.

vendor/github.com/aws/smithy-go/sync/error.go−53

This diff is too big to show here.

vendor/github.com/aws/smithy-go/time/time.go−134

This diff is too big to show here.

vendor/github.com/aws/smithy-go/tracing/context.go−96

This diff is too big to show here.

vendor/github.com/aws/smithy-go/tracing/nop.go−32

This diff is too big to show here.

vendor/github.com/aws/smithy-go/tracing/tracing.go−95

This diff is too big to show here.

vendor/github.com/aws/smithy-go/trait.go−21

This diff is too big to show here.

vendor/github.com/aws/smithy-go/traits/http.go−69

This diff is too big to show here.

vendor/github.com/aws/smithy-go/traits/index.go−107

This diff is too big to show here.

vendor/github.com/aws/smithy-go/traits/serde.go−56

This diff is too big to show here.

vendor/github.com/aws/smithy-go/traits/traits.go−72

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/auth.go−30

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/auth_schemes.go−45

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/checksum_middleware.go−70

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/client.go−161

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/doc.go−5

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/eventstream.go−209

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/eventstream_middleware.go−69

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/headerlist.go−163

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/host.go−89

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/interceptor.go−321

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/interceptor_middleware.go−325

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/internal/io/safe.go−75

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/md5_checksum.go−25

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/metrics.go−204

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/middleware_close_response_body.go−79

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/middleware_content_length.go−84

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/middleware_header_comment.go−81

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/middleware_headers.go−167

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/middleware_http_logging.go−75

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/middleware_metadata.go−51

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/middleware_min_proto.go−79

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/properties.go−80

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/protocol.go−27

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/request.go−188

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/response.go−34

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/time.go−13

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/url.go−44

This diff is too big to show here.

vendor/github.com/aws/smithy-go/transport/http/user_agent.go−37

This diff is too big to show here.

vendor/github.com/aws/smithy-go/type_registry.go−70

This diff is too big to show here.

vendor/github.com/aws/smithy-go/validation.go−140

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/LICENSE-BSD−27

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/LICENSE-MIT−21

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/Makefile−4

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/README.md−142

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/diff.go−177

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/export.go−16

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/lcs/common.go−179

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/lcs/doc.go−156

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/lcs/export.go−11

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/lcs/git.sh−33

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/lcs/labels.go−55

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/lcs/old.go−475

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/lcs/sequence.go−70

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/merge.go−81

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/ndiff.go−118

This diff is too big to show here.

vendor/github.com/aymanbagabas/go-udiff/unified.go−314

This diff is too big to show here.

vendor/github.com/aymerick/douceur/LICENSE−22

This diff is too big to show here.

vendor/github.com/aymerick/douceur/css/declaration.go−60

This diff is too big to show here.

vendor/github.com/aymerick/douceur/css/rule.go−230

This diff is too big to show here.

vendor/github.com/aymerick/douceur/css/stylesheet.go−25

This diff is too big to show here.

vendor/github.com/aymerick/douceur/parser/parser.go−409

This diff is too big to show here.

vendor/github.com/bahlo/generic-list-go/LICENSE−27

This diff is too big to show here.

vendor/github.com/bahlo/generic-list-go/README.md−5

This diff is too big to show here.

vendor/github.com/bahlo/generic-list-go/list.go−235

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/.codecov.yml−10

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/.gitignore−32

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/LICENSE−22

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/README.md−495

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/UPGRADING.md−63

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/doublestar.go−13

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/glob.go−497

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/globoptions.go−188

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/globwalk.go−438

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/match.go−419

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/utils.go−165

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/utils_unix.go−19

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/utils_windows.go−43

This diff is too big to show here.

vendor/github.com/bmatcuk/doublestar/v4/validate.go−80

This diff is too big to show here.

vendor/github.com/buger/jsonparser/.gitignore−12

This diff is too big to show here.

vendor/github.com/buger/jsonparser/.travis.yml−12

This diff is too big to show here.

vendor/github.com/buger/jsonparser/Dockerfile−12

This diff is too big to show here.

vendor/github.com/buger/jsonparser/LICENSE−21

This diff is too big to show here.

vendor/github.com/buger/jsonparser/Makefile−36

This diff is too big to show here.

vendor/github.com/buger/jsonparser/README.md−361

This diff is too big to show here.

vendor/github.com/buger/jsonparser/bytes.go−47

This diff is too big to show here.

vendor/github.com/buger/jsonparser/bytes_safe.go−25

This diff is too big to show here.

vendor/github.com/buger/jsonparser/bytes_unsafe.go−44

This diff is too big to show here.

vendor/github.com/buger/jsonparser/escape.go−173

This diff is too big to show here.

vendor/github.com/buger/jsonparser/fuzz.go−117

This diff is too big to show here.

vendor/github.com/buger/jsonparser/oss-fuzz-build.sh−47

This diff is too big to show here.

vendor/github.com/buger/jsonparser/parser.go−1318

This diff is too big to show here.

vendor/github.com/cespare/xxhash/v2/LICENSE.txt−22

This diff is too big to show here.

vendor/github.com/cespare/xxhash/v2/README.md−74

This diff is too big to show here.

vendor/github.com/cespare/xxhash/v2/testall.sh−10

This diff is too big to show here.

vendor/github.com/cespare/xxhash/v2/xxhash.go−243

This diff is too big to show here.

vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s−209

This diff is too big to show here.

vendor/github.com/cespare/xxhash/v2/xxhash_arm64.s−183

This diff is too big to show here.

vendor/github.com/cespare/xxhash/v2/xxhash_asm.go−15

This diff is too big to show here.

vendor/github.com/cespare/xxhash/v2/xxhash_other.go−76

This diff is too big to show here.

vendor/github.com/cespare/xxhash/v2/xxhash_safe.go−16

This diff is too big to show here.

vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go−58

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/.gitignore−2

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/LICENSE−21

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/Makefile−101

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/README.md−193

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/adapters.go−102

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/dirent.go−73

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/dirent_portable.go−134

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/dirent_unix.go−146

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/entry_filter_portable.go−37

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/entry_filter_unix.go−59

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/entry_filter_windows.go−156

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/fastwalk.go−697

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/fastwalk_darwin.go−122

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/fastwalk_portable.go−65

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/fastwalk_unix.go−131

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/go.LICENSE−27

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent.go−85

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_aix.go−29

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_dragonfly.go−54

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_freebsd.go−46

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_js.go−27

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_linux.go−50

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_netbsd.go−46

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_openbsd.go−46

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_solaris.go−29

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/internal/dirent/doc.go−2

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/internal/fmtdirent/fmtdirent_go120.go−31

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/internal/fmtdirent/fmtdirent_go121.go−15

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/zsyscall_darwin.go−93

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/zsyscall_darwin_amd64.1_13.s−22

This diff is too big to show here.

vendor/github.com/charlievieth/fastwalk/zsyscall_darwin_arm64.1_13.s−22

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/.gitignore−4

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/.release-please-manifest.json−3

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/.stats.yml−4

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/Brewfile−1

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/CHANGELOG.md−641

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/CONTRIBUTING.md−65

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/LICENSE−8

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/README.md−999

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/SECURITY.md−27

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/aliases.go−72

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/api.md−545

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/bedrock/bedrock.go−295

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/beta.go−374

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/betafile.go−273

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/betamessage.go−11734

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/betamessagebatch.go−925

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/betamessageutil.go−459

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/betamodel.go−151

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/betaskill.go−362

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/betaskillversion.go−373

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/betatoolrunner.go−435

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/client.go−157

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/completion.go−196

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/field.go−46

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apierror/apierror.go−58

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiform/encoder.go−473

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiform/form.go−5

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiform/richparam.go−21

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiform/tag.go−51

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/decoder.go−709

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/encoder.go−392

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/enum.go−145

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/field.go−23

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/port.go−120

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/registry.go−59

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/subfield.go−68

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/tag.go−47

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apijson/union.go−209

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiquery/encoder.go−414

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiquery/query.go−55

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiquery/richparam.go−20

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/apiquery/tag.go−44

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/decode.go−1325

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/encode.go−1396

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/fold.go−48

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/indent.go−182

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/scanner.go−610

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/sentinel/null.go−47

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/shims/shims.go−113

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/stream.go−512

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/tables.go−218

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/tags.go−38

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/encoding/json/time.go−62

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/paramutil/field.go−30

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/paramutil/union.go−49

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/requestconfig/requestconfig.go−638

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/internal/version.go−5

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/message.go−9436

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/messagebatch.go−827

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/messageutil.go−413

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/model.go−151

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/option/middleware.go−38

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/option/requestoption.go−285

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/packages/jsonl/jsonl.go−62

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/packages/pagination/pagination.go−351

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/packages/param/encoder.go−109

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/packages/param/null.go−19

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/packages/param/option.go−122

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/packages/param/param.go−189

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/packages/respjson/respjson.go−88

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/packages/ssestream/ssestream.go−198

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/release-please-config.json−68

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/schemautil.go−244

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/shared/constant/constants.go−487

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/shared/shared.go−346

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/tools.md−292

This diff is too big to show here.

vendor/github.com/charmbracelet/anthropic-sdk-go/vertex/vertex.go−134

This diff is too big to show here.

vendor/github.com/charmbracelet/colorprofile/.golangci.yml−47

This diff is too big to show here.

vendor/github.com/charmbracelet/colorprofile/.goreleaser.yml−6

This diff is too big to show here.

vendor/github.com/charmbracelet/colorprofile/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/colorprofile/README.md−109

This diff is too big to show here.

vendor/github.com/charmbracelet/colorprofile/doc.go−4

This diff is too big to show here.

vendor/github.com/charmbracelet/colorprofile/env.go−294

This diff is too big to show here.

vendor/github.com/charmbracelet/colorprofile/env_other.go−8

This diff is too big to show here.

vendor/github.com/charmbracelet/colorprofile/env_windows.go−40

This diff is too big to show here.

vendor/github.com/charmbracelet/colorprofile/profile.go−108

This diff is too big to show here.

vendor/github.com/charmbracelet/colorprofile/writer.go−171

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/.gitignore−4

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/.release-please-manifest.json−3

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/.stats.yml−4

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/Brewfile−1

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/CHANGELOG.md−1171

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/CONTRIBUTING.md−65

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/LICENSE−201

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/MIGRATION.md−284

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/README.md−1007

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/SECURITY.md−29

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/aliases.go−585

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/api.md−774

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/audio.go−61

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/audiospeech.go−179

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/audiotranscription.go−950

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/audiotranslation.go−120

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/azure/azure.go−269

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/batch.go−464

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/beta.go−36

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/betaassistant.go−2284

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/betachatkit.go−125

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/betachatkitsession.go−83

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/betachatkitthread.go−1564

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/betathread.go−1574

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/betathreadmessage.go−1717

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/betathreadrun.go−974

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/betathreadrunstep.go−1396

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/chat.go−30

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/chatcompletion.go−3521

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/chatcompletionmessage.go−100

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/client.go−185

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/completion.go−436

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/container.go−648

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/containerfile.go−287

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/containerfilecontent.go−50

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/conversations/aliases.go−585

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/conversations/api.md−31

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/conversations/conversation.go−493

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/conversations/item.go−1082

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/embedding.go−207

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/field.go−45

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/file.go−352

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/finetuning.go−35

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/finetuningalpha.go−29

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/finetuningalphagrader.go−675

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/finetuningcheckpoint.go−29

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/finetuningcheckpointpermission.go−352

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/finetuningjob.go−884

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/finetuningjobcheckpoint.go−152

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/finetuningmethod.go−1487

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/grader.go−28

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/gradergradermodel.go−1753

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/image.go−1337

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apierror/apierror.go−58

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apiform/encoder.go−473

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apiform/form.go−5

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apiform/richparam.go−20

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apiform/tag.go−72

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apijson/decoder.go−688

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apijson/encoder.go−371

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apijson/enum.go−140

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apijson/field.go−23

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apijson/port.go−120

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apijson/registry.go−51

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apijson/subfield.go−67

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apijson/tag.go−69

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apijson/union.go−208

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apiquery/encoder.go−394

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apiquery/query.go−55

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apiquery/richparam.go−20

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/apiquery/tag.go−44

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/encoding/json/decode.go−1324

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/encoding/json/encode.go−1396

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/encoding/json/fold.go−48

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/encoding/json/indent.go−182

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/encoding/json/scanner.go−610

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/encoding/json/sentinel/null.go−46

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/encoding/json/shims/shims.go−113

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/encoding/json/stream.go−512

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/encoding/json/tables.go−218

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/encoding/json/tags.go−38

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/encoding/json/time.go−61

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/paramutil/field.go−30

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/paramutil/union.go−48

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/requestconfig/requestconfig.go−644

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/internal/version.go−5

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/model.go−137

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/moderation.go−489

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/option/middleware.go−38

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/option/requestoption.go−299

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/packages/pagination/pagination.go−329

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/packages/param/encoder.go−109

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/packages/param/null.go−19

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/packages/param/option.go−122

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/packages/param/param.go−186

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/packages/respjson/respjson.go−88

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/packages/ssestream/ssestream.go−247

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/polling.go−129

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/realtime/aliases.go−585

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/realtime/api.md−55

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/realtime/call.go−133

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/realtime/clientsecret.go−1826

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/realtime/realtime.go−1979

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/release-please-config.json−68

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/responses/aliases.go−585

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/responses/api.md−240

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/responses/inputitem.go−135

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/responses/inputtoken.go−336

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/responses/response.go−21786

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/shared/constant/constants.go−1143

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/shared/shared.go−1183

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/skill.go−292

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/skillcontent.go−46

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/skillversion.go−283

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/skillversioncontent.go−50

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/streamaccumulator.go−215

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/upload.go−238

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/uploadpart.go−115

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/vectorstore.go−911

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/vectorstorefile.go−535

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/vectorstorefilebatch.go−427

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/video.go−677

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/webhooks/aliases.go−585

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/webhooks/api.md−20

This diff is too big to show here.

vendor/github.com/charmbracelet/openai-go/webhooks/webhook.go−1306

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/.gitattributes−1

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/.golangci.yml−48

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/.goreleaser.yml−5

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/README.md−161

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/TUTORIAL.md−175

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/border.go−231

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/buffer.go−831

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/cancelreader_other.go−16

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/cancelreader_windows.go−139

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/cell.go−465

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/console.go−219

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/console_unix.go−13

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/console_windows.go−13

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/cursor.go−22

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/decoder.go−2102

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/doc.go−46

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/environ.go−32

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/event.go−567

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/internal/casso/math.go−172

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/internal/casso/solver.go−275

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/internal/lru/lru.go−69

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/key.go−762

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/key_table.go−664

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/layout/cache.go−29

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/layout/constraint.go−174

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/layout/flex.go−210

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/layout/layout.go−973

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/layout/padding.go−66

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/logger.go−6

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/mouse.go−150

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/poll.go−38

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/poll_bsd.go−165

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/poll_default.go−12

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/poll_fallback.go−158

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/poll_linux.go−175

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/poll_select.go−156

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/poll_solaris.go−12

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/poll_windows.go−217

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/screen/context.go−379

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/screen/screen.go−117

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/styled.go−337

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/tabstop.go−142

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal.go−394

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_bsdly.go−10

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_bsdly_other.go−8

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_other.go−22

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go−503

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_reader_other.go−13

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go−336

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_renderer.go−1639

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hardscroll.go−243

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hashmap.go−298

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_screen.go−837

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_tabdly.go−10

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_tabdly_other.go−8

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_unix.go−56

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/terminal_windows.go−77

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/tty.go−42

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/tty_other.go−26

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/tty_unix.go−39

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/tty_windows.go−39

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/utils.go−15

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/uv.go−406

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/winch.go−62

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/winch_other.go−17

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/winch_unix.go−50

This diff is too big to show here.

vendor/github.com/charmbracelet/ultraviolet/window.go−147

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/ansi.go−11

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/ascii.go−8

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/background.go−178

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/c0.go−79

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/c1.go−72

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/charset.go−55

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/clipboard.go−75

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/color.go−784

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/ctrl.go−156

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/cursor.go−637

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/cwd.go−26

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/doc.go−7

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/finalterm.go−67

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/focus.go−9

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/graphics.go−62

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/hyperlink.go−28

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/inband.go−24

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/iterm2.go−18

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/keypad.go−28

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/kitty.go−90

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/kitty/decoder.go−86

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/kitty/encoder.go−64

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/kitty/graphics.go−414

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/kitty/options.go−373

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/kitty/writer.go−187

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/method.go−196

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/mode.go−690

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/mode_deprecated.go−495

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/modes.go−65

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/mouse.go−172

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/notification.go−32

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/palette.go−34

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/parser.go−417

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/parser/const.go−79

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/parser/seq.go−136

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/parser/transition_table.go−273

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/parser_decode.go−525

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/parser_handler.go−60

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/parser_sync.go−29

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/passthrough.go−60

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/paste.go−7

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/progress.go−49

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/reset.go−11

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/screen.go−410

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/sgr.go−79

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/status.go−168

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/style.go−819

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/termcap.go−41

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/title.go−48

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/truncate.go−290

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/urxvt.go−17

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/util.go−92

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/width.go−107

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/winop.go−59

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/wrap.go−472

This diff is too big to show here.

vendor/github.com/charmbracelet/x/ansi/xterm.go−138

This diff is too big to show here.

vendor/github.com/charmbracelet/x/editor/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/x/editor/editor.go−140

This diff is too big to show here.

vendor/github.com/charmbracelet/x/etag/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/x/etag/etag.go−48

This diff is too big to show here.

vendor/github.com/charmbracelet/x/exp/charmtone/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/x/exp/charmtone/charmtone.go−443

This diff is too big to show here.

vendor/github.com/charmbracelet/x/exp/golden/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/x/exp/golden/golden.go−83

This diff is too big to show here.

vendor/github.com/charmbracelet/x/exp/ordered/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/x/exp/ordered/ordered.go−42

This diff is too big to show here.

vendor/github.com/charmbracelet/x/exp/slice/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/x/exp/slice/slice.go−148

This diff is too big to show here.

vendor/github.com/charmbracelet/x/exp/strings/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/x/exp/strings/contains.go−16

This diff is too big to show here.

vendor/github.com/charmbracelet/x/exp/strings/join.go−134

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/config/config.go−118

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/config/lsps.json−367

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/config/overrides.go−85

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/client.go−875

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/language.go−158

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol.go−50

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/LICENSE−27

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/interface.go−132

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/pattern_interfaces.go−79

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/tables.go−31

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/tsdocument-changes.go−83

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/tsjson.go−3072

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/tsprotocol.go−6963

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/protocol/uri.go−192

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/sync.go−240

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/lsp/types.go−93

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/transport/connection.go−130

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/transport/router.go−107

This diff is too big to show here.

vendor/github.com/charmbracelet/x/powernap/pkg/transport/transport.go−214

This diff is too big to show here.

vendor/github.com/charmbracelet/x/term/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/x/term/term.go−51

This diff is too big to show here.

vendor/github.com/charmbracelet/x/term/term_other.go−39

This diff is too big to show here.

vendor/github.com/charmbracelet/x/term/term_plan9.go−118

This diff is too big to show here.

vendor/github.com/charmbracelet/x/term/term_unix.go−96

This diff is too big to show here.

vendor/github.com/charmbracelet/x/term/term_unix_bsd.go−11

This diff is too big to show here.

vendor/github.com/charmbracelet/x/term/term_unix_other.go−11

This diff is too big to show here.

vendor/github.com/charmbracelet/x/term/term_windows.go−87

This diff is too big to show here.

vendor/github.com/charmbracelet/x/term/terminal.go−12

This diff is too big to show here.

vendor/github.com/charmbracelet/x/term/util.go−47

This diff is too big to show here.

vendor/github.com/charmbracelet/x/termios/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/x/termios/bit_bsd.go−7

This diff is too big to show here.

vendor/github.com/charmbracelet/x/termios/bit_darwin.go−7

This diff is too big to show here.

vendor/github.com/charmbracelet/x/termios/bit_other.go−7

This diff is too big to show here.

vendor/github.com/charmbracelet/x/termios/syscalls_bsd.go−11

This diff is too big to show here.

vendor/github.com/charmbracelet/x/termios/syscalls_darwin.go−10

This diff is too big to show here.

vendor/github.com/charmbracelet/x/termios/syscalls_linux.go−14

This diff is too big to show here.

vendor/github.com/charmbracelet/x/termios/termios.go−249

This diff is too big to show here.

vendor/github.com/charmbracelet/x/termios/termios_bsd.go−13

This diff is too big to show here.

vendor/github.com/charmbracelet/x/termios/termios_linux.go−13

This diff is too big to show here.

vendor/github.com/charmbracelet/x/termios/termios_other.go−15

This diff is too big to show here.

vendor/github.com/charmbracelet/x/termios/termios_solaris.go−26

This diff is too big to show here.

vendor/github.com/charmbracelet/x/windows/LICENSE−21

This diff is too big to show here.

vendor/github.com/charmbracelet/x/windows/doc.go−4

This diff is too big to show here.

vendor/github.com/charmbracelet/x/windows/syscall_windows.go−14

This diff is too big to show here.

vendor/github.com/charmbracelet/x/windows/types.go−221

This diff is too big to show here.

vendor/github.com/charmbracelet/x/windows/types_windows.go−142

This diff is too big to show here.

vendor/github.com/charmbracelet/x/windows/zsyscall_windows.go−77

This diff is too big to show here.

vendor/github.com/clipperhouse/displaywidth/.gitignore−3

This diff is too big to show here.

vendor/github.com/clipperhouse/displaywidth/AGENTS.md−51

This diff is too big to show here.

vendor/github.com/clipperhouse/displaywidth/CHANGELOG.md−129

This diff is too big to show here.

vendor/github.com/clipperhouse/displaywidth/LICENSE−21

This diff is too big to show here.

vendor/github.com/clipperhouse/displaywidth/README.md−190

This diff is too big to show here.

vendor/github.com/clipperhouse/displaywidth/gen.go−3

This diff is too big to show here.

vendor/github.com/clipperhouse/displaywidth/graphemes.go−73

This diff is too big to show here.

vendor/github.com/clipperhouse/displaywidth/options.go−30

This diff is too big to show here.

vendor/github.com/clipperhouse/displaywidth/trie.go−1699

This diff is too big to show here.

vendor/github.com/clipperhouse/displaywidth/truncate.go−149

This diff is too big to show here.

vendor/github.com/clipperhouse/displaywidth/width.go−239

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/LICENSE−21

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md−120

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/graphemes/ansi.go−138

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/graphemes/ansi8.go−79

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go−144

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go−25

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go−205

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go−1717

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/words/README.md−121

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/words/bleve_compat.go−104

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/words/iterator.go−126

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/words/joiners.go−41

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/words/reader.go−34

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/words/seek.go−43

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/words/splitfunc.go−269

This diff is too big to show here.

vendor/github.com/clipperhouse/uax29/v2/words/trie.go−3774

This diff is too big to show here.

vendor/github.com/cyphar/filepath-securejoin/.golangci.yml−60

This diff is too big to show here.

vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md−453

This diff is too big to show here.

vendor/github.com/cyphar/filepath-securejoin/COPYING.md−447

This diff is too big to show here.

vendor/github.com/cyphar/filepath-securejoin/LICENSE.BSD−28

This diff is too big to show here.

vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0−373

This diff is too big to show here.

vendor/github.com/cyphar/filepath-securejoin/README.md−184

This diff is too big to show here.

vendor/github.com/cyphar/filepath-securejoin/VERSION−1

This diff is too big to show here.

vendor/github.com/cyphar/filepath-securejoin/codecov.yml−29

This diff is too big to show here.

vendor/github.com/cyphar/filepath-securejoin/doc.go−47

This diff is too big to show here.

vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go−15

This diff is too big to show here.

vendor/github.com/cyphar/filepath-securejoin/join.go−169

This diff is too big to show here.

vendor/github.com/cyphar/filepath-securejoin/vfs.go−37

This diff is too big to show here.

vendor/github.com/davecgh/go-spew/LICENSE−15

This diff is too big to show here.

vendor/github.com/davecgh/go-spew/spew/bypass.go−145

This diff is too big to show here.

vendor/github.com/davecgh/go-spew/spew/bypasssafe.go−38

This diff is too big to show here.

vendor/github.com/davecgh/go-spew/spew/common.go−341

This diff is too big to show here.

vendor/github.com/davecgh/go-spew/spew/config.go−306

This diff is too big to show here.

vendor/github.com/davecgh/go-spew/spew/doc.go−211

This diff is too big to show here.

vendor/github.com/davecgh/go-spew/spew/dump.go−509

This diff is too big to show here.

vendor/github.com/davecgh/go-spew/spew/format.go−419

This diff is too big to show here.

vendor/github.com/davecgh/go-spew/spew/spew.go−148

This diff is too big to show here.

vendor/github.com/denisbrodbeck/machineid/.gitignore−29

This diff is too big to show here.

vendor/github.com/denisbrodbeck/machineid/LICENSE.md−22

This diff is too big to show here.

vendor/github.com/denisbrodbeck/machineid/README.md−182

This diff is too big to show here.

vendor/github.com/denisbrodbeck/machineid/helper.go−36

This diff is too big to show here.

vendor/github.com/denisbrodbeck/machineid/id.go−45

This diff is too big to show here.

vendor/github.com/denisbrodbeck/machineid/id_bsd.go−42

This diff is too big to show here.

vendor/github.com/denisbrodbeck/machineid/id_darwin.go−37

This diff is too big to show here.

vendor/github.com/denisbrodbeck/machineid/id_linux.go−27

This diff is too big to show here.

vendor/github.com/denisbrodbeck/machineid/id_windows.go−23

This diff is too big to show here.

vendor/github.com/denisbrodbeck/machineid/logo.png

This diff is too big to show here.

vendor/github.com/denisbrodbeck/machineid/makefile−12

This diff is too big to show here.

vendor/github.com/disintegration/imaging/.travis.yml−12

This diff is too big to show here.

vendor/github.com/disintegration/imaging/LICENSE−21

This diff is too big to show here.

vendor/github.com/disintegration/imaging/README.md−226

This diff is too big to show here.

vendor/github.com/disintegration/imaging/adjust.go−253

This diff is too big to show here.

vendor/github.com/disintegration/imaging/convolution.go−148

This diff is too big to show here.

vendor/github.com/disintegration/imaging/doc.go−7

This diff is too big to show here.

vendor/github.com/disintegration/imaging/effects.go−169

This diff is too big to show here.

vendor/github.com/disintegration/imaging/histogram.go−52

This diff is too big to show here.

vendor/github.com/disintegration/imaging/io.go−444

This diff is too big to show here.

vendor/github.com/disintegration/imaging/resize.go−595

This diff is too big to show here.

vendor/github.com/disintegration/imaging/scanner.go−285

This diff is too big to show here.

vendor/github.com/disintegration/imaging/tools.go−249

This diff is too big to show here.

vendor/github.com/disintegration/imaging/transform.go−268

This diff is too big to show here.

vendor/github.com/disintegration/imaging/utils.go−167

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/.gitignore−29

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/ATTRIB−133

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/LICENSE−21

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/README.md−283

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/Taskfile.yml−64

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/bufferpool.go−76

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/fastclock.go−141

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/helpers/indexof.go−394

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/helpers/math.go−15

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/helpers/runes.go−58

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/helpers/searchvalues.go−180

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/match.go−455

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/options.go−157

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/regexp.go−717

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/regexp_codegen.go−70

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/replace.go−357

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/runner.go−2197

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/split.go−67

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/stringprefixfilter.go−375

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/syntax/charclass.go−1507

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/syntax/code.go−316

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/syntax/escape.go−94

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/syntax/fuzz.go−21

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/syntax/optimizations.go−672

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/syntax/parser.go−2529

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/syntax/prefix.go−943

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/syntax/prefixanalyzer.go−1491

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/syntax/replacerdata.go−87

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/syntax/tree.go−2362

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/syntax/unicode_alias_tables.go−6058

This diff is too big to show here.

vendor/github.com/dlclark/regexp2/v2/syntax/writer.go−500

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/.travis.yml−21

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/LICENSE−21

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/README.markdown−124

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/big.go−31

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/bigbytes.go−189

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/bytes.go−143

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/comma.go−116

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/commaf.go−41

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/ftoa.go−49

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/humanize.go−8

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/number.go−192

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/ordinals.go−25

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/si.go−127

This diff is too big to show here.

vendor/github.com/dustin/go-humanize/times.go−117

This diff is too big to show here.

vendor/github.com/ebitengine/purego/.gitignore−1

This diff is too big to show here.

vendor/github.com/ebitengine/purego/LICENSE−201

This diff is too big to show here.

vendor/github.com/ebitengine/purego/README.md−119

This diff is too big to show here.

vendor/github.com/ebitengine/purego/abi_amd64.h−99

This diff is too big to show here.

vendor/github.com/ebitengine/purego/abi_arm64.h−39

This diff is too big to show here.

vendor/github.com/ebitengine/purego/abi_loong64.h−60

This diff is too big to show here.

vendor/github.com/ebitengine/purego/cgo.go−19

This diff is too big to show here.

vendor/github.com/ebitengine/purego/dlerror.go−17

This diff is too big to show here.

vendor/github.com/ebitengine/purego/dlfcn.go−99

This diff is too big to show here.

vendor/github.com/ebitengine/purego/dlfcn_android.go−34

This diff is too big to show here.

vendor/github.com/ebitengine/purego/dlfcn_darwin.go−20

This diff is too big to show here.

vendor/github.com/ebitengine/purego/dlfcn_freebsd.go−14

This diff is too big to show here.

vendor/github.com/ebitengine/purego/dlfcn_linux.go−16

This diff is too big to show here.

vendor/github.com/ebitengine/purego/dlfcn_netbsd.go−15

This diff is too big to show here.

vendor/github.com/ebitengine/purego/dlfcn_nocgo_freebsd.go−11

This diff is too big to show here.

vendor/github.com/ebitengine/purego/dlfcn_nocgo_linux.go−19

This diff is too big to show here.

vendor/github.com/ebitengine/purego/dlfcn_nocgo_netbsd.go−9

This diff is too big to show here.

vendor/github.com/ebitengine/purego/dlfcn_playground.go−24

This diff is too big to show here.

vendor/github.com/ebitengine/purego/dlfcn_stubs.s−22

This diff is too big to show here.

vendor/github.com/ebitengine/purego/func.go−564

This diff is too big to show here.

vendor/github.com/ebitengine/purego/gen.go−6

This diff is too big to show here.

vendor/github.com/ebitengine/purego/go_runtime.go−13

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/cgo/dlfcn_cgo_unix.go−56

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/cgo/empty.go−6

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/cgo/syscall_cgo_unix.go−55

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/abi_amd64.h−99

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/abi_arm64.h−39

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/abi_loong64.h−60

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/abi_ppc64x.h−195

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/asm_386.s−29

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/asm_amd64.s−39

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/asm_arm.s−52

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/asm_arm64.s−36

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/asm_loong64.s−40

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/asm_ppc64le.s−82

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/asm_riscv64.s−78

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/asm_s390x.s−55

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/callbacks.go−93

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/doc.go−32

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/fakecgo.go−14

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/freebsd.go−27

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin.go−88

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd.go−100

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go−72

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux.go−100

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/go_netbsd.go−106

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/go_setenv.go−18

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/go_util.go−38

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/iscgo.go−19

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo.go−39

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_darwin.go−26

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_freebsd.go−20

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_linux.go−20

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_netbsd.go−26

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/netbsd.go−23

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/setenv.go−19

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_386.s−107

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_amd64.s−107

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm.s−81

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm64.s−84

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_loong64.s−88

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_ppc64le.s−227

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_riscv64.s−72

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/zsymbols.go−165

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/zsymbols_darwin.go−59

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/zsymbols_freebsd.go−48

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/zsymbols_linux.go−48

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/zsymbols_netbsd.go−59

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/ztrampolines_darwin.s−19

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/ztrampolines_freebsd.s−16

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/ztrampolines_linux.s−16

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/ztrampolines_netbsd.s−19

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/fakecgo/ztrampolines_stubs.s−55

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/strings/strings.go−40

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/xreflect/reflect_go124.go−15

This diff is too big to show here.

vendor/github.com/ebitengine/purego/internal/xreflect/reflect_go125.go−12

This diff is too big to show here.

vendor/github.com/ebitengine/purego/is_ios.go−13

This diff is too big to show here.

vendor/github.com/ebitengine/purego/nocgo.go−25

This diff is too big to show here.

vendor/github.com/ebitengine/purego/objc/objc_block_darwin.go−272

This diff is too big to show here.

vendor/github.com/ebitengine/purego/objc/objc_runtime_darwin.go−709

This diff is too big to show here.

vendor/github.com/ebitengine/purego/struct_386.go−41

This diff is too big to show here.

vendor/github.com/ebitengine/purego/struct_amd64.go−286

This diff is too big to show here.

vendor/github.com/ebitengine/purego/struct_arm.go−85

This diff is too big to show here.

vendor/github.com/ebitengine/purego/struct_arm64.go−549

This diff is too big to show here.

vendor/github.com/ebitengine/purego/struct_loong64.go−213

This diff is too big to show here.

vendor/github.com/ebitengine/purego/struct_ppc64le.go−143

This diff is too big to show here.

vendor/github.com/ebitengine/purego/struct_riscv64.go−143

This diff is too big to show here.

vendor/github.com/ebitengine/purego/struct_s390x.go−143

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_386.s−147

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_amd64.s−170

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_arm.s−142

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_arm64.s−97

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_loong64.s−96

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_ppc64le.s−120

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_riscv64.s−101

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_s390x.s−114

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_unix_386.s−226

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_unix_arm.s−89

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_unix_arm64.s−70

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_unix_loong64.s−75

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_unix_ppc64le.s−114

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_unix_riscv64.s−79

This diff is too big to show here.

vendor/github.com/ebitengine/purego/sys_unix_s390x.s−109

This diff is too big to show here.

vendor/github.com/ebitengine/purego/syscall.go−83

This diff is too big to show here.

vendor/github.com/ebitengine/purego/syscall_32bit.go−109

This diff is too big to show here.

vendor/github.com/ebitengine/purego/syscall_cgo_linux.go−21

This diff is too big to show here.

vendor/github.com/ebitengine/purego/syscall_sysv.go−317

This diff is too big to show here.

vendor/github.com/ebitengine/purego/syscall_sysv_others.go−28

This diff is too big to show here.

vendor/github.com/ebitengine/purego/syscall_sysv_stackargs.go−33

This diff is too big to show here.

vendor/github.com/ebitengine/purego/syscall_windows.go−46

This diff is too big to show here.

vendor/github.com/ebitengine/purego/zcallback_386.s−4014

This diff is too big to show here.

vendor/github.com/ebitengine/purego/zcallback_amd64.s−2014

This diff is too big to show here.

vendor/github.com/ebitengine/purego/zcallback_arm.s−4014

This diff is too big to show here.

vendor/github.com/ebitengine/purego/zcallback_arm64.s−4014

This diff is too big to show here.

vendor/github.com/ebitengine/purego/zcallback_loong64.s−4014

This diff is too big to show here.

vendor/github.com/ebitengine/purego/zcallback_ppc64le.s−4014

This diff is too big to show here.

vendor/github.com/ebitengine/purego/zcallback_riscv64.s−4051

This diff is too big to show here.

vendor/github.com/ebitengine/purego/zcallback_s390x.s−4015

This diff is too big to show here.

vendor/github.com/esiqveland/notify/.gitignore−1

This diff is too big to show here.

vendor/github.com/esiqveland/notify/LICENSE.txt−27

This diff is too big to show here.

vendor/github.com/esiqveland/notify/README.md−54

This diff is too big to show here.

vendor/github.com/esiqveland/notify/doc.go−18

This diff is too big to show here.

vendor/github.com/esiqveland/notify/notification.go−547

This diff is too big to show here.

vendor/github.com/felixge/httpsnoop/.gitignore

This diff is too big to show here.

vendor/github.com/felixge/httpsnoop/LICENSE.txt−19

This diff is too big to show here.

vendor/github.com/felixge/httpsnoop/Makefile−10

This diff is too big to show here.

vendor/github.com/felixge/httpsnoop/README.md−95

This diff is too big to show here.

vendor/github.com/felixge/httpsnoop/capture_metrics.go−121

This diff is too big to show here.

vendor/github.com/felixge/httpsnoop/docs.go−10

This diff is too big to show here.

vendor/github.com/felixge/httpsnoop/wrap_generated.go−16179

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/.cirrus.yml−14

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/.gitignore−10

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/.mailmap−2

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/CHANGELOG.md−602

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md−145

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/LICENSE−25

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/README.md−182

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/backend_fen.go−467

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/backend_inotify.go−583

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/backend_kqueue.go−705

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/backend_other.go−22

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/backend_windows.go−680

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/fsnotify.go−496

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/darwin.go−39

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go−57

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go−33

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go−42

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go−32

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go−56

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go−25

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go−28

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go−45

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go−40

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/freebsd.go−31

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/internal.go−2

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/unix.go−31

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/unix2.go−7

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/internal/windows.go−41

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/shared.go−64

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/staticcheck.conf−3

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/system_bsd.go−7

This diff is too big to show here.

vendor/github.com/fsnotify/fsnotify/system_darwin.go−8

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/LICENSE−23

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/README.md−41

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/alert_darwin.go−8

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/alert_js.go−12

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/alert_unix.go−17

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/alert_unsupported.go−8

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/alert_windows.go−43

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/beeep.go−55

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/beep_darwin.go−32

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/beep_js.go−39

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/beep_unix.go−100

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/beep_unsupported.go−15

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/beep_windows.go−52

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/notify_darwin.go−131

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/notify_js.go−63

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/notify_unix.go−181

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/notify_unix_nodbus.go−78

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/notify_unsupported.go−8

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/notify_windows.go−157

This diff is too big to show here.

vendor/github.com/gen2brain/beeep/notify_windows_compat.go−109

This diff is too big to show here.

vendor/github.com/go-git/gcfg/.gitignore−1

This diff is too big to show here.

vendor/github.com/go-git/gcfg/LICENSE−28

This diff is too big to show here.

vendor/github.com/go-git/gcfg/Makefile−17

This diff is too big to show here.

vendor/github.com/go-git/gcfg/README−4

This diff is too big to show here.

vendor/github.com/go-git/gcfg/doc.go−145

This diff is too big to show here.

vendor/github.com/go-git/gcfg/errors.go−41

This diff is too big to show here.

vendor/github.com/go-git/gcfg/read.go−273

This diff is too big to show here.

vendor/github.com/go-git/gcfg/scanner/errors.go−121

This diff is too big to show here.

vendor/github.com/go-git/gcfg/scanner/scanner.go−334

This diff is too big to show here.

vendor/github.com/go-git/gcfg/set.go−334

This diff is too big to show here.

vendor/github.com/go-git/gcfg/token/position.go−435

This diff is too big to show here.

vendor/github.com/go-git/gcfg/token/serialize.go−56

This diff is too big to show here.

vendor/github.com/go-git/gcfg/token/token.go−83

This diff is too big to show here.

vendor/github.com/go-git/gcfg/types/bool.go−23

This diff is too big to show here.

vendor/github.com/go-git/gcfg/types/doc.go−4

This diff is too big to show here.

vendor/github.com/go-git/gcfg/types/enum.go−44

This diff is too big to show here.

vendor/github.com/go-git/gcfg/types/int.go−86

This diff is too big to show here.

vendor/github.com/go-git/gcfg/types/scan.go−23

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/.gitignore−4

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/LICENSE−201

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/Makefile−18

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/README.md−77

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/fs.go−210

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go−444

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go−114

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/memfs/memory.go−428

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/memfs/storage.go−251

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/osfs/os.go−132

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go−351

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go−126

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/osfs/os_js.go−25

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/osfs/os_options.go−3

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/osfs/os_plan9.go−91

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/osfs/os_posix.go−38

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/osfs/os_wasip1.go−34

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/osfs/os_windows.go−58

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/util/glob.go−111

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/util/util.go−282

This diff is too big to show here.

vendor/github.com/go-git/go-billy/v5/util/walk.go−72

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/LICENSE−201

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/config/branch.go−123

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/config/config.go−727

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/config/modules.go−191

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/config/optbool.go−82

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/config/refspec.go−155

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/config/url.go−81

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/internal/path_util/path_util.go−29

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/internal/pathutil/dotgit.go−21

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/internal/pathutil/hfs.go−99

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/internal/pathutil/ntfs.go−187

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/internal/pathutil/tree.go−66

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/internal/url/url.go−72

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/error.go−35

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/format/config/common.go−109

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/format/config/decoder.go−37

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/format/config/doc.go−122

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/format/config/encoder.go−82

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/format/config/format.go−53

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/format/config/option.go−127

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/format/config/section.go−181

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/format/gitignore/dir.go−148

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/format/gitignore/doc.go−70

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/format/gitignore/matcher.go−30

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/format/gitignore/pattern.go−155

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/hash.go−84

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/hash/hash.go−60

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/hash/hash_sha1.go−15

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/hash/hash_sha256.go−15

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/memory.go−72

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/object.go−111

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/reference.go−315

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/plumbing/revision.go−11

This diff is too big to show here.

vendor/github.com/go-git/go-git/v5/utils/ioutil/common.go−210

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/AUTHORS−3

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/CONTRIBUTORS−3

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/LICENSE−27

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/README.md−176

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/alias.go−943

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/api.png

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/arshal.go−569

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/arshal_any.go−289

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/arshal_default.go−2043

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/arshal_embedded.go−230

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/arshal_funcs.go−428

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/arshal_methods.go−378

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/arshal_time.go−795

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/doc.go−247

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/errors.go−465

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/fields.go−656

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/fold.go−58

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/intern.go−88

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/internal/internal.go−42

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/internal/jsonflags/flags.go−223

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/internal/jsonopts/options.go−217

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/internal/jsonopts/options_format.go−124

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/internal/jsonwire/decode.go−606

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/internal/jsonwire/encode.go−290

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/internal/jsonwire/wire.go−209

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/jsontext/alias.go−555

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/jsontext/decode.go−1218

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/jsontext/doc.go−111

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/jsontext/encode.go−993

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/jsontext/errors.go−213

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/jsontext/export.go−91

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/jsontext/options.go−304

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/jsontext/pools.go−164

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/jsontext/quote.go−41

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/jsontext/state.go−828

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/jsontext/token.go−697

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/jsontext/value.go−414

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/migrate.sh−60

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/options.go−290

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/options_format.go−122

This diff is too big to show here.

vendor/github.com/go-json-experiment/json/options_format_global.go−30

This diff is too big to show here.

vendor/github.com/go-logfmt/logfmt/.gitignore−1

This diff is too big to show here.

vendor/github.com/go-logfmt/logfmt/CHANGELOG.md−82

This diff is too big to show here.

vendor/github.com/go-logfmt/logfmt/LICENSE−22

This diff is too big to show here.

vendor/github.com/go-logfmt/logfmt/README.md−41

This diff is too big to show here.

vendor/github.com/go-logfmt/logfmt/decode.go−254

This diff is too big to show here.

vendor/github.com/go-logfmt/logfmt/doc.go−6

This diff is too big to show here.

vendor/github.com/go-logfmt/logfmt/encode.go−322

This diff is too big to show here.

vendor/github.com/go-logfmt/logfmt/jsonstring.go−277

This diff is too big to show here.

vendor/github.com/go-logr/logr/.golangci.yaml−28

This diff is too big to show here.

vendor/github.com/go-logr/logr/CHANGELOG.md−6

This diff is too big to show here.

vendor/github.com/go-logr/logr/CONTRIBUTING.md−17

This diff is too big to show here.

vendor/github.com/go-logr/logr/LICENSE−201

This diff is too big to show here.

vendor/github.com/go-logr/logr/README.md−407

This diff is too big to show here.

vendor/github.com/go-logr/logr/SECURITY.md−18

This diff is too big to show here.

vendor/github.com/go-logr/logr/context.go−33

This diff is too big to show here.

vendor/github.com/go-logr/logr/context_noslog.go−49

This diff is too big to show here.

vendor/github.com/go-logr/logr/context_slog.go−83

This diff is too big to show here.

vendor/github.com/go-logr/logr/discard.go−24

This diff is too big to show here.

vendor/github.com/go-logr/logr/funcr/funcr.go−914

This diff is too big to show here.

vendor/github.com/go-logr/logr/funcr/slogsink.go−105

This diff is too big to show here.

vendor/github.com/go-logr/logr/logr.go−520

This diff is too big to show here.

vendor/github.com/go-logr/logr/sloghandler.go−192

This diff is too big to show here.

vendor/github.com/go-logr/logr/slogr.go−100

This diff is too big to show here.

vendor/github.com/go-logr/logr/slogsink.go−120

This diff is too big to show here.

vendor/github.com/go-logr/stdr/LICENSE−201

This diff is too big to show here.

vendor/github.com/go-logr/stdr/README.md−6

This diff is too big to show here.

vendor/github.com/go-logr/stdr/stdr.go−170

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/.travis.yml−8

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/ChangeLog.md−49

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/LICENSE−21

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/README.md−46

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/SECURITY.md−13

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/appveyor.yml−68

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/com.go−386

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/com_func.go−174

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/connect.go−192

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/constants.go−153

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/error.go−51

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/error_func.go−8

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/error_windows.go−24

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/guid.go−284

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iconnectionpoint.go−20

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iconnectionpoint_func.go−21

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iconnectionpoint_windows.go−43

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iconnectionpointcontainer.go−17

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iconnectionpointcontainer_func.go−11

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iconnectionpointcontainer_windows.go−25

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/idispatch.go−94

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/idispatch_func.go−19

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/idispatch_windows.go−203

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/ienumvariant.go−19

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/ienumvariant_func.go−19

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/ienumvariant_windows.go−63

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iinspectable.go−18

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iinspectable_func.go−15

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iinspectable_windows.go−72

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iprovideclassinfo.go−21

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iprovideclassinfo_func.go−7

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iprovideclassinfo_windows.go−21

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/itypeinfo.go−34

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/itypeinfo_func.go−7

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/itypeinfo_windows.go−21

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iunknown.go−57

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iunknown_func.go−19

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/iunknown_windows.go−58

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/ole.go−190

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/safearray.go−27

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/safearray_func.go−211

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/safearray_windows.go−337

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/safearrayconversion.go−140

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/safearrayslices.go−33

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/utility.go−101

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/variables.go−15

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/variant.go−105

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/variant_386.go−11

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/variant_amd64.go−12

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/variant_arm.go−11

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/variant_arm64.go−13

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/variant_date_386.go−22

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/variant_date_amd64.go−20

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/variant_date_arm.go−22

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/variant_date_arm64.go−23

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/variant_ppc64le.go−12

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/variant_s390x.go−12

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/vt_string.go−58

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/winrt.go−99

This diff is too big to show here.

vendor/github.com/go-ole/go-ole/winrt_doc.go−36

This diff is too big to show here.

vendor/github.com/go-openapi/jsonpointer/.editorconfig−26

This diff is too big to show here.

vendor/github.com/go-openapi/jsonpointer/.gitignore−1

This diff is too big to show here.

vendor/github.com/go-openapi/jsonpointer/.travis.yml−15

This diff is too big to show here.

vendor/github.com/go-openapi/jsonpointer/CODE_OF_CONDUCT.md−74

This diff is too big to show here.

vendor/github.com/go-openapi/jsonpointer/LICENSE−202

This diff is too big to show here.