diff --git a/go.mod b/go.mod index 383d79cca8339dfae199a2487f39f0377410197a..315c0539463a9edd6b6afde8195baa5067702309 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.5 require ( charm.land/bubbles/v2 v2.0.0-rc.1.0.20260109112849-ae99f46cec66 - charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251216153312-819e2e89c62e + charm.land/bubbletea/v2 v2.0.0-rc.2.0.20260209074636-30878e43d7b0 charm.land/catwalk v0.17.1 charm.land/fantasy v0.7.1 charm.land/glamour/v2 v2.0.0-20260123212943-6014aa153a9b @@ -22,8 +22,8 @@ require ( github.com/charlievieth/fastwalk v1.0.14 github.com/charmbracelet/colorprofile v0.4.1 github.com/charmbracelet/fang v0.4.4 - github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560 - github.com/charmbracelet/x/ansi v0.11.4 + github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 + github.com/charmbracelet/x/ansi v0.11.6 github.com/charmbracelet/x/editor v0.2.0 github.com/charmbracelet/x/etag v0.2.0 github.com/charmbracelet/x/exp/charmtone v0.0.0-20260109001716-2fbdffcb221f diff --git a/go.sum b/go.sum index 34cb7024b27660846968efc1013cf85de9a9f029..e6b69ffb19e4d2a1cc2003aa1c26b0fa423fe418 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ charm.land/bubbles/v2 v2.0.0-rc.1.0.20260109112849-ae99f46cec66 h1:2BdJynsAW+8rv9xq6ZS+x0mtacfxpxjIK1KUIeTqBOs= charm.land/bubbles/v2 v2.0.0-rc.1.0.20260109112849-ae99f46cec66/go.mod h1:5AbN6cEd/47gkEf8TgiQ2O3RZ5QxMS14l9W+7F9fPC4= -charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251216153312-819e2e89c62e h1:tXwTmgGpwZT7ParKF5xbEQBVjM2e1uKhKi/GpfU3mYQ= -charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251216153312-819e2e89c62e/go.mod h1:pDM18flq3Z4njKZPA3zCvyVSSIJbMcoqlE82BdGUtL8= +charm.land/bubbletea/v2 v2.0.0-rc.2.0.20260209074636-30878e43d7b0 h1:HAbpM9TPjZM18D677ww3VnkKXdd2hyMQtHUsVV0HcPQ= +charm.land/bubbletea/v2 v2.0.0-rc.2.0.20260209074636-30878e43d7b0/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ= charm.land/catwalk v0.17.1 h1:UsHvBi3S7CxONiIZTWKTXM+H9qla8I0fCb/SVru33ms= charm.land/catwalk v0.17.1/go.mod h1:kAdk/GjAJbl1AjRjmfU5c9lZfs7PeC3Uy9TgaVtlN64= charm.land/fantasy v0.7.1 h1:JOCYeLz32PM11y1u08/YgWl3LfPwhjOIuoyjBXjFofI= @@ -102,10 +102,10 @@ github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= github.com/charmbracelet/fang v0.4.4 h1:G4qKxF6or/eTPgmAolwPuRNyuci3hTUGGX1rj1YkHJY= github.com/charmbracelet/fang v0.4.4/go.mod h1:P5/DNb9DddQ0Z0dbc0P3ol4/ix5Po7Ofr2KMBfAqoCo= -github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560 h1:j3PW2hypGoPKBy3ooKzW0TFxaxhyHK3NbkLLn4KeRFc= -github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560/go.mod h1:VWATWLRwYP06VYCEur7FsNR2B1xAo7Y+xl1PTbd1ePc= -github.com/charmbracelet/x/ansi v0.11.4 h1:6G65PLu6HjmE858CnTUQY1LXT3ZUWwfvqEROLF8vqHI= -github.com/charmbracelet/x/ansi v0.11.4/go.mod h1:/5AZ+UfWExW3int5H5ugnsG/PWjNcSQcwYsHBlPFQN4= +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/editor v0.2.0 h1:7XLUKtaRaB8jN7bWU2p2UChiySyaAuIfYiIRg8gGWwk= github.com/charmbracelet/x/editor v0.2.0/go.mod h1:p3oQ28TSL3YPd+GKJ1fHWcp+7bVGpedHpXmo0D6t1dY= github.com/charmbracelet/x/etag v0.2.0 h1:Euj1VkheoHfTYA9y+TCwkeXF/hN8Fb9l4LqZl79pt04= diff --git a/internal/ui/common/capabilities.go b/internal/ui/common/capabilities.go index 6636976d7d4f86d9283be2db759b44f948ad40f5..b9a9de674d4b17e4ac59ff41a93b9f0db2e0028a 100644 --- a/internal/ui/common/capabilities.go +++ b/internal/ui/common/capabilities.go @@ -47,7 +47,7 @@ func (c *Capabilities) Update(msg any) { case tea.WindowSizeMsg: c.Columns = m.Width c.Rows = m.Height - case uv.WindowPixelSizeEvent: + case uv.PixelSizeEvent: c.PixelX = m.Width c.PixelY = m.Height case uv.KittyGraphicsEvent: @@ -71,6 +71,7 @@ func (c *Capabilities) Update(msg any) { func QueryCmd(env uv.Environ) tea.Cmd { var sb strings.Builder sb.WriteString(ansi.RequestPrimaryDeviceAttributes) + sb.WriteString(ansi.QueryModifyOtherKeys) // Queries that should only be sent to "smart" normal terminals. shouldQueryFor := shouldQueryCapabilities(env) diff --git a/internal/ui/model/landing.go b/internal/ui/model/landing.go index a90ef76fdaf779e61477f5a05fd92a68d2e8a257..45d376ff5ddc691b978e438ddef04a702af100f9 100644 --- a/internal/ui/model/landing.go +++ b/internal/ui/model/landing.go @@ -4,7 +4,7 @@ import ( "charm.land/lipgloss/v2" "github.com/charmbracelet/crush/internal/agent" "github.com/charmbracelet/crush/internal/ui/common" - uv "github.com/charmbracelet/ultraviolet" + "github.com/charmbracelet/ultraviolet/layout" ) // selectedLargeModel returns the currently selected large language model from @@ -31,7 +31,7 @@ func (m *UI) landingView() string { parts = append(parts, "", m.modelInfo(width)) infoSection := lipgloss.JoinVertical(lipgloss.Left, parts...) - _, remainingHeightArea := uv.SplitVertical(m.layout.main, uv.Fixed(lipgloss.Height(infoSection)+1)) + _, remainingHeightArea := layout.SplitVertical(m.layout.main, layout.Fixed(lipgloss.Height(infoSection)+1)) mcpLspSectionWidth := min(30, (width-1)/2) diff --git a/internal/ui/model/sidebar.go b/internal/ui/model/sidebar.go index ea751a596077cdc809f5f0cee518e08c57f0c1fb..221405a0a276a38c531223f7ed5adde1139c6ef8 100644 --- a/internal/ui/model/sidebar.go +++ b/internal/ui/model/sidebar.go @@ -8,6 +8,7 @@ import ( "github.com/charmbracelet/crush/internal/ui/common" "github.com/charmbracelet/crush/internal/ui/logo" uv "github.com/charmbracelet/ultraviolet" + "github.com/charmbracelet/ultraviolet/layout" "golang.org/x/text/cases" "golang.org/x/text/language" ) @@ -134,7 +135,7 @@ func (m *UI) drawSidebar(scr uv.Screen, area uv.Rectangle) { blocks..., ) - _, remainingHeightArea := uv.SplitVertical(m.layout.sidebar, uv.Fixed(lipgloss.Height(sidebarHeader))) + _, remainingHeightArea := layout.SplitVertical(m.layout.sidebar, layout.Fixed(lipgloss.Height(sidebarHeader))) remainingHeight := remainingHeightArea.Dy() - 10 maxFiles, maxLSPs, maxMCPs := getDynamicHeightLimits(remainingHeight) diff --git a/internal/ui/model/ui.go b/internal/ui/model/ui.go index 498b9c3fe3e5bb47d51d76bab5d310da204ab206..77dfc9a726f466fafaacede483438629e293ae2f 100644 --- a/internal/ui/model/ui.go +++ b/internal/ui/model/ui.go @@ -47,6 +47,7 @@ import ( "github.com/charmbracelet/crush/internal/ui/util" "github.com/charmbracelet/crush/internal/version" uv "github.com/charmbracelet/ultraviolet" + "github.com/charmbracelet/ultraviolet/layout" "github.com/charmbracelet/ultraviolet/screen" "github.com/charmbracelet/x/editor" ) @@ -135,7 +136,7 @@ type UI struct { // The width and height of the terminal in cells. width int height int - layout layout + layout uiLayout isTransparent bool @@ -2235,7 +2236,7 @@ func (m *UI) updateSize() { // generateLayout calculates the layout rectangles for all UI components based // on the current UI state and terminal dimensions. -func (m *UI) generateLayout(w, h int) layout { +func (m *UI) generateLayout(w, h int) uiLayout { // The screen area we're working with area := image.Rect(0, 0, w, h) @@ -2256,7 +2257,7 @@ func (m *UI) generateLayout(w, h int) layout { } // Add app margins - appRect, helpRect := uv.SplitVertical(area, uv.Fixed(area.Dy()-helpHeight)) + appRect, helpRect := layout.SplitVertical(area, layout.Fixed(area.Dy()-helpHeight)) appRect.Min.Y += 1 appRect.Max.Y -= 1 helpRect.Min.Y -= 1 @@ -2269,7 +2270,7 @@ func (m *UI) generateLayout(w, h int) layout { appRect.Max.X -= 1 } - layout := layout{ + uiLayout := uiLayout{ area: area, status: helpRect, } @@ -2285,9 +2286,9 @@ func (m *UI) generateLayout(w, h int) layout { // ------ // help - headerRect, mainRect := uv.SplitVertical(appRect, uv.Fixed(landingHeaderHeight)) - layout.header = headerRect - layout.main = mainRect + headerRect, mainRect := layout.SplitVertical(appRect, layout.Fixed(landingHeaderHeight)) + uiLayout.header = headerRect + uiLayout.main = mainRect case uiLanding: // Layout @@ -2299,14 +2300,14 @@ func (m *UI) generateLayout(w, h int) layout { // editor // ------ // help - headerRect, mainRect := uv.SplitVertical(appRect, uv.Fixed(landingHeaderHeight)) - mainRect, editorRect := uv.SplitVertical(mainRect, uv.Fixed(mainRect.Dy()-editorHeight)) + headerRect, mainRect := layout.SplitVertical(appRect, layout.Fixed(landingHeaderHeight)) + mainRect, editorRect := layout.SplitVertical(mainRect, layout.Fixed(mainRect.Dy()-editorHeight)) // Remove extra padding from editor (but keep it for header and main) editorRect.Min.X -= 1 editorRect.Max.X += 1 - layout.header = headerRect - layout.main = mainRect - layout.editor = editorRect + uiLayout.header = headerRect + uiLayout.main = mainRect + uiLayout.editor = editorRect case uiChat: if m.isCompact { @@ -2320,28 +2321,28 @@ func (m *UI) generateLayout(w, h int) layout { // ------ // help const compactHeaderHeight = 1 - headerRect, mainRect := uv.SplitVertical(appRect, uv.Fixed(compactHeaderHeight)) + headerRect, mainRect := layout.SplitVertical(appRect, layout.Fixed(compactHeaderHeight)) detailsHeight := min(sessionDetailsMaxHeight, area.Dy()-1) // One row for the header - sessionDetailsArea, _ := uv.SplitVertical(appRect, uv.Fixed(detailsHeight)) - layout.sessionDetails = sessionDetailsArea - layout.sessionDetails.Min.Y += compactHeaderHeight // adjust for header + sessionDetailsArea, _ := layout.SplitVertical(appRect, layout.Fixed(detailsHeight)) + uiLayout.sessionDetails = sessionDetailsArea + uiLayout.sessionDetails.Min.Y += compactHeaderHeight // adjust for header // Add one line gap between header and main content mainRect.Min.Y += 1 - mainRect, editorRect := uv.SplitVertical(mainRect, uv.Fixed(mainRect.Dy()-editorHeight)) + mainRect, editorRect := layout.SplitVertical(mainRect, layout.Fixed(mainRect.Dy()-editorHeight)) mainRect.Max.X -= 1 // Add padding right - layout.header = headerRect + uiLayout.header = headerRect pillsHeight := m.pillsAreaHeight() if pillsHeight > 0 { pillsHeight = min(pillsHeight, mainRect.Dy()) - chatRect, pillsRect := uv.SplitVertical(mainRect, uv.Fixed(mainRect.Dy()-pillsHeight)) - layout.main = chatRect - layout.pills = pillsRect + chatRect, pillsRect := layout.SplitVertical(mainRect, layout.Fixed(mainRect.Dy()-pillsHeight)) + uiLayout.main = chatRect + uiLayout.pills = pillsRect } else { - layout.main = mainRect + uiLayout.main = mainRect } // Add bottom margin to main - layout.main.Max.Y -= 1 - layout.editor = editorRect + uiLayout.main.Max.Y -= 1 + uiLayout.editor = editorRect } else { // Layout // @@ -2352,40 +2353,40 @@ func (m *UI) generateLayout(w, h int) layout { // ---------- // help - mainRect, sideRect := uv.SplitHorizontal(appRect, uv.Fixed(appRect.Dx()-sidebarWidth)) + mainRect, sideRect := layout.SplitHorizontal(appRect, layout.Fixed(appRect.Dx()-sidebarWidth)) // Add padding left sideRect.Min.X += 1 - mainRect, editorRect := uv.SplitVertical(mainRect, uv.Fixed(mainRect.Dy()-editorHeight)) + mainRect, editorRect := layout.SplitVertical(mainRect, layout.Fixed(mainRect.Dy()-editorHeight)) mainRect.Max.X -= 1 // Add padding right - layout.sidebar = sideRect + uiLayout.sidebar = sideRect pillsHeight := m.pillsAreaHeight() if pillsHeight > 0 { pillsHeight = min(pillsHeight, mainRect.Dy()) - chatRect, pillsRect := uv.SplitVertical(mainRect, uv.Fixed(mainRect.Dy()-pillsHeight)) - layout.main = chatRect - layout.pills = pillsRect + chatRect, pillsRect := layout.SplitVertical(mainRect, layout.Fixed(mainRect.Dy()-pillsHeight)) + uiLayout.main = chatRect + uiLayout.pills = pillsRect } else { - layout.main = mainRect + uiLayout.main = mainRect } // Add bottom margin to main - layout.main.Max.Y -= 1 - layout.editor = editorRect + uiLayout.main.Max.Y -= 1 + uiLayout.editor = editorRect } } - if !layout.editor.Empty() { + if !uiLayout.editor.Empty() { // Add editor margins 1 top and bottom if len(m.attachments.List()) == 0 { - layout.editor.Min.Y += 1 + uiLayout.editor.Min.Y += 1 } - layout.editor.Max.Y -= 1 + uiLayout.editor.Max.Y -= 1 } - return layout + return uiLayout } -// layout defines the positioning of UI elements. -type layout struct { +// uiLayout defines the positioning of UI elements. +type uiLayout struct { // area is the overall available area. area uv.Rectangle