From 6a04f6755b6de499a2451fd0f8dd732b437e8546 Mon Sep 17 00:00:00 2001 From: bbrodriges Date: Fri, 1 Aug 2025 19:21:10 +0300 Subject: [PATCH 01/14] treat Escape as Deny for permission prompt --- internal/tui/components/dialogs/permissions/keys.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/tui/components/dialogs/permissions/keys.go b/internal/tui/components/dialogs/permissions/keys.go index 9edc368d275d90d670eeb8f03346184d3edea800..4b7660ceb2310595fc0ad7d1ce51dade83169035 100644 --- a/internal/tui/components/dialogs/permissions/keys.go +++ b/internal/tui/components/dialogs/permissions/keys.go @@ -42,7 +42,7 @@ func DefaultKeyMap() KeyMap { key.WithHelp("s", "allow session"), ), Deny: key.NewBinding( - key.WithKeys("d", "D", "ctrl+d"), + key.WithKeys("d", "D", "ctrl+d", "esc"), key.WithHelp("d", "deny"), ), Select: key.NewBinding( From 9a9861c693fdc6a69a67628c35e0bb80bee22202 Mon Sep 17 00:00:00 2001 From: Kujtim Hoxha Date: Tue, 5 Aug 2025 16:31:11 +0200 Subject: [PATCH 02/14] chore: update catwalk --- go.mod | 9 ++++++++- go.sum | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 11cdd601897e4b3e564ede57e5166c0515b8225f..1724fad6445f85f3edf6e88ac2f048aaebe17f8a 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/charlievieth/fastwalk v1.0.11 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5 github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d - github.com/charmbracelet/catwalk v0.4.5 + github.com/charmbracelet/catwalk v0.4.6 github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674 github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0 @@ -51,8 +51,15 @@ require ( require ( cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect diff --git a/go.sum b/go.sum index 2f0a23a24faa153d3a836005c1b06b7c2f6bd5f3..daa8cbc337f4b9b34d1603e62c693bcbb7f237e1 100644 --- a/go.sum +++ b/go.sum @@ -68,10 +68,14 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar/v4 v4.9.0 h1:DBvuZxjdKkRP/dr4GVV4w2fnmrk5Hxc90T51LZjv0JA= github.com/bmatcuk/doublestar/v4 v4.9.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charlievieth/fastwalk v1.0.11 h1:5sLT/q9+d9xMdpKExawLppqvXFZCVKf6JHnr2u/ufj8= github.com/charlievieth/fastwalk v1.0.11/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI= github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5 h1:GTcMIfDQJKyNKS+xVt7GkNIwz+tBuQtIuiP50WpzNgs= @@ -80,6 +84,8 @@ github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52 github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d/go.mod h1:XIQ1qQfRph6Z5o2EikCydjumo0oDInQySRHuPATzbZc= github.com/charmbracelet/catwalk v0.4.5 h1:Kv3PadDe8IF8gpcYTfAJdCee5Bv4HufvtNT61FXtq5g= github.com/charmbracelet/catwalk v0.4.5/go.mod h1:WnKgNPmQHuMyk7GtwAQwl+ezHusfH40IvzML2qwUGwc= +github.com/charmbracelet/catwalk v0.4.6 h1:Y0JDq5V4agK8oO3lKC/hhInrYXePGwZPNo8I1Lk08jc= +github.com/charmbracelet/catwalk v0.4.6/go.mod h1:WnKgNPmQHuMyk7GtwAQwl+ezHusfH40IvzML2qwUGwc= github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40= github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0= github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674 h1:+Cz+VfxD5DO+JT1LlswXWhre0HYLj6l2HW8HVGfMuC0= @@ -202,6 +208,8 @@ github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/ncruces/go-sqlite3 v0.25.0 h1:trugKUs98Zwy9KwRr/EUxZHL92LYt7UqcKqAfpGpK+I= github.com/ncruces/go-sqlite3 v0.25.0/go.mod h1:n6Z7036yFilJx04yV0mi5JWaF66rUmXn1It9Ux8dx68= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -221,6 +229,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pressly/goose/v3 v3.24.2 h1:c/ie0Gm8rnIVKvnDQ/scHErv46jrDv9b4I0WRcFJzYU= github.com/pressly/goose/v3 v3.24.2/go.mod h1:kjefwFB0eR4w30Td2Gj2Mznyw94vSP+2jJYkOVNbD1k= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c h1:kmzxiX+OB0knCo1V0dkEkdPelzCdAzCURCfmFArn2/A= github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c/go.mod h1:wNJrtinHyC3YSf6giEh4FJN8+yZV7nXBjvmfjhBIcw4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= From 1e3ca2ea478a804c0658a3a1c2a13a95b5ad2523 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 14:38:34 +0000 Subject: [PATCH 03/14] chore(deps): bump github.com/bmatcuk/doublestar/v4 from 4.9.0 to 4.9.1 (#559) Bumps [github.com/bmatcuk/doublestar/v4](https://github.com/bmatcuk/doublestar) from 4.9.0 to 4.9.1. - [Release notes](https://github.com/bmatcuk/doublestar/releases) - [Commits](https://github.com/bmatcuk/doublestar/compare/v4.9.0...v4.9.1) --- updated-dependencies: - dependency-name: github.com/bmatcuk/doublestar/v4 dependency-version: 4.9.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 9 +-------- go.sum | 20 ++------------------ 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 1724fad6445f85f3edf6e88ac2f048aaebe17f8a..b310743f91f0d6942794fce8f06ce47ce85c727d 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/anthropics/anthropic-sdk-go v1.6.2 github.com/atotto/clipboard v0.1.4 github.com/aymanbagabas/go-udiff v0.3.1 - github.com/bmatcuk/doublestar/v4 v4.9.0 + github.com/bmatcuk/doublestar/v4 v4.9.1 github.com/charlievieth/fastwalk v1.0.11 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5 github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d @@ -51,15 +51,8 @@ require ( require ( cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/prometheus/client_golang v1.23.0 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect diff --git a/go.sum b/go.sum index daa8cbc337f4b9b34d1603e62c693bcbb7f237e1..3f936b5b4d25009f61e3921f3476f72ca78a7d5d 100644 --- a/go.sum +++ b/go.sum @@ -68,22 +68,16 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bmatcuk/doublestar/v4 v4.9.0 h1:DBvuZxjdKkRP/dr4GVV4w2fnmrk5Hxc90T51LZjv0JA= -github.com/bmatcuk/doublestar/v4 v4.9.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE= +github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charlievieth/fastwalk v1.0.11 h1:5sLT/q9+d9xMdpKExawLppqvXFZCVKf6JHnr2u/ufj8= github.com/charlievieth/fastwalk v1.0.11/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI= github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5 h1:GTcMIfDQJKyNKS+xVt7GkNIwz+tBuQtIuiP50WpzNgs= github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw= github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d h1:YMXLZHSo8DjytVY/b5dK8LDuyQsVUmBK3ydQMpu2Ui4= github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d/go.mod h1:XIQ1qQfRph6Z5o2EikCydjumo0oDInQySRHuPATzbZc= -github.com/charmbracelet/catwalk v0.4.5 h1:Kv3PadDe8IF8gpcYTfAJdCee5Bv4HufvtNT61FXtq5g= -github.com/charmbracelet/catwalk v0.4.5/go.mod h1:WnKgNPmQHuMyk7GtwAQwl+ezHusfH40IvzML2qwUGwc= github.com/charmbracelet/catwalk v0.4.6 h1:Y0JDq5V4agK8oO3lKC/hhInrYXePGwZPNo8I1Lk08jc= github.com/charmbracelet/catwalk v0.4.6/go.mod h1:WnKgNPmQHuMyk7GtwAQwl+ezHusfH40IvzML2qwUGwc= github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40= @@ -208,8 +202,6 @@ github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/ncruces/go-sqlite3 v0.25.0 h1:trugKUs98Zwy9KwRr/EUxZHL92LYt7UqcKqAfpGpK+I= github.com/ncruces/go-sqlite3 v0.25.0/go.mod h1:n6Z7036yFilJx04yV0mi5JWaF66rUmXn1It9Ux8dx68= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -229,14 +221,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pressly/goose/v3 v3.24.2 h1:c/ie0Gm8rnIVKvnDQ/scHErv46jrDv9b4I0WRcFJzYU= github.com/pressly/goose/v3 v3.24.2/go.mod h1:kjefwFB0eR4w30Td2Gj2Mznyw94vSP+2jJYkOVNbD1k= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c h1:kmzxiX+OB0knCo1V0dkEkdPelzCdAzCURCfmFArn2/A= github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c/go.mod h1:wNJrtinHyC3YSf6giEh4FJN8+yZV7nXBjvmfjhBIcw4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= From b045eec87edfc09660f4e8b862a41c48c8618087 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 5 Aug 2025 11:53:18 -0400 Subject: [PATCH 04/14] chore: bump ultraviolet to v0.0.0-20250805154935-01be9d7ef65d Fix scroll and rendering issues related to missing HPA, CHA, and REP capabilities support. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b310743f91f0d6942794fce8f06ce47ce85c727d..b5581bb9456e7968981d562a23ee88761f7e725c 100644 --- a/go.mod +++ b/go.mod @@ -85,7 +85,7 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/colorprofile v0.3.1 // indirect - github.com/charmbracelet/ultraviolet v0.0.0-20250731212901-76da584cc9a5 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20250805154935-01be9d7ef65d // indirect github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa // indirect github.com/charmbracelet/x/exp/slice v0.0.0-20250611152503-f53cdd7e01ef github.com/charmbracelet/x/term v0.2.1 diff --git a/go.sum b/go.sum index 3f936b5b4d25009f61e3921f3476f72ca78a7d5d..81855680a10524e34b1327a789a200ca03eeb2f0 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,8 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0/go.mod h1:XIuqKpZTUXtVyeyiN1k9Tc/U7EzfaDnVc34feFHfBws= github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706 h1:WkwO6Ks3mSIGnGuSdKl9qDSyfbYK50z2wc2gGMggegE= github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706/go.mod h1:mjJGp00cxcfvD5xdCa+bso251Jt4owrQvuimJtVmEmM= -github.com/charmbracelet/ultraviolet v0.0.0-20250731212901-76da584cc9a5 h1:FrEzjuUbVbGd8UtZBfK8mf/IA4ExT2i3/fi+SEOv2eM= -github.com/charmbracelet/ultraviolet v0.0.0-20250731212901-76da584cc9a5/go.mod h1:XrrgNFfXLrFAyd9DUmrqVc3yQFVv8Uk+okj4PsNNzpc= +github.com/charmbracelet/ultraviolet v0.0.0-20250805154935-01be9d7ef65d h1:miSXsyi0ARm35O+DulTdaLYoUioWdGkoBfFoIhdqpCA= +github.com/charmbracelet/ultraviolet v0.0.0-20250805154935-01be9d7ef65d/go.mod h1:XrrgNFfXLrFAyd9DUmrqVc3yQFVv8Uk+okj4PsNNzpc= github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0= github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa h1:lphz0Z3rsiOtMYiz8axkT24i9yFiueDhJbzyNUADmME= From 4f7098f3569de595131d07524243b4047a86d038 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 5 Aug 2025 15:06:32 -0400 Subject: [PATCH 05/14] chore: bump bubbletea to v2.0.0-beta.4.0.20250805190305 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b5581bb9456e7968981d562a23ee88761f7e725c..23557668d50f06455b76b4e1b24ef74e5cff86e4 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/bmatcuk/doublestar/v4 v4.9.1 github.com/charlievieth/fastwalk v1.0.11 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5 - github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d + github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250805190305-70e94a2e0b2d github.com/charmbracelet/catwalk v0.4.6 github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674 github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe diff --git a/go.sum b/go.sum index 81855680a10524e34b1327a789a200ca03eeb2f0..5176b9092d42f5370eea1a1e41c7dc256f8d7e46 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,8 @@ github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw= github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d h1:YMXLZHSo8DjytVY/b5dK8LDuyQsVUmBK3ydQMpu2Ui4= github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d/go.mod h1:XIQ1qQfRph6Z5o2EikCydjumo0oDInQySRHuPATzbZc= +github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250805190305-70e94a2e0b2d h1:1C2whi5rgs+APtsQDE4riA+W5neYaMNY+Y+5o6J/rDU= +github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250805190305-70e94a2e0b2d/go.mod h1:XIQ1qQfRph6Z5o2EikCydjumo0oDInQySRHuPATzbZc= github.com/charmbracelet/catwalk v0.4.6 h1:Y0JDq5V4agK8oO3lKC/hhInrYXePGwZPNo8I1Lk08jc= github.com/charmbracelet/catwalk v0.4.6/go.mod h1:WnKgNPmQHuMyk7GtwAQwl+ezHusfH40IvzML2qwUGwc= github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40= From e23563af5f3c6a3e0a76fdf541a73cf43db25a59 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 5 Aug 2025 15:21:53 -0400 Subject: [PATCH 06/14] chore: bump charmbracelet/x/ansi to v0.10.0 --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 23557668d50f06455b76b4e1b24ef74e5cff86e4..3749dfdb75209486a80e386547b708b7002e3807 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0 github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706 - github.com/charmbracelet/x/ansi v0.9.3 + github.com/charmbracelet/x/ansi v0.10.0 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250708181618-a60a724ba6c3 github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec diff --git a/go.sum b/go.sum index 5176b9092d42f5370eea1a1e41c7dc256f8d7e46..07607afea2b6ea6392d2767f17bd70f90df5f23c 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,6 @@ github.com/charlievieth/fastwalk v1.0.11 h1:5sLT/q9+d9xMdpKExawLppqvXFZCVKf6JHnr github.com/charlievieth/fastwalk v1.0.11/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI= github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5 h1:GTcMIfDQJKyNKS+xVt7GkNIwz+tBuQtIuiP50WpzNgs= github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw= -github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d h1:YMXLZHSo8DjytVY/b5dK8LDuyQsVUmBK3ydQMpu2Ui4= -github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250730165737-56ff7146d52d/go.mod h1:XIQ1qQfRph6Z5o2EikCydjumo0oDInQySRHuPATzbZc= github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250805190305-70e94a2e0b2d h1:1C2whi5rgs+APtsQDE4riA+W5neYaMNY+Y+5o6J/rDU= github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250805190305-70e94a2e0b2d/go.mod h1:XIQ1qQfRph6Z5o2EikCydjumo0oDInQySRHuPATzbZc= github.com/charmbracelet/catwalk v0.4.6 h1:Y0JDq5V4agK8oO3lKC/hhInrYXePGwZPNo8I1Lk08jc= @@ -94,8 +92,8 @@ github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706 h1:WkwO6Ks3mS github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706/go.mod h1:mjJGp00cxcfvD5xdCa+bso251Jt4owrQvuimJtVmEmM= github.com/charmbracelet/ultraviolet v0.0.0-20250805154935-01be9d7ef65d h1:miSXsyi0ARm35O+DulTdaLYoUioWdGkoBfFoIhdqpCA= github.com/charmbracelet/ultraviolet v0.0.0-20250805154935-01be9d7ef65d/go.mod h1:XrrgNFfXLrFAyd9DUmrqVc3yQFVv8Uk+okj4PsNNzpc= -github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0= -github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= +github.com/charmbracelet/x/ansi v0.10.0 h1:jnOP9pFxY6/gw5nYjkpi6f17K0P/sN4fqT0Y1ioaORI= +github.com/charmbracelet/x/ansi v0.10.0/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa h1:lphz0Z3rsiOtMYiz8axkT24i9yFiueDhJbzyNUADmME= github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa/go.mod h1:xBlh2Yi3DL3zy/2n15kITpg0YZardf/aa/hgUaIM6Rk= github.com/charmbracelet/x/exp/charmtone v0.0.0-20250708181618-a60a724ba6c3 h1:1xwHZg6eMZ9Wv5TE1UGub6ARubyOd1Lo5kPUI/6VL50= From 7e58d53f58f14028fcfa1aef1500b0fef8447d4b Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 5 Aug 2025 16:22:40 -0400 Subject: [PATCH 07/14] fix(chat): focus chat and editor on mouse click --- internal/tui/page/chat/chat.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/tui/page/chat/chat.go b/internal/tui/page/chat/chat.go index 6fd3849fbf37d01489f624924c77c299a06d2087..a281ef0b0ae80ffd4ad07d7d16a8755b782bf0b6 100644 --- a/internal/tui/page/chat/chat.go +++ b/internal/tui/page/chat/chat.go @@ -172,10 +172,18 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return p, nil case tea.MouseClickMsg: + if p.isMouseOverChat(msg.X, msg.Y) { + p.focusedPane = PanelTypeChat + p.chat.Focus() + p.editor.Blur() + } else { + p.focusedPane = PanelTypeEditor + p.editor.Focus() + p.chat.Blur() + } u, cmd := p.chat.Update(msg) p.chat = u.(chat.MessageListCmp) return p, cmd - return p, nil case tea.MouseMotionMsg: if msg.Button == tea.MouseLeft { u, cmd := p.chat.Update(msg) From 6b545747ded24028e506ec8442eff1315bb6c231 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 5 Aug 2025 16:24:30 -0400 Subject: [PATCH 08/14] fix(chat): expose copy key binding --- internal/tui/components/chat/messages/messages.go | 5 +++-- internal/tui/components/chat/messages/tool.go | 2 +- internal/tui/page/chat/chat.go | 6 ++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/tui/components/chat/messages/messages.go b/internal/tui/components/chat/messages/messages.go index b1a17eee4e4b91885941deb415cf8c9fd877fe72..891f4e483fcc7b9cdb6d82485998329995d542a6 100644 --- a/internal/tui/components/chat/messages/messages.go +++ b/internal/tui/components/chat/messages/messages.go @@ -25,7 +25,8 @@ import ( "github.com/charmbracelet/crush/internal/tui/util" ) -var copyKey = key.NewBinding(key.WithKeys("c", "y", "C", "Y"), key.WithHelp("c/y", "copy")) +// CopyKey is the key binding for copying message content to the clipboard. +var CopyKey = key.NewBinding(key.WithKeys("c", "y", "C", "Y"), key.WithHelp("c/y", "copy")) // MessageCmp defines the interface for message components in the chat interface. // It combines standard UI model interfaces with message-specific functionality. @@ -99,7 +100,7 @@ func (m *messageCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } case tea.KeyPressMsg: - if key.Matches(msg, copyKey) { + if key.Matches(msg, CopyKey) { err := clipboard.WriteAll(m.message.Content().Text) if err != nil { return m, util.ReportError(fmt.Errorf("failed to copy message content to clipboard: %w", err)) diff --git a/internal/tui/components/chat/messages/tool.go b/internal/tui/components/chat/messages/tool.go index 7708b6b3e273471973a355bc77c0110c0be21e45..41bb7b81d0e9fa202e98bd91aca4f2eddf9c22c1 100644 --- a/internal/tui/components/chat/messages/tool.go +++ b/internal/tui/components/chat/messages/tool.go @@ -165,7 +165,7 @@ func (m *toolCallCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return m, tea.Batch(cmds...) case tea.KeyPressMsg: - if key.Matches(msg, copyKey) { + if key.Matches(msg, CopyKey) { return m, m.copyTool() } } diff --git a/internal/tui/page/chat/chat.go b/internal/tui/page/chat/chat.go index a281ef0b0ae80ffd4ad07d7d16a8755b782bf0b6..0276ab2a917bf9225d381b483f403d064fefe39a 100644 --- a/internal/tui/page/chat/chat.go +++ b/internal/tui/page/chat/chat.go @@ -19,6 +19,7 @@ import ( "github.com/charmbracelet/crush/internal/tui/components/chat" "github.com/charmbracelet/crush/internal/tui/components/chat/editor" "github.com/charmbracelet/crush/internal/tui/components/chat/header" + "github.com/charmbracelet/crush/internal/tui/components/chat/messages" "github.com/charmbracelet/crush/internal/tui/components/chat/sidebar" "github.com/charmbracelet/crush/internal/tui/components/chat/splash" "github.com/charmbracelet/crush/internal/tui/components/completions" @@ -865,10 +866,7 @@ func (p *chatPage) Help() help.KeyMap { key.WithKeys("up", "down"), key.WithHelp("↑↓", "scroll"), ), - key.NewBinding( - key.WithKeys("c", "y"), - key.WithHelp("c/y", "copy"), - ), + messages.CopyKey, ) fullList = append(fullList, []key.Binding{ From 1f8bc4e2d3ed389463fe0ce04c2d5d9bce8acabe Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 5 Aug 2025 16:25:30 -0400 Subject: [PATCH 09/14] feat(list): add HasSelection method and selectionView for text-only output --- internal/tui/exp/list/list.go | 145 ++++++++++++++-------------------- 1 file changed, 59 insertions(+), 86 deletions(-) diff --git a/internal/tui/exp/list/list.go b/internal/tui/exp/list/list.go index f1a798a7dd23a534157ba0a143936d157b0a4dbe..8b729565f99d83accdebc0bc077963a7b25f8a34 100644 --- a/internal/tui/exp/list/list.go +++ b/internal/tui/exp/list/list.go @@ -56,6 +56,7 @@ type List[T Item] interface { SelectWord(col, line int) SelectParagraph(col, line int) GetSelectedText(paddingLeft int) string + HasSelection() bool } type direction int @@ -286,30 +287,10 @@ func (l *list[T]) handleMouseWheel(msg tea.MouseWheelMsg) (tea.Model, tea.Cmd) { return l, cmd } -// View implements List. -func (l *list[T]) View() string { - if l.height <= 0 || l.width <= 0 { - return "" - } +// selectionView renders the highlighted selection in the view and returns it +// as a string. If textOnly is true, it won't render any styles. +func (l *list[T]) selectionView(view string, textOnly bool) string { t := styles.CurrentTheme() - view := l.rendered - lines := strings.Split(view, "\n") - - start, end := l.viewPosition() - viewStart := max(0, start) - viewEnd := min(len(lines), end+1) - lines = lines[viewStart:viewEnd] - if l.resize { - return strings.Join(lines, "\n") - } - view = t.S().Base. - Height(l.height). - Width(l.width). - Render(strings.Join(lines, "\n")) - - if !l.hasSelection() { - return view - } area := uv.Rect(0, 0, l.width, l.height) scr := uv.NewScreenBuffer(area.Dx(), area.Dy()) uv.NewStyledString(view).Draw(scr, area) @@ -397,6 +378,8 @@ func (l *list[T]) View() string { lineTextBounds[y] = bounds } + var selectedText strings.Builder + // Second pass: apply selection highlighting for y := range scr.Height() { selBounds := lineSelections[y] @@ -421,16 +404,59 @@ func (l *list[T]) View() string { cellStr := cell.String() if len(cellStr) > 0 && !specialChars[cellStr] { + if textOnly { + // Collect selected text without styles + selectedText.WriteString(cell.String()) + continue + } + cell = cell.Clone() cell.Style = cell.Style.Background(t.BgOverlay).Foreground(t.White) scr.SetCell(x, y, cell) } } + + if textOnly { + // Make sure we add a newline after each line of selected text + selectedText.WriteByte('\n') + } + } + + if textOnly { + return strings.TrimSpace(selectedText.String()) } return scr.Render() } +// View implements List. +func (l *list[T]) View() string { + if l.height <= 0 || l.width <= 0 { + return "" + } + t := styles.CurrentTheme() + view := l.rendered + lines := strings.Split(view, "\n") + + start, end := l.viewPosition() + viewStart := max(0, start) + viewEnd := min(len(lines), end+1) + lines = lines[viewStart:viewEnd] + if l.resize { + return strings.Join(lines, "\n") + } + view = t.S().Base. + Height(l.height). + Width(l.width). + Render(strings.Join(lines, "\n")) + + if !l.hasSelection() { + return view + } + + return l.selectionView(view, false) +} + func (l *list[T]) viewPosition() (int, int) { start, end := 0, 0 renderedLines := lipgloss.Height(l.rendered) - 1 @@ -1374,69 +1400,16 @@ func (l *list[T]) SelectParagraph(col, line int) { l.selectionActive = false // Not actively selecting, just selected } +// HasSelection returns whether there is an active selection. +func (l *list[T]) HasSelection() bool { + return l.hasSelection() +} + // GetSelectedText returns the currently selected text. func (l *list[T]) GetSelectedText(paddingLeft int) string { - return "" - // if !l.hasSelection() { - // return "" - // } - // - // startLine := l.selectionStartLine - // endLine := l.selectionEndLine - // startCol := l.selectionStartCol - // endCol := l.selectionEndCol - // - // if l.direction == DirectionBackward { - // startLine = (lipgloss.Height(l.rendered) - 1) - startLine - // endLine = (lipgloss.Height(l.rendered) - 1) - endLine - // } - // - // if l.offset > 0 { - // if l.direction == DirectionBackward { - // startLine += l.offset - // endLine += l.offset - // } else { - // startLine -= l.offset - // endLine -= l.offset - // } - // } - // - // lines := strings.Split(l.rendered, "\n") - // - // if startLine < 0 || endLine < 0 || startLine >= len(lines) || endLine >= len(lines) { - // return "" - // } - // - // var result strings.Builder - // for i := range lines { - // lines[i] = ansi.Strip(lines[i]) - // for _, icon := range styles.SelectionIgnoreIcons { - // lines[i] = strings.ReplaceAll(lines[i], icon, " ") - // } - // - // if i == startLine { - // if startCol < 0 || startCol >= len(lines[i]) { - // startCol = 0 - // } - // if startCol < paddingLeft { - // startCol = paddingLeft - // } - // if i != endLine { - // endCol = len(lines[i]) - // } - // result.WriteString(strings.TrimRightFunc(lines[i][startCol:endCol], unicode.IsSpace)) - // } else if i > startLine && i < endLine { - // result.WriteString(strings.TrimRightFunc(lines[i][paddingLeft:], unicode.IsSpace)) - // } else if i == endLine { - // if endCol < 0 || endCol >= len(lines[i]) { - // endCol = len(lines[i]) - // } - // if endCol < paddingLeft { - // endCol = paddingLeft - // } - // result.WriteString(strings.TrimRightFunc(lines[i][paddingLeft:endCol], unicode.IsSpace)) - // } - // } - // - // return result.String() + if !l.hasSelection() { + return "" + } + + return l.selectionView(l.View(), true) } From 3d91fd0fa25a65f30086b800a5bdd91122e308f1 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 5 Aug 2025 16:26:10 -0400 Subject: [PATCH 10/14] feat(chat): copy selected text in chat messages via shared key binding This uses both OSC 52 and native clipboard for maximum compatibility with different terminal emulators and environments. The `CopySelectedText` method now accepts a boolean parameter to clear the selection after copying. --- internal/tui/components/chat/chat.go | 65 +++++++++++++------ .../tui/components/chat/messages/messages.go | 13 ++-- internal/tui/components/chat/messages/tool.go | 13 ++-- 3 files changed, 62 insertions(+), 29 deletions(-) diff --git a/internal/tui/components/chat/chat.go b/internal/tui/components/chat/chat.go index b1af201f33be840fa626244a42aa2d88fa18649c..22ef7099b505977432c650655ec9a30c098f85dd 100644 --- a/internal/tui/components/chat/chat.go +++ b/internal/tui/components/chat/chat.go @@ -2,7 +2,6 @@ package chat import ( "context" - "fmt" "time" "github.com/atotto/clipboard" @@ -45,7 +44,7 @@ type MessageListCmp interface { SetSession(session.Session) tea.Cmd GoToBottom() tea.Cmd GetSelectedText() string - CopySelectedText() tea.Cmd + CopySelectedText(bool) tea.Cmd } // messageListCmp implements MessageListCmp, providing a virtualized list @@ -96,6 +95,10 @@ func (m *messageListCmp) Init() tea.Cmd { // Update handles incoming messages and updates the component state. func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { + case tea.KeyPressMsg: + if key.Matches(msg, messages.CopyKey) && m.listCmp.HasSelection() { + return m, m.CopySelectedText(true) + } case tea.MouseClickMsg: x := msg.X - 1 // Adjust for padding y := msg.Y - 1 // Adjust for padding @@ -128,11 +131,10 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.Button == tea.MouseLeft { if x < 0 || y < 0 || x >= m.width-2 || y >= m.height-1 { m.listCmp.SelectionStop() - return m, m.CopySelectedText() + } else { + m.listCmp.EndSelection(x, y) + m.listCmp.SelectionStop() } - m.listCmp.EndSelection(x, y) - m.listCmp.SelectionStop() - return m, m.CopySelectedText() } return m, nil case pubsub.Event[permission.PermissionNotification]: @@ -155,13 +157,11 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { u, cmd := m.listCmp.Update(msg) m.listCmp = u.(list.List[list.Item]) return m, cmd - default: - var cmds []tea.Cmd - u, cmd := m.listCmp.Update(msg) - m.listCmp = u.(list.List[list.Item]) - cmds = append(cmds, cmd) - return m, tea.Batch(cmds...) } + + u, cmd := m.listCmp.Update(msg) + m.listCmp = u.(list.List[list.Item]) + return m, cmd } // View renders the message list or an initial screen if empty. @@ -654,24 +654,51 @@ func (m *messageListCmp) handleMouseClick(x, y int) tea.Cmd { return nil } +// SelectionClear clears the current selection in the list component. +func (m *messageListCmp) SelectionClear() tea.Cmd { + m.listCmp.SelectionClear() + m.previousSelected = "" + m.lastClickX, m.lastClickY = 0, 0 + m.clickCount = 0 + return nil +} + +// HasSelection checks if there is a selection in the list component. +func (m *messageListCmp) HasSelection() bool { + return m.listCmp.HasSelection() +} + // GetSelectedText returns the currently selected text from the list component. func (m *messageListCmp) GetSelectedText() string { return m.listCmp.GetSelectedText(3) // 3 padding for the left border/padding } -// CopySelectedText copies the currently selected text to the clipboard. -func (m *messageListCmp) CopySelectedText() tea.Cmd { - return nil +// CopySelectedText copies the currently selected text to the clipboard. When +// clear is true, it clears the selection after copying. +func (m *messageListCmp) CopySelectedText(clear bool) tea.Cmd { + if !m.listCmp.HasSelection() { + return nil + } + selectedText := m.GetSelectedText() if selectedText == "" { return util.ReportInfo("No text selected") } - err := clipboard.WriteAll(selectedText) - if err != nil { - return util.ReportError(fmt.Errorf("failed to copy selected text to clipboard: %w", err)) + if clear { + defer func() { m.SelectionClear() }() } - return util.ReportInfo("Selected text copied to clipboard") + + return tea.Sequence( + // We use both OSC 52 and native clipboard for compatibility with different + // terminal emulators and environments. + tea.SetClipboard(selectedText), + func() tea.Msg { + _ = clipboard.WriteAll(selectedText) + return nil + }, + util.ReportInfo("Selected text copied to clipboard"), + ) } // abs returns the absolute value of an integer. diff --git a/internal/tui/components/chat/messages/messages.go b/internal/tui/components/chat/messages/messages.go index 891f4e483fcc7b9cdb6d82485998329995d542a6..bc594ab5e1240ff004bdb9548afa872ac1bb62e8 100644 --- a/internal/tui/components/chat/messages/messages.go +++ b/internal/tui/components/chat/messages/messages.go @@ -101,11 +101,14 @@ func (m *messageCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case tea.KeyPressMsg: if key.Matches(msg, CopyKey) { - err := clipboard.WriteAll(m.message.Content().Text) - if err != nil { - return m, util.ReportError(fmt.Errorf("failed to copy message content to clipboard: %w", err)) - } - return m, util.ReportInfo("Message copied to clipboard") + return m, tea.Sequence( + tea.SetClipboard(m.message.Content().Text), + func() tea.Msg { + _ = clipboard.WriteAll(m.message.Content().Text) + return nil + }, + util.ReportInfo("Message copied to clipboard"), + ) } } return m, nil diff --git a/internal/tui/components/chat/messages/tool.go b/internal/tui/components/chat/messages/tool.go index 41bb7b81d0e9fa202e98bd91aca4f2eddf9c22c1..7e03674f97243e7d9e569b341fe1c6f1d2450b93 100644 --- a/internal/tui/components/chat/messages/tool.go +++ b/internal/tui/components/chat/messages/tool.go @@ -198,11 +198,14 @@ func (m *toolCallCmp) SetCancelled() { func (m *toolCallCmp) copyTool() tea.Cmd { content := m.formatToolForCopy() - err := clipboard.WriteAll(content) - if err != nil { - return util.ReportError(fmt.Errorf("failed to copy tool content to clipboard: %w", err)) - } - return util.ReportInfo("Tool content copied to clipboard") + return tea.Sequence( + tea.SetClipboard(content), + func() tea.Msg { + _ = clipboard.WriteAll(content) + return nil + }, + util.ReportInfo("Tool content copied to clipboard"), + ) } func (m *toolCallCmp) formatToolForCopy() string { From 3671bd0f7330fa4c0b0592f27b7bd5166ebc020a Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 5 Aug 2025 16:36:05 -0400 Subject: [PATCH 11/14] fix(list): include inbetween empty lines when selecting text --- internal/tui/exp/list/list.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/tui/exp/list/list.go b/internal/tui/exp/list/list.go index 8b729565f99d83accdebc0bc077963a7b25f8a34..4a9db7351ef561ec14414e4f70f1973f3d207064 100644 --- a/internal/tui/exp/list/list.go +++ b/internal/tui/exp/list/list.go @@ -389,6 +389,11 @@ func (l *list[T]) selectionView(view string, textOnly bool) string { textBounds := lineTextBounds[y] if textBounds.start < 0 { + if textOnly { + // We don't want to get rid of all empty lines in text-only mode + selectedText.WriteByte('\n') + } + continue // No text on this line } From 9ce53b18fc16c23c068669a6cf0ac30eb4cbfcd2 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 5 Aug 2025 22:20:56 -0400 Subject: [PATCH 12/14] chore(tui/status): remove parens around MCP detail (#580) --- internal/tui/components/mcp/mcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/tui/components/mcp/mcp.go b/internal/tui/components/mcp/mcp.go index 2376011ae1f18f44962d59f142652a52bfc47c3d..d11826b77749ba65276b5336a5d88cdbc8552881 100644 --- a/internal/tui/components/mcp/mcp.go +++ b/internal/tui/components/mcp/mcp.go @@ -68,7 +68,7 @@ func RenderMCPList(opts RenderOptions) []string { case agent.MCPStateConnected: icon = t.ItemOnlineIcon if state.ToolCount > 0 { - extraContent = t.S().Subtle.Render(fmt.Sprintf("(%d tools)", state.ToolCount)) + extraContent = t.S().Subtle.Render(fmt.Sprintf("%d tools", state.ToolCount)) } case agent.MCPStateError: icon = t.ItemErrorIcon From 31dc01ecfaeb94750fc78259000bb842263bbc79 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 5 Aug 2025 22:55:33 -0400 Subject: [PATCH 13/14] chore: rename default theme + infer default theme name (#570) * chore: rename default theme + infer default theme name --- internal/tui/styles/{crush.go => charmtone.go} | 4 ++-- internal/tui/styles/theme.go | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) rename internal/tui/styles/{crush.go => charmtone.go} (96%) diff --git a/internal/tui/styles/crush.go b/internal/tui/styles/charmtone.go similarity index 96% rename from internal/tui/styles/crush.go rename to internal/tui/styles/charmtone.go index f27632784ad64ed3228ee548c7c8fe84b58bc9ec..cf3d6a092e88fa7832e7eda57c6e10be328c075b 100644 --- a/internal/tui/styles/crush.go +++ b/internal/tui/styles/charmtone.go @@ -5,9 +5,9 @@ import ( "github.com/charmbracelet/x/exp/charmtone" ) -func NewCrushTheme() *Theme { +func NewCharmtoneTheme() *Theme { t := &Theme{ - Name: "crush", + Name: "charmtone", IsDark: true, Primary: charmtone.Charple, diff --git a/internal/tui/styles/theme.go b/internal/tui/styles/theme.go index e917cb2b6ffc1ff864012366e0711b66ccf1be83..706bd199491daaff525b13dd808a52dae5f359eb 100644 --- a/internal/tui/styles/theme.go +++ b/internal/tui/styles/theme.go @@ -491,26 +491,26 @@ func SetDefaultManager(m *Manager) { func DefaultManager() *Manager { if defaultManager == nil { - defaultManager = NewManager("crush") + defaultManager = NewManager() } return defaultManager } func CurrentTheme() *Theme { if defaultManager == nil { - defaultManager = NewManager("crush") + defaultManager = NewManager() } return defaultManager.Current() } -func NewManager(defaultTheme string) *Manager { +func NewManager() *Manager { m := &Manager{ themes: make(map[string]*Theme), } - m.Register(NewCrushTheme()) - - m.current = m.themes[defaultTheme] + t := NewCharmtoneTheme() // default theme + m.Register(t) + m.current = m.themes[t.Name] return m } From 567b778645113721822df45824a43b73281fe40e Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 5 Aug 2025 23:19:54 -0400 Subject: [PATCH 14/14] chore(list): set selection colors --- internal/tui/exp/list/list.go | 6 +++++- internal/tui/styles/charmtone.go | 3 +++ internal/tui/styles/theme.go | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/tui/exp/list/list.go b/internal/tui/exp/list/list.go index 4a9db7351ef561ec14414e4f70f1973f3d207064..d40f8d26128eef0d6930ff598c4aa21b971fd75e 100644 --- a/internal/tui/exp/list/list.go +++ b/internal/tui/exp/list/list.go @@ -415,8 +415,12 @@ func (l *list[T]) selectionView(view string, textOnly bool) string { continue } + // Text selection styling, which is a Lip Gloss style. We must + // extract the values to use in a UV style, below. + ts := t.TextSelection + cell = cell.Clone() - cell.Style = cell.Style.Background(t.BgOverlay).Foreground(t.White) + cell.Style = cell.Style.Background(ts.GetBackground()).Foreground(ts.GetForeground()) scr.SetCell(x, y, cell) } } diff --git a/internal/tui/styles/charmtone.go b/internal/tui/styles/charmtone.go index cf3d6a092e88fa7832e7eda57c6e10be328c075b..2e3783f522eac79cd1feb432fe0e399be0802882 100644 --- a/internal/tui/styles/charmtone.go +++ b/internal/tui/styles/charmtone.go @@ -56,6 +56,9 @@ func NewCharmtoneTheme() *Theme { Cherry: charmtone.Cherry, } + // Text selection. + t.TextSelection = lipgloss.NewStyle().Foreground(charmtone.Salt).Background(charmtone.Charple) + // LSP and MCP status. t.ItemOfflineIcon = lipgloss.NewStyle().Foreground(charmtone.Squid).SetString("●") t.ItemBusyIcon = t.ItemOfflineIcon.Foreground(charmtone.Citron) diff --git a/internal/tui/styles/theme.go b/internal/tui/styles/theme.go index 706bd199491daaff525b13dd808a52dae5f359eb..0503539ba720188a0894d26abd299b54d602494e 100644 --- a/internal/tui/styles/theme.go +++ b/internal/tui/styles/theme.go @@ -74,6 +74,9 @@ type Theme struct { RedLight color.Color Cherry color.Color + // Text selection. + TextSelection lipgloss.Style + // LSP and MCP status indicators. ItemOfflineIcon lipgloss.Style ItemBusyIcon lipgloss.Style