chore(deps): bump to charm.land v2 dependencies (#161)

Co-authored-by: bjarneo <bjarneo@users.noreply.github.com>
This commit is contained in:
Gjermund Garaba
2026-04-02 15:49:51 +02:00
committed by GitHub
parent 32cb03c687
commit b6deffe92b
31 changed files with 259 additions and 241 deletions
+14 -15
View File
@@ -3,8 +3,8 @@ module cliamp
go 1.25.5
require (
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
charm.land/bubbletea/v2 v2.0.2
charm.land/lipgloss/v2 v2.0.2
github.com/devgianlu/go-librespot v0.7.1
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8
github.com/godbus/dbus/v5 v5.2.2
@@ -23,14 +23,17 @@ require (
cloud.google.com/go/auth v0.18.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bitly/go-simplejson v0.5.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/coder/websocket v1.8.14 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/devgianlu/shannon v0.0.0-20230613115856-82ec90b7fa7e // indirect
@@ -38,7 +41,6 @@ require (
github.com/dop251/goja v0.0.0-20250125213203-5ef83b82af17 // indirect
github.com/ebitengine/oto/v3 v3.3.2 // indirect
github.com/ebitengine/purego v0.8.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@@ -52,15 +54,11 @@ require (
github.com/icza/bitio v1.1.0 // indirect
github.com/jfreymuth/pulse v0.1.2-0.20241102120944-4ffb35054b53 // indirect
github.com/jfreymuth/vorbis v1.0.2 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mewkiz/flac v1.0.12 // indirect
github.com/mewkiz/pkg v0.0.0-20230226050401-4010bf0fec14 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
@@ -77,7 +75,8 @@ require (
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.42.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
google.golang.org/grpc v1.79.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
+30 -33
View File
@@ -1,3 +1,7 @@
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
charm.land/lipgloss/v2 v2.0.2 h1:xFolbF8JdpNkM2cEPTfXEcW1p6NRzOWTSamRfYEw8cs=
charm.land/lipgloss/v2 v2.0.2/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
@@ -6,26 +10,32 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
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/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
@@ -47,8 +57,6 @@ github.com/ebitengine/oto/v3 v3.3.2 h1:VTWBsKX9eb+dXzaF4jEwQbs4yWIdXukJ0K40KgkpY
github.com/ebitengine/oto/v3 v3.3.2/go.mod h1:MZeb/lwoC4DCOdiTIxYezrURTw7EvK/yF863+tmBI+U=
github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE=
github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -96,26 +104,18 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/madelynnblue/go-dsp v1.0.0 h1:ufzvSGl8IdjCA6BFVUx1cZW/aDiiXxDBWU1MpkrtAiM=
github.com/madelynnblue/go-dsp v1.0.0/go.mod h1:dpf07Rj/u3te6cW3KwRBAqlyjP4InXHhNaYVuY73hHU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mewkiz/flac v1.0.12 h1:5Y1BRlUebfiVXPmz7hDD7h3ceV2XNrGNMejNVjDpgPY=
github.com/mewkiz/flac v1.0.12/go.mod h1:1UeXlFRJp4ft2mfZnPLRpQTd7cSjb/s17o7JQzzyrCA=
github.com/mewkiz/pkg v0.0.0-20230226050401-4010bf0fec14 h1:tnAPMExbRERsyEYkmR1YjhTgDM0iqyiBYf8ojRXxdbA=
github.com/mewkiz/pkg v0.0.0-20230226050401-4010bf0fec14/go.mod h1:QYCFBiH5q6XTHEbWhR0uhR3M9qNPoD2CSQzr0g75kE4=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
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/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw=
github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -124,7 +124,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@@ -185,15 +184,13 @@ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+2 -3
View File
@@ -7,7 +7,7 @@ import (
"strings"
"time"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/config"
"cliamp/external/jellyfin"
@@ -270,8 +270,7 @@ func run(overrides config.Overrides, positional []string) error {
}
}
prog := tea.NewProgram(m, tea.WithAltScreen())
prog.SetWindowTitle(model.InitialTerminalTitle())
prog := tea.NewProgram(m)
if luaMgr != nil {
luaMgr.SetControlProvider(luaplugin.ControlProvider{
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context"
"time"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/lyrics"
"cliamp/player"
+9 -9
View File
@@ -6,12 +6,12 @@ import (
"path/filepath"
"strings"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/player"
"cliamp/ui"
"cliamp/playlist"
"cliamp/resolve"
"cliamp/ui"
)
// fbMaxVisible is a number of visible entries in file browser.
@@ -120,7 +120,7 @@ func (m *Model) loadFBDir() {
}
// handleFileBrowserKey processes key presses while the file browser is open.
func (m *Model) handleFileBrowserKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleFileBrowserKey(msg tea.KeyPressMsg) tea.Cmd {
var cd string
switch msg.String() {
case "ctrl+c":
@@ -134,7 +134,7 @@ func (m *Model) handleFileBrowserKey(msg tea.KeyMsg) tea.Cmd {
if m.fileBrowser.cursor > 0 {
m.fileBrowser.cursor--
} else if len(m.fileBrowser.entries) > 0 {
m.fileBrowser.cursor = len(m.fileBrowser.entries)-1
m.fileBrowser.cursor = len(m.fileBrowser.entries) - 1
}
case "down", "j":
@@ -151,7 +151,7 @@ func (m *Model) handleFileBrowserKey(msg tea.KeyMsg) tea.Cmd {
case "pgdown", "ctrl+d":
if m.fileBrowser.cursor < len(m.fileBrowser.entries)-1 {
m.fileBrowser.cursor = min(len(m.fileBrowser.entries)-1, m.fileBrowser.cursor + fbMaxVisible)
m.fileBrowser.cursor = min(len(m.fileBrowser.entries)-1, m.fileBrowser.cursor+fbMaxVisible)
}
case "enter", "l", "right":
@@ -203,7 +203,7 @@ func (m *Model) handleFileBrowserKey(msg tea.KeyMsg) tea.Cmd {
m.loadFBDir()
}
case " ":
case "space":
if m.fileBrowser.cursor < len(m.fileBrowser.entries) {
e := m.fileBrowser.entries[m.fileBrowser.cursor]
if !e.isParent && (e.isAudio || e.isDir) {
@@ -279,9 +279,9 @@ func (m *Model) fbConfirm(replace bool) tea.Cmd {
// renderFileBrowser renders the file browser overlay.
func (m Model) renderFileBrowser() string {
lines := append(make([]string, 0, 3 + fbMaxVisible + 4),
lines := append(make([]string, 0, 3+fbMaxVisible+4),
titleStyle.Render("O P E N F I L E S"),
dimStyle.Render(" " + m.fileBrowser.dir),
dimStyle.Render(" "+m.fileBrowser.dir),
"",
)
@@ -353,7 +353,7 @@ func (m Model) renderFileBrowser() string {
}
}
help := helpKey("↑↓", "Scroll ") + helpKey("Enter", "Open ") +
help := helpKey("↑↓", "Scroll ") + helpKey("Enter", "Open ") +
helpKey("Spc", "Select ") + helpKey("a", "All ") +
helpKey("←", "Back ") + helpKey("~.", "Home/Cwd ")
if os.PathSeparator == '\\' {
+2 -5
View File
@@ -4,7 +4,7 @@ import (
"strings"
"time"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/luaplugin"
"cliamp/player"
@@ -160,10 +160,7 @@ func (m Model) Init() tea.Cmd {
if m.luaMgr != nil {
m.luaMgr.Emit(luaplugin.EventAppStart, nil)
}
cmds := []tea.Cmd{tickCmd(), tea.WindowSize()}
if cmd := m.terminalTitleCmd(); cmd != nil {
cmds = append(cmds, cmd)
}
cmds := []tea.Cmd{tickCmd(), func() tea.Msg { return tea.RequestWindowSize() }}
if m.provider != nil {
cmds = append(cmds, fetchPlaylistsCmd(m.provider))
}
+6 -6
View File
@@ -3,7 +3,7 @@ package model
import (
"strings"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
)
// keymapEntry is a key-action pair for the keymap overlay.
@@ -62,8 +62,8 @@ var keymapEntries = []keymapEntry{
}
// handleKeymapKey processes key presses while the keymap overlay is open.
func (m *Model) handleKeymapKey(msg tea.KeyMsg) tea.Cmd {
switch msg.Type {
func (m *Model) handleKeymapKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.Code {
case tea.KeyEscape:
m.keymap.visible = false
m.keymap.search = ""
@@ -78,7 +78,7 @@ func (m *Model) handleKeymapKey(msg tea.KeyMsg) tea.Cmd {
count = len(m.keymap.filtered)
}
if count > 0 {
m.keymap.cursor = count-1
m.keymap.cursor = count - 1
}
}
case tea.KeyDown:
@@ -105,8 +105,8 @@ func (m *Model) handleKeymapKey(msg tea.KeyMsg) tea.Cmd {
m.keymap.visible = false
return m.quit()
default:
if msg.Type == tea.KeyRunes {
m.keymap.search += string(msg.Runes)
if len(msg.Text) > 0 {
m.keymap.search += msg.Text
m.updateKeymapFilter()
}
}
+45 -43
View File
@@ -8,14 +8,14 @@ import (
"strings"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"cliamp/config"
"cliamp/ui"
"cliamp/internal/fileutil"
"cliamp/playlist"
"cliamp/provider"
"cliamp/ui"
)
// quit shuts down the player and signals the TUI to exit.
@@ -47,7 +47,7 @@ func (m *Model) scrobbleCurrent() {
}
}
func (m *Model) handleSpeedKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleSpeedKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() {
case "q", "ctrl+c":
return m.quit()
@@ -63,14 +63,14 @@ func (m *Model) handleSpeedKey(msg tea.KeyMsg) tea.Cmd {
}
case "esc", "backspace":
m.focus = focusEQ
case " ":
case "space":
return m.togglePlayPause()
}
return nil
}
// handleKey processes a single key press and returns an optional command.
func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleKey(msg tea.KeyPressMsg) tea.Cmd {
if m.keymap.visible {
return m.handleKeymapKey(msg)
}
@@ -175,7 +175,7 @@ func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd {
} else if len(m.providerLists) > 0 {
m.provCursor = len(m.providerLists) - 1
}
case " ":
case "space":
return m.togglePlayPause()
case "down", "j":
if m.provCursor < len(m.providerLists)-1 {
@@ -269,7 +269,7 @@ func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd {
m.focus = focusPlaylist
case "esc", "backspace":
m.focus = focusSpeed
case " ":
case "space":
return m.togglePlayPause()
}
return nil
@@ -287,7 +287,7 @@ func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd {
m.focus = focusProvider
}
case " ":
case "space":
cmd := m.togglePlayPause()
m.notifyMPRIS()
return cmd
@@ -608,8 +608,10 @@ func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd {
m.fullVis = !m.fullVis
if m.fullVis {
m.vis.Rows = max(ui.DefaultVisRows, (m.height-10)*4/5)
ui.PanelWidth = max(0, m.width-2*ui.PaddingH)
} else {
m.vis.Rows = ui.DefaultVisRows
ui.PanelWidth = max(0, min(m.width, 80)-2*ui.PaddingH)
}
case "x":
@@ -712,14 +714,14 @@ func (m *Model) closeJumpMode() {
}
// handleJumpKey processes key presses while in jump-time mode.
func (m *Model) handleJumpKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleJumpKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() {
case "ctrl+c":
m.closeJumpMode()
return m.quit()
}
switch msg.Type {
switch msg.Code {
case tea.KeyEscape:
m.closeJumpMode()
return nil
@@ -745,8 +747,8 @@ func (m *Model) handleJumpKey(msg tea.KeyMsg) tea.Cmd {
return nil
}
if msg.Type == tea.KeyRunes {
m.jumpInput += string(msg.Runes)
if len(msg.Text) > 0 {
m.jumpInput += msg.Text
}
return nil
}
@@ -754,12 +756,12 @@ func (m *Model) handleJumpKey(msg tea.KeyMsg) tea.Cmd {
// handleProvSearchKey processes key presses while filtering the provider playlist list.
// For the radio provider, Enter fires an API search; for others, Enter loads the
// selected result. Esc cancels and restores the normal catalog view.
func (m *Model) handleProvSearchKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleProvSearchKey(msg tea.KeyPressMsg) tea.Cmd {
// Catalog search: API-based search (no live client-side filtering).
if cs, ok := m.provider.(provider.CatalogSearcher); ok {
return m.handleCatalogSearchKey(msg, cs)
}
switch msg.Type {
switch msg.Code {
case tea.KeyEscape:
m.provSearch.active = false
case tea.KeyEnter:
@@ -787,8 +789,8 @@ func (m *Model) handleProvSearchKey(msg tea.KeyMsg) tea.Cmd {
m.provSearch.query += " "
m.updateProvSearch()
default:
if msg.Type == tea.KeyRunes {
m.provSearch.query += string(msg.Runes)
if len(msg.Text) > 0 {
m.provSearch.query += msg.Text
m.updateProvSearch()
}
}
@@ -797,8 +799,8 @@ func (m *Model) handleProvSearchKey(msg tea.KeyMsg) tea.Cmd {
// handleCatalogSearchKey handles search input for providers with catalog search.
// Types a query, Enter fires API search, Esc cancels/clears.
func (m *Model) handleCatalogSearchKey(msg tea.KeyMsg, cs provider.CatalogSearcher) tea.Cmd {
switch msg.Type {
func (m *Model) handleCatalogSearchKey(msg tea.KeyPressMsg, cs provider.CatalogSearcher) tea.Cmd {
switch msg.Code {
case tea.KeyEscape:
m.provSearch.active = false
m.restoreCatalog(cs)
@@ -817,8 +819,8 @@ func (m *Model) handleCatalogSearchKey(msg tea.KeyMsg, cs provider.CatalogSearch
case tea.KeySpace:
m.provSearch.query += " "
default:
if msg.Type == tea.KeyRunes {
m.provSearch.query += string(msg.Runes)
if len(msg.Text) > 0 {
m.provSearch.query += msg.Text
}
}
return nil
@@ -868,7 +870,7 @@ func (m *Model) toggleExpandPlaylist() {
m.adjustScroll()
}
func (m *Model) handleSearchKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleSearchKey(msg tea.KeyPressMsg) tea.Cmd {
// Allow opening overlays during search (ctrl combos don't conflict with text input).
switch msg.String() {
case "ctrl+k":
@@ -876,7 +878,7 @@ func (m *Model) handleSearchKey(msg tea.KeyMsg) tea.Cmd {
return nil
}
switch msg.Type {
switch msg.Code {
case tea.KeyEscape:
m.search.active = false
m.focus = m.prevFocus
@@ -925,8 +927,8 @@ func (m *Model) handleSearchKey(msg tea.KeyMsg) tea.Cmd {
m.updateSearch()
default:
if msg.Type == tea.KeyRunes {
m.search.query += string(msg.Runes)
if len(msg.Text) > 0 {
m.search.query += msg.Text
m.updateSearch()
}
}
@@ -935,14 +937,14 @@ func (m *Model) handleSearchKey(msg tea.KeyMsg) tea.Cmd {
}
// handleNetSearchKey processes key presses while in net search mode.
func (m *Model) handleNetSearchKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleNetSearchKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() {
case "ctrl+k":
m.keymap.visible = true
return nil
}
switch msg.Type {
switch msg.Code {
case tea.KeyEscape:
m.netSearch.active = false
m.focus = m.prevFocus
@@ -968,8 +970,8 @@ func (m *Model) handleNetSearchKey(msg tea.KeyMsg) tea.Cmd {
m.netSearch.query += " "
default:
if msg.Type == tea.KeyRunes {
m.netSearch.query += string(msg.Runes)
if len(msg.Text) > 0 {
m.netSearch.query += msg.Text
}
}
@@ -977,8 +979,8 @@ func (m *Model) handleNetSearchKey(msg tea.KeyMsg) tea.Cmd {
}
// handleURLInputKey processes key presses while in URL input mode.
func (m *Model) handleURLInputKey(msg tea.KeyMsg) tea.Cmd {
switch msg.Type {
func (m *Model) handleURLInputKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.Code {
case tea.KeyEscape:
m.urlInputting = false
case tea.KeyEnter:
@@ -992,15 +994,15 @@ func (m *Model) handleURLInputKey(msg tea.KeyMsg) tea.Cmd {
case tea.KeyBackspace:
m.urlInput = removeLastRune(m.urlInput)
default:
if msg.Type == tea.KeyRunes {
m.urlInput += string(msg.Runes)
if len(msg.Text) > 0 {
m.urlInput += msg.Text
}
}
return nil
}
// handlePlaylistManagerKey dispatches keys to the active manager screen.
func (m *Model) handlePlaylistManagerKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handlePlaylistManagerKey(msg tea.KeyPressMsg) tea.Cmd {
switch m.plManager.screen {
case plMgrScreenList:
return m.handlePlMgrListKey(msg)
@@ -1013,7 +1015,7 @@ func (m *Model) handlePlaylistManagerKey(msg tea.KeyMsg) tea.Cmd {
}
// handlePlMgrListKey handles keys on screen 0 (playlist list).
func (m *Model) handlePlMgrListKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handlePlMgrListKey(msg tea.KeyPressMsg) tea.Cmd {
// If waiting for delete confirmation, only accept y/n.
if m.plManager.confirmDel {
switch msg.String() {
@@ -1078,7 +1080,7 @@ func (m *Model) handlePlMgrListKey(msg tea.KeyMsg) tea.Cmd {
}
// handlePlMgrTracksKey handles keys on screen 1 (track list inside a playlist).
func (m *Model) handlePlMgrTracksKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handlePlMgrTracksKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() {
case "ctrl+c":
m.plManager.visible = false
@@ -1157,8 +1159,8 @@ func (m *Model) handlePlMgrTracksKey(msg tea.KeyMsg) tea.Cmd {
}
// handlePlMgrNewNameKey handles keys on screen 2 (new playlist name input).
func (m *Model) handlePlMgrNewNameKey(msg tea.KeyMsg) tea.Cmd {
switch msg.Type {
func (m *Model) handlePlMgrNewNameKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.Code {
case tea.KeyEscape:
m.plManager.screen = plMgrScreenList
case tea.KeyEnter:
@@ -1173,8 +1175,8 @@ func (m *Model) handlePlMgrNewNameKey(msg tea.KeyMsg) tea.Cmd {
case tea.KeySpace:
m.plManager.newName += " "
default:
if msg.Type == tea.KeyRunes {
m.plManager.newName += string(msg.Runes)
if len(msg.Text) > 0 {
m.plManager.newName += msg.Text
}
}
return nil
@@ -1203,7 +1205,7 @@ func (m *Model) addToPlaylist(name string) {
}
// handleThemeKey processes key presses while the theme picker is open.
func (m *Model) handleThemeKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleThemeKey(msg tea.KeyPressMsg) tea.Cmd {
count := len(m.themes) + 1 // +1 for Default
switch msg.String() {
case "ctrl+c":
@@ -1234,7 +1236,7 @@ func (m *Model) handleThemeKey(msg tea.KeyMsg) tea.Cmd {
}
// handleQueueKey processes key presses while the queue manager overlay is open.
func (m *Model) handleQueueKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleQueueKey(msg tea.KeyPressMsg) tea.Cmd {
qLen := m.playlist.QueueLen()
switch msg.String() {
@@ -1284,7 +1286,7 @@ func (m *Model) handleQueueKey(msg tea.KeyMsg) tea.Cmd {
}
// handleDeviceKey processes key presses while the audio device picker is open.
func (m *Model) handleDeviceKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleDeviceKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() {
case "ctrl+c":
m.devicePicker.visible = false
+13 -15
View File
@@ -1,14 +1,14 @@
package model
import (
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/playlist"
"cliamp/provider"
)
// handleNavBrowserKey processes key presses while the provider browser is open.
func (m *Model) handleNavBrowserKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleNavBrowserKey(msg tea.KeyPressMsg) tea.Cmd {
if m.navBrowser.prov == nil {
m.navBrowser.visible = false
return nil
@@ -43,7 +43,7 @@ func (m *Model) handleNavBrowserKey(msg tea.KeyMsg) tea.Cmd {
return nil
}
func (m *Model) handleNavMenuKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleNavMenuKey(msg tea.KeyPressMsg) tea.Cmd {
const menuItems = 3
switch msg.String() {
case "ctrl+c":
@@ -104,7 +104,7 @@ func (m *Model) handleNavMenuKey(msg tea.KeyMsg) tea.Cmd {
return nil
}
func (m *Model) handleNavByAlbumKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleNavByAlbumKey(msg tea.KeyPressMsg) tea.Cmd {
switch m.navBrowser.screen {
case navBrowseScreenList:
return m.handleNavAlbumListKey(msg, false)
@@ -114,7 +114,7 @@ func (m *Model) handleNavByAlbumKey(msg tea.KeyMsg) tea.Cmd {
return nil
}
func (m *Model) handleNavByArtistKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleNavByArtistKey(msg tea.KeyPressMsg) tea.Cmd {
switch m.navBrowser.screen {
case navBrowseScreenList:
return m.handleNavArtistListKey(msg)
@@ -124,7 +124,7 @@ func (m *Model) handleNavByArtistKey(msg tea.KeyMsg) tea.Cmd {
return nil
}
func (m *Model) handleNavByArtistAlbumKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleNavByArtistAlbumKey(msg tea.KeyPressMsg) tea.Cmd {
switch m.navBrowser.screen {
case navBrowseScreenList:
return m.handleNavArtistListKey(msg)
@@ -137,7 +137,7 @@ func (m *Model) handleNavByArtistAlbumKey(msg tea.KeyMsg) tea.Cmd {
}
// handleNavArtistListKey handles the artist list screen.
func (m *Model) handleNavArtistListKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleNavArtistListKey(msg tea.KeyPressMsg) tea.Cmd {
// Determine effective list length (filtered or full).
listLen := len(m.navBrowser.artists)
if len(m.navBrowser.searchIdx) > 0 {
@@ -197,7 +197,7 @@ func (m *Model) handleNavArtistListKey(msg tea.KeyMsg) tea.Cmd {
// handleNavAlbumListKey handles the album list screen.
// artistAlbums=true means this is the artist's album sub-screen (ArtistAlbum mode), not the global list.
func (m *Model) handleNavAlbumListKey(msg tea.KeyMsg, artistAlbums bool) tea.Cmd {
func (m *Model) handleNavAlbumListKey(msg tea.KeyPressMsg, artistAlbums bool) tea.Cmd {
// Determine effective list length (filtered or full).
listLen := len(m.navBrowser.albums)
if len(m.navBrowser.searchIdx) > 0 {
@@ -278,7 +278,7 @@ func (m *Model) handleNavAlbumListKey(msg tea.KeyMsg, artistAlbums bool) tea.Cmd
}
// handleNavTrackListKey handles the final track-list screen (used by all modes).
func (m *Model) handleNavTrackListKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleNavTrackListKey(msg tea.KeyPressMsg) tea.Cmd {
// Determine effective list length (filtered or full).
listLen := len(m.navBrowser.tracks)
if len(m.navBrowser.searchIdx) > 0 {
@@ -427,10 +427,9 @@ func (m *Model) handleNavTrackListKey(msg tea.KeyMsg) tea.Cmd {
}
// handleNavSearchKey handles key input while the nav search bar is open.
func (m *Model) handleNavSearchKey(msg tea.KeyMsg) tea.Cmd {
switch msg.Type {
func (m *Model) handleNavSearchKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.Code {
case tea.KeyEscape:
// Close the search bar; keep the filter active so the user can act on results.
m.navBrowser.searching = false
return nil
case tea.KeyEnter:
@@ -445,9 +444,8 @@ func (m *Model) handleNavSearchKey(msg tea.KeyMsg) tea.Cmd {
}
return nil
}
// Printable character — append to query.
if msg.Type == tea.KeyRunes {
m.navBrowser.search += string(msg.Runes)
if len(msg.Text) > 0 {
m.navBrowser.search += msg.Text
m.navBrowser.cursor = 0
m.navBrowser.scroll = 0
m.navUpdateSearch()
+1 -1
View File
@@ -1,7 +1,7 @@
package model
import (
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/provider"
)
+13 -13
View File
@@ -1,13 +1,13 @@
package model
import (
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/provider"
)
// handleSpotSearchKey dispatches key presses to the active provider search screen.
func (m *Model) handleSpotSearchKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleSpotSearchKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() {
case "ctrl+c":
m.spotSearch.visible = false
@@ -28,8 +28,8 @@ func (m *Model) handleSpotSearchKey(msg tea.KeyMsg) tea.Cmd {
}
// handleSpotSearchInputKey handles text input for the search query.
func (m *Model) handleSpotSearchInputKey(msg tea.KeyMsg) tea.Cmd {
switch msg.Type {
func (m *Model) handleSpotSearchInputKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.Code {
case tea.KeyEscape:
m.spotSearch.visible = false
case tea.KeyEnter:
@@ -49,15 +49,15 @@ func (m *Model) handleSpotSearchInputKey(msg tea.KeyMsg) tea.Cmd {
case tea.KeySpace:
m.spotSearch.query += " "
default:
if msg.Type == tea.KeyRunes {
m.spotSearch.query += string(msg.Runes)
if len(msg.Text) > 0 {
m.spotSearch.query += msg.Text
}
}
return nil
}
// handleSpotSearchResultsKey handles navigation through search results.
func (m *Model) handleSpotSearchResultsKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleSpotSearchResultsKey(msg tea.KeyPressMsg) tea.Cmd {
count := len(m.spotSearch.results)
switch msg.String() {
@@ -88,7 +88,7 @@ func (m *Model) handleSpotSearchResultsKey(msg tea.KeyMsg) tea.Cmd {
}
// handleSpotSearchPlaylistKey handles picking a playlist to add to.
func (m *Model) handleSpotSearchPlaylistKey(msg tea.KeyMsg) tea.Cmd {
func (m *Model) handleSpotSearchPlaylistKey(msg tea.KeyPressMsg) tea.Cmd {
count := len(m.spotSearch.playlists) + 1 // +1 for "+ New Playlist..."
switch msg.String() {
@@ -136,11 +136,11 @@ func (m *Model) handleSpotSearchPlaylistKey(msg tea.KeyMsg) tea.Cmd {
}
// handleSpotSearchNewNameKey handles text input for new playlist name.
func (m *Model) handleSpotSearchNewNameKey(msg tea.KeyMsg) tea.Cmd {
switch msg.Type {
func (m *Model) handleSpotSearchNewNameKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.Code {
case tea.KeyEscape:
m.spotSearch.screen = spotSearchPlaylist
m.spotSearch.cursor = len(m.spotSearch.playlists) // back on "+ New Playlist..."
m.spotSearch.cursor = len(m.spotSearch.playlists)
case tea.KeyEnter:
if m.spotSearch.newName != "" && !m.spotSearch.loading {
c, cOk := m.spotSearch.prov.(provider.PlaylistCreator)
@@ -159,8 +159,8 @@ func (m *Model) handleSpotSearchNewNameKey(msg tea.KeyMsg) tea.Cmd {
case tea.KeySpace:
m.spotSearch.newName += " "
default:
if msg.Type == tea.KeyRunes {
m.spotSearch.newName += string(msg.Runes)
if len(msg.Text) > 0 {
m.spotSearch.newName += msg.Text
}
}
return nil
+9 -9
View File
@@ -127,14 +127,14 @@ type Model struct {
// Provider state
provider playlist.Provider
localProvider playlist.Provider // local playlist provider for file-based playlist management (always available)
providerLists []playlist.PlaylistInfo
provCursor int
provLoading bool
provSignIn bool // true when provider needs interactive sign-in
providers []ProviderEntry // all available providers
provPillIdx int // selected pill index
eqPresetIdx int // -1 = custom, 0+ = index into eqPresets
eqCustomLabel string // non-empty = plugin-defined preset label (shown instead of "Custom")
providerLists []playlist.PlaylistInfo
provCursor int
provLoading bool
provSignIn bool // true when provider needs interactive sign-in
providers []ProviderEntry // all available providers
provPillIdx int // selected pill index
eqPresetIdx int // -1 = custom, 0+ = index into eqPresets
eqCustomLabel string // non-empty = plugin-defined preset label (shown instead of "Custom")
// Overlay / feature state (see state.go for struct definitions)
search searchState
@@ -149,7 +149,7 @@ type Model struct {
spotSearch spotSearchState
fileBrowser fileBrowserState
navBrowser navBrowserState
catalogBatch catalogBatchState
catalogBatch catalogBatchState
ytdlBatch ytdlBatchState
reconnect reconnectState
save saveState
+1 -1
View File
@@ -3,7 +3,7 @@ package model
import (
"time"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/playlist"
)
+1 -1
View File
@@ -3,7 +3,7 @@ package model
import (
"time"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/playlist"
)
+1 -1
View File
@@ -3,7 +3,7 @@ package model
import (
"strings"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/playlist"
"cliamp/provider"
+1 -1
View File
@@ -3,7 +3,7 @@ package model
import (
"strings"
"github.com/charmbracelet/lipgloss"
"charm.land/lipgloss/v2"
"cliamp/playlist"
"cliamp/ui"
+1 -1
View File
@@ -3,7 +3,7 @@ package model
import (
"time"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/ui"
)
+3 -4
View File
@@ -1,14 +1,13 @@
package model
import (
"cliamp/ui"
"os"
"path/filepath"
"strings"
"testing"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
)
func TestHandleSpeedKeyUsesArrowKeysWhenSpeedFocused(t *testing.T) {
@@ -28,7 +27,7 @@ func TestHandleSpeedKeyUsesArrowKeysWhenSpeedFocused(t *testing.T) {
focus: focusSpeed,
}
if cmd := m.handleKey(tea.KeyMsg{Type: tea.KeyRight}); cmd != nil {
if cmd := m.handleKey(tea.KeyPressMsg{Code: tea.KeyRight}); cmd != nil {
t.Fatalf("handleKey(right) cmd = %v, want nil", cmd)
}
if got := sharedPlayer.Speed(); got != 1.25 {
@@ -38,7 +37,7 @@ func TestHandleSpeedKeyUsesArrowKeysWhenSpeedFocused(t *testing.T) {
t.Fatalf("speedSaveAfter after right = %v, want %v", got, speedSaveDebounce)
}
if cmd := m.handleKey(tea.KeyMsg{Type: tea.KeyLeft}); cmd != nil {
if cmd := m.handleKey(tea.KeyPressMsg{Code: tea.KeyLeft}); cmd != nil {
t.Fatalf("handleKey(left) cmd = %v, want nil", cmd)
}
if got := sharedPlayer.Speed(); got != 1.0 {
-1
View File
@@ -246,5 +246,4 @@ type terminalTitleState struct {
introActive bool
introOffset int
introTick int
last string
}
+2 -2
View File
@@ -1,7 +1,7 @@
package model
import (
"github.com/charmbracelet/lipgloss"
"charm.land/lipgloss/v2"
"cliamp/ui"
)
@@ -34,7 +34,7 @@ var (
Bold(true)
eqInactiveStyle = lipgloss.NewStyle().
Foreground(ui.ColorDim)
Foreground(ui.ColorDim)
playlistActiveStyle = lipgloss.NewStyle().
Foreground(ui.ColorPlaying).
+1 -1
View File
@@ -3,7 +3,7 @@ package model
import (
"time"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/ui"
)
+4 -4
View File
@@ -9,7 +9,7 @@ import (
"cliamp/playlist"
"cliamp/ui"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
)
var sharedPlayer *player.Player
@@ -38,7 +38,7 @@ func TestTickIntervalStoppedUsesSlow(t *testing.T) {
player: sharedPlayer,
vis: ui.NewVisualizer(float64(sharedPlayer.SampleRate())),
playlist: playlist.New(),
termTitle: terminalTitleState{last: baseTerminalTitle},
termTitle: terminalTitleState{},
}
// Player is stopped by default (IsPlaying=false).
@@ -153,7 +153,7 @@ func TestUpdateRequestsVisualizerRefreshWhenOverlayCloses(t *testing.T) {
},
}
nextModel, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEscape})
nextModel, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEscape})
if cmd != nil {
t.Fatalf("Update() cmd = %v, want nil", cmd)
}
@@ -178,7 +178,7 @@ func TestUpdateRequestsVisualizerRefreshWhenLyricsClose(t *testing.T) {
},
}
nextModel, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEscape})
nextModel, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEscape})
if cmd != nil {
t.Fatalf("Update() cmd = %v, want nil", cmd)
}
-15
View File
@@ -4,8 +4,6 @@ import (
"strings"
"unicode"
tea "github.com/charmbracelet/bubbletea"
"cliamp/playlist"
)
@@ -29,10 +27,6 @@ type terminalTitleValues struct {
var defaultTerminalTitleIntroRunes = []rune(defaultTerminalTitleIntro)
func InitialTerminalTitle() string {
return sanitizeTerminalTitle(titleIntroFrame(titleIntroInitialOffset(titleIntroViewportDefault), titleIntroViewportDefault, defaultTerminalTitleIntroRunes))
}
func initialTerminalTitleState() terminalTitleState {
if len(defaultTerminalTitleIntroRunes) == 0 {
return terminalTitleState{}
@@ -141,15 +135,6 @@ func sanitizeTerminalTitle(title string) string {
return b.String()
}
func (m *Model) terminalTitleCmd() tea.Cmd {
title := currentTerminalTitle(m.termTitle, m.width, m.terminalTitleValues())
if title == m.termTitle.last {
return nil
}
m.termTitle.last = title
return tea.SetWindowTitle(title)
}
func (m *Model) advanceTerminalTitle() {
advanceTerminalTitleState(&m.termTitle, m.width)
}
-8
View File
@@ -116,14 +116,6 @@ func TestTerminalTitleIntroSequence(t *testing.T) {
}
}
func TestInitialTerminalTitle(t *testing.T) {
got := InitialTerminalTitle()
want := strings.Repeat(" ", titleIntroViewportDefault-4) + "It r"
if got != want {
t.Fatalf("InitialTerminalTitle() = %q, want %q", got, want)
}
}
func TestCurrentTerminalTitleSanitizesRenderedTitle(t *testing.T) {
tests := []struct {
name string
+4 -6
View File
@@ -6,7 +6,7 @@ import (
"strings"
"time"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
"cliamp/config"
"cliamp/ipc"
@@ -35,7 +35,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}()
switch msg := msg.(type) {
case tea.KeyMsg:
case tea.KeyPressMsg:
cmd := m.handleKey(msg)
if m.quitting {
return m, tea.Quit
@@ -62,9 +62,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
ui.PanelWidth = max(0, frameW-2*ui.PaddingH)
if m.fullVis {
m.vis.Rows = max(ui.DefaultVisRows, (m.height-10)*4/5)
ui.PanelWidth = max(0, m.width-2*ui.PaddingH)
}
m.plVisible = m.defaultPlVisible()
return m, m.terminalTitleCmd()
return m, nil
case seekTickMsg:
// Async yt-dlp seek completed.
@@ -254,9 +255,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
m.advanceTerminalTitle()
if cmd := m.terminalTitleCmd(); cmd != nil {
cmds = append(cmds, cmd)
}
cmds = append(cmds, tickCmdAt(m.tickInterval()))
return m, tea.Batch(cmds...)
+32 -23
View File
@@ -7,12 +7,13 @@ import (
"time"
"unicode/utf8"
"github.com/charmbracelet/lipgloss"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"cliamp/playlist"
"cliamp/ui"
"cliamp/provider"
"cliamp/theme"
"cliamp/ui"
)
// titleScrollSep is the separator runes for cyclic title scrolling,
@@ -38,9 +39,9 @@ func playlistLabel(prefix string, p playlist.PlaylistInfo) string {
}
// View renders the full TUI frame.
func (m Model) View() string {
func (m Model) View() tea.View {
if m.quitting {
return ""
return tea.NewView("")
}
screen := m.activeScreen()
@@ -48,43 +49,51 @@ func (m Model) View() string {
m.refreshVisualizerIfPending()
}
var content string
switch screen {
case screenKeymap:
return m.renderKeymapOverlay()
content = m.renderKeymapOverlay()
case screenThemePicker:
return m.renderThemePicker()
content = m.renderThemePicker()
case screenDevicePicker:
return m.renderDeviceOverlay()
content = m.renderDeviceOverlay()
case screenFileBrowser:
return m.renderFileBrowser()
content = m.renderFileBrowser()
case screenNavBrowser:
return m.renderNavBrowser()
content = m.renderNavBrowser()
case screenPlaylistManager:
return m.renderPlaylistManager()
content = m.renderPlaylistManager()
case screenSpotSearch:
return m.renderSpotSearch()
content = m.renderSpotSearch()
case screenQueue:
return m.renderQueueOverlay()
content = m.renderQueueOverlay()
case screenInfo:
return m.renderInfoOverlay()
content = m.renderInfoOverlay()
case screenSearch:
return m.renderSearchOverlay()
content = m.renderSearchOverlay()
case screenNetSearch:
return m.renderNetSearchOverlay()
content = m.renderNetSearchOverlay()
case screenURLInput:
return m.renderURLInputOverlay()
content = m.renderURLInputOverlay()
case screenLyrics:
return m.renderLyricsOverlay()
content = m.renderLyricsOverlay()
case screenJump:
return m.renderJumpOverlay()
content = m.renderJumpOverlay()
case screenFullVisualizer:
return m.renderFullVisualizer()
content = m.renderFullVisualizer()
default:
content = strings.Join(m.mainSections(m.renderPlaylist(), true), "\n")
}
content := strings.Join(m.mainSections(m.renderPlaylist(), true), "\n")
frame := ui.FrameStyle.Render(content)
rendered := content
if screen == screenMain || screen == screenFullVisualizer {
rendered = m.centerFrame(ui.FrameStyle.Render(content))
}
return m.centerFrame(frame)
view := tea.NewView(rendered)
view.AltScreen = true
view.WindowTitle = currentTerminalTitle(m.termTitle, m.width, m.terminalTitleValues())
return view
}
func trimTrailingEmpty(sections []string) []string {
@@ -288,7 +297,7 @@ func (m Model) renderFullVisualizer() string {
helpKey("V", "Exit ") + helpKey("v", "Mode:"+m.vis.ModeName()+" ") + helpKey("Spc", "⏯ ") + helpKey("<>", "Trk ") + helpKey("+-", "Vol"),
}
return m.centerOverlay(strings.Join(sections, "\n"))
return strings.Join(sections, "\n")
}
func (m Model) renderSeekBar() string {
+45 -2
View File
@@ -5,9 +5,9 @@ import (
"strings"
"testing"
"charm.land/lipgloss/v2"
"cliamp/playlist"
"cliamp/ui"
"github.com/charmbracelet/lipgloss"
)
func withFrameWidth(t *testing.T, width int) {
@@ -51,7 +51,7 @@ func TestMainViewShrinksPlaylistForFooterMessages(t *testing.T) {
if got := m.effectivePlaylistVisible(); got != 1 {
t.Fatalf("effectivePlaylistVisible() = %d, want 1 with one row left after footer lines", got)
}
if got := lipgloss.Height(m.View()); got > m.height {
if got := lipgloss.Height(m.View().Content); got > m.height {
t.Fatalf("View() height = %d, want <= %d after footer lines shrink playlist", got, m.height)
}
}
@@ -142,3 +142,46 @@ func TestRenderNavBrowserIncludesFooterMessages(t *testing.T) {
t.Fatalf("renderNavBrowser() missing download footer: %q", out)
}
}
func TestViewKeepsOverlayLayoutUnchanged(t *testing.T) {
withFrameWidth(t, 80)
m := Model{
width: 80,
height: 22,
keymap: keymapOverlay{
visible: true,
},
}
want := m.renderKeymapOverlay()
if got := lipgloss.Height(want); got > m.height {
t.Fatalf("renderKeymapOverlay() height = %d, want <= %d for test setup", got, m.height)
}
if got := m.View().Content; got != want {
t.Fatalf("View() changed overlay layout")
}
}
func TestFullVisualizerViewFitsTerminalWidth(t *testing.T) {
if sharedPlayer == nil {
t.Skip("audio hardware unavailable")
}
withFrameWidth(t, 80)
sharedPlayer.Stop()
m := Model{
player: sharedPlayer,
playlist: playlist.New(),
vis: ui.NewVisualizer(float64(sharedPlayer.SampleRate())),
width: 80,
height: 24,
fullVis: true,
}
m.vis.Mode = ui.VisNone
if got := lipgloss.Width(m.View().Content); got > m.width {
t.Fatalf("View() width = %d, want <= %d in full visualizer mode", got, m.width)
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"cliamp/resolve"
tea "github.com/charmbracelet/bubbletea"
tea "charm.land/bubbletea/v2"
)
const ytdlBatchSize = 100 // items per background batch
+15 -14
View File
@@ -1,27 +1,29 @@
package ui
import (
"cliamp/theme"
"image/color"
"github.com/charmbracelet/lipgloss"
"charm.land/lipgloss/v2"
"cliamp/theme"
)
// CLIAMP color palette using standard ANSI terminal colors (0-15).
// These adapt to the user's terminal theme for consistent appearance.
var (
ColorTitle lipgloss.TerminalColor = lipgloss.ANSIColor(10) // bright green
ColorText lipgloss.TerminalColor = lipgloss.ANSIColor(15) // bright white
ColorDim lipgloss.TerminalColor = lipgloss.ANSIColor(7) // white (light gray)
ColorAccent lipgloss.TerminalColor = lipgloss.ANSIColor(11) // bright yellow
ColorPlaying lipgloss.TerminalColor = lipgloss.ANSIColor(10) // bright green
ColorSeekBar lipgloss.TerminalColor = lipgloss.ANSIColor(11) // bright yellow
ColorVolume lipgloss.TerminalColor = lipgloss.ANSIColor(2) // green
ColorError lipgloss.TerminalColor = lipgloss.ANSIColor(9) // bright red
ColorTitle color.Color = lipgloss.ANSIColor(10) // bright green
ColorText color.Color = lipgloss.ANSIColor(15) // bright white
ColorDim color.Color = lipgloss.ANSIColor(7) // white (light gray)
ColorAccent color.Color = lipgloss.ANSIColor(11) // bright yellow
ColorPlaying color.Color = lipgloss.ANSIColor(10) // bright green
ColorSeekBar color.Color = lipgloss.ANSIColor(11) // bright yellow
ColorVolume color.Color = lipgloss.ANSIColor(2) // green
ColorError color.Color = lipgloss.ANSIColor(9) // bright red
// Spectrum gradient: green -> yellow -> red
SpectrumLow lipgloss.TerminalColor = lipgloss.ANSIColor(10) // bright green
SpectrumMid lipgloss.TerminalColor = lipgloss.ANSIColor(11) // bright yellow
SpectrumHigh lipgloss.TerminalColor = lipgloss.ANSIColor(9) // bright red
SpectrumLow color.Color = lipgloss.ANSIColor(10) // bright green
SpectrumMid color.Color = lipgloss.ANSIColor(11) // bright yellow
SpectrumHigh color.Color = lipgloss.ANSIColor(9) // bright red
)
// PaddingH is the horizontal padding inside the frame.
@@ -51,7 +53,6 @@ var FrameStyle = lipgloss.NewStyle().
// If the theme is the default (empty hex values), ANSI fallback colors are restored.
func ApplyThemeColors(t theme.Theme) {
if t.IsDefault() {
// Restore ANSI defaults.
ColorTitle = lipgloss.ANSIColor(10)
ColorText = lipgloss.ANSIColor(15)
ColorDim = lipgloss.ANSIColor(7)
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/charmbracelet/lipgloss"
"charm.land/lipgloss/v2"
)
const classicPeakTestGlyphs = "⎺⎻⎼⎽"
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"strings"
"time"
"github.com/charmbracelet/lipgloss"
"charm.land/lipgloss/v2"
"github.com/madelynnblue/go-dsp/fft"
)