From 2060f938a069c5894a2213d684855296c12742af Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 16 Dec 2024 11:27:00 -0500 Subject: [PATCH 01/30] docs(examples): fix glamour example (#1204) --- examples/glamour/main.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/glamour/main.go b/examples/glamour/main.go index 976ee3be5e..1e5dd66a7a 100644 --- a/examples/glamour/main.go +++ b/examples/glamour/main.go @@ -65,9 +65,20 @@ func newExample() (*example, error) { BorderForeground(lipgloss.Color("62")). PaddingRight(2) + // We need to adjust the width of the glamour render from our main width + // to account for a few things: + // + // * The viewport border width + // * The viewport padding + // * The viewport margins + // * The gutter glamour applies to the left side of the content + // + const glamourGutter = 2 + glamourRenderWidth := width - vp.Style.GetHorizontalFrameSize() - glamourGutter + renderer, err := glamour.NewTermRenderer( glamour.WithAutoStyle(), - glamour.WithWordWrap(width), + glamour.WithWordWrap(glamourRenderWidth), ) if err != nil { return nil, err From 07288b1a746ed3168bbc7a71d3990fbcc4817a90 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 17 Dec 2024 08:52:24 -0500 Subject: [PATCH 02/30] docs(readme): add more stuff to "In the Wild" --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c4f9c90b0..84a2c272f9 100644 --- a/README.md +++ b/README.md @@ -327,7 +327,7 @@ your program in another window. ## Bubble Tea in the Wild -There are over 8k applications built with Bubble Tea! Here are a handful of ’em. +There are over [10,000 applications](https://github.com/charmbracelet/bubbletea/network/dependents) built with Bubble Tea! Here are a handful of ’em. ### Staff favourites @@ -335,15 +335,19 @@ There are over 8k applications built with Bubble Tea! Here are a handful of ’e - [circumflex](https://github.com/bensadeh/circumflex): read Hacker News in the terminal - [gh-dash](https://www.github.com/dlvhdr/gh-dash): a GitHub CLI extension for PRs and issues - [Tetrigo](https://github.com/Broderick-Westrope/tetrigo): Tetris in the terminal +- [Signls](https://github.com/emprcl/signls): a generative midi sequencer designed for composition and live performance +- [Superfile](https://github.com/yorukot/superfile): a super file manager ### In Industry - Microsoft Azure – [Aztify](https://github.com/Azure/aztfy): bring Microsoft Azure resources under Terraform - Daytona – [Daytona](https://github.com/daytonaio/daytona): open source dev environment manager +- Cockroach Labs - [CockroachDB](https://github.com/cockroachdb/cockroach): a cloud-native, high-availability distributed SQL database - Truffle Security Co. – [Trufflehog](https://github.com/trufflesecurity/trufflehog): find leaked credentials - NVIDIA – [container-canary](https://github.com/NVIDIA/container-canary) from NVIDIA: a container validator - AWS – [eks-node-viewer](https://github.com/awslabs/eks-node-viewer) from AWS: a tool for visualizing dynamic node usage within an EKS cluster - MinIO – [mc](https://github.com/minio/mc) from Min.io: the official [MinIO](https://min.io) client +- Ubuntu – [Authd](https://github.com/ubuntu/authd): an authentication daemon for cloud-based identity providers ### Charm stuff From 79cc2fb92466c957002319fdc321a8e31c103acb Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 17 Dec 2024 09:03:33 -0500 Subject: [PATCH 03/30] docs(readme): fix tiny typo in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84a2c272f9..d9df5d791f 100644 --- a/README.md +++ b/README.md @@ -342,7 +342,7 @@ There are over [10,000 applications](https://github.com/charmbracelet/bubbletea/ - Microsoft Azure – [Aztify](https://github.com/Azure/aztfy): bring Microsoft Azure resources under Terraform - Daytona – [Daytona](https://github.com/daytonaio/daytona): open source dev environment manager -- Cockroach Labs - [CockroachDB](https://github.com/cockroachdb/cockroach): a cloud-native, high-availability distributed SQL database +- Cockroach Labs – [CockroachDB](https://github.com/cockroachdb/cockroach): a cloud-native, high-availability distributed SQL database - Truffle Security Co. – [Trufflehog](https://github.com/trufflesecurity/trufflehog): find leaked credentials - NVIDIA – [container-canary](https://github.com/NVIDIA/container-canary) from NVIDIA: a container validator - AWS – [eks-node-viewer](https://github.com/awslabs/eks-node-viewer) from AWS: a tool for visualizing dynamic node usage within an EKS cluster From 1bf18861d91be438c860c58d2d2c1274f5eab444 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 17 Dec 2024 09:12:28 -0500 Subject: [PATCH 04/30] docs(readme): additional readme tiyding up --- README.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d9df5d791f..8c13bd808f 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,8 @@ complex terminal applications, either inline, full-window, or a mix of both.

Bubble Tea is in use in production and includes a number of features and -performance optimizations we’ve added along the way. Among those is a standard -framerate-based renderer, a renderer for high-performance scrollable -regions which works alongside the main renderer, and mouse support. +performance optimizations we’ve added along the way. Among those is +a framerate-based renderer, mouse support, focus reporting and more. To get started, see the tutorial below, the [examples][examples], the [docs][docs], the [video tutorials][youtube] and some common [resources](#libraries-we-use-with-bubble-tea). @@ -61,8 +60,8 @@ import will be the Bubble Tea library, which we'll call `tea` for short. ```go package main -// XXX: These imports will be used later on the tutorial. If you save the file -// now, Go might complain they are unused, but that is to be expected. +// These imports will be used later on the tutorial. If you save the file +// now, Go might complain they are unused, but that's fine. // You may also need to run `go mod tidy` to download bubbletea and its // dependencies. import ( @@ -314,16 +313,12 @@ your program in another window. - [Harmonica][harmonica]: A spring animation library for smooth, natural motion - [BubbleZone][bubblezone]: Easy mouse event tracking for Bubble Tea components - [ntcharts][ntcharts]: A terminal charting library built for Bubble Tea and [Lip Gloss][lipgloss] -- [Termenv][termenv]: Advanced ANSI styling for terminal applications -- [Reflow][reflow]: Advanced ANSI-aware methods for working with text [bubbles]: https://github.com/charmbracelet/bubbles [lipgloss]: https://github.com/charmbracelet/lipgloss [harmonica]: https://github.com/charmbracelet/harmonica [bubblezone]: https://github.com/lrstanley/bubblezone [ntcharts]: https://github.com/NimbleMarkets/ntcharts -[termenv]: https://github.com/muesli/termenv -[reflow]: https://github.com/muesli/reflow ## Bubble Tea in the Wild @@ -344,9 +339,9 @@ There are over [10,000 applications](https://github.com/charmbracelet/bubbletea/ - Daytona – [Daytona](https://github.com/daytonaio/daytona): open source dev environment manager - Cockroach Labs – [CockroachDB](https://github.com/cockroachdb/cockroach): a cloud-native, high-availability distributed SQL database - Truffle Security Co. – [Trufflehog](https://github.com/trufflesecurity/trufflehog): find leaked credentials -- NVIDIA – [container-canary](https://github.com/NVIDIA/container-canary) from NVIDIA: a container validator -- AWS – [eks-node-viewer](https://github.com/awslabs/eks-node-viewer) from AWS: a tool for visualizing dynamic node usage within an EKS cluster -- MinIO – [mc](https://github.com/minio/mc) from Min.io: the official [MinIO](https://min.io) client +- NVIDIA – [container-canary](https://github.com/NVIDIA/container-canary): a container validator +- AWS – [eks-node-viewer](https://github.com/awslabs/eks-node-viewer): a tool for visualizing dynamic node usage within an EKS cluster +- MinIO – [mc](https://github.com/minio/mc): the official [MinIO](https://min.io) client - Ubuntu – [Authd](https://github.com/ubuntu/authd): an authentication daemon for cloud-based identity providers ### Charm stuff From d67033479573c91fc4a0e450fe84bbd0d6db6c5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:18:34 +0000 Subject: [PATCH 05/30] chore(deps): bump golang.org/x/sys from 0.28.0 to 0.29.0 (#1281) Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.28.0 to 0.29.0. - [Commits](https://github.com/golang/sys/compare/v0.28.0...v0.29.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 5f2201d94b..cc93713ed1 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 github.com/muesli/cancelreader v0.2.2 golang.org/x/sync v0.10.0 - golang.org/x/sys v0.28.0 + golang.org/x/sys v0.29.0 ) require ( diff --git a/go.sum b/go.sum index 8a8ad17542..45c802d307 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= -github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM= -github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= @@ -31,7 +29,7 @@ golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= From 0cf58beba4cf48b3bbea72e7cf5b0b3c975bf5a0 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 9 Dec 2024 15:49:57 -0500 Subject: [PATCH 06/30] feat: use the cursed ferocious renderer This implements the new cursed ferocious renderer. This is based on the new `cellbuf.Screen` renderer. This renderer is more efficient and flexible than the previous renderer. The renderer controls the altscreen buffer and cursor visibility which means we don't need to do so in Bubble Tea. Instead, Bubble Tea can focus on the event loop and the application state. --- examples/go.mod | 11 +- examples/go.sum | 18 +- ferocious_renderer.go | 631 +++++------------------------------------- go.mod | 7 +- go.sum | 14 +- tea.go | 17 +- tty.go | 13 - 7 files changed, 103 insertions(+), 608 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index a603068e18..032686a47e 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -4,8 +4,8 @@ go 1.23.1 require ( github.com/charmbracelet/bubbles/v2 v2.0.0-alpha.2.0.20241121172047-bd415b4ebae8 - github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241121171714-fbd5423ea935 - github.com/charmbracelet/colorprofile v0.1.8 + github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241126192050-a8ed96118b08 + github.com/charmbracelet/colorprofile v0.1.9 github.com/charmbracelet/glamour v0.8.0 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804 @@ -22,12 +22,11 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/charmbracelet/lipgloss v0.13.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.6 // indirect + github.com/charmbracelet/lipgloss v1.0.0 // indirect + github.com/charmbracelet/x/cellbuf v0.0.7-0.20241209212528-0eec74ecaa6f // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect - github.com/charmbracelet/x/input v0.2.1-0.20241210230033-45c33cb7b57a // indirect + github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect - github.com/charmbracelet/x/vt v0.0.0-20241121165045-a3720547cbb4 // indirect github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 // indirect github.com/charmbracelet/x/windows v0.2.0 // indirect github.com/dlclark/regexp2 v1.11.0 // indirect diff --git a/examples/go.sum b/examples/go.sum index f7ad5ef170..5d3e33341d 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -16,30 +16,28 @@ 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/charmbracelet/bubbles/v2 v2.0.0-alpha.2.0.20241121172047-bd415b4ebae8 h1:CRhvWh0cIainbY47znHAxzohyXDNmcmrp9ggjvn1cJk= github.com/charmbracelet/bubbles/v2 v2.0.0-alpha.2.0.20241121172047-bd415b4ebae8/go.mod h1:fKcC1zxdgRjgg21XbRIf/bkSELpd9D9XKlHRCrhR1Tk= -github.com/charmbracelet/colorprofile v0.1.8 h1:PywDeXsiAzlPtkiiKgMEVLvb6nlEuKrMj9+FJBtj4jU= -github.com/charmbracelet/colorprofile v0.1.8/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= +github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU9pGu1jkZxLw= +github.com/charmbracelet/colorprofile v0.1.9/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs= github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= -github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw= -github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= +github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= +github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804 h1:7CYjb9YMZA4kMhLgGdtlXvq+nu1oyENpMyMQlTvqSFw= github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.6 h1:pJUWN/G1jbt1Nj/+ILfC2/ABQoZzWu1vG73yHQEYELI= -github.com/charmbracelet/x/cellbuf v0.0.6/go.mod h1:d72o71glp8flkCz54PHLe3+nuw5u2v3UxmKqruUERWQ= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241209212528-0eec74ecaa6f h1:r/BMib+AQerOPAa39CuiTwfkk0G1CGQsquiSQ24I30E= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241209212528-0eec74ecaa6f/go.mod h1:EfpoBnbE9VTpF6D2jVg4YFDzL6Vzk+1O+LGTU6OMV7E= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233/go.mod h1:cw9df32BXdkcd0LzAHsFMmvXOsrrlDKazIW8PCq0cPM= -github.com/charmbracelet/x/input v0.2.1-0.20241210230033-45c33cb7b57a h1:CspqRQ5YjX8qE/3/QefJf3twGokKeM42I/fZmx72H90= -github.com/charmbracelet/x/input v0.2.1-0.20241210230033-45c33cb7b57a/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= +github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= +github.com/charmbracelet/x/input v0.3.0/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= 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/x/vt v0.0.0-20241121165045-a3720547cbb4 h1:EacjHxcQEEgOZ7TbkAU3b84hd1Bn5NwA8YV5uyJ9EI4= -github.com/charmbracelet/x/vt v0.0.0-20241121165045-a3720547cbb4/go.mod h1:1/jFoHl7/I4br0StC9OXXEondkK9qi3nUtKoqI35HcI= github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 h1:14czE6R5CgOlvONsJYa2B1uTyLvXzGXpBqw2AyZeTh4= github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32/go.mod h1:hyua5CY63kyl7IfyIxv1SjVEqoKze/XmDkEglItuVjA= github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw= diff --git a/ferocious_renderer.go b/ferocious_renderer.go index 2cd6948ba0..e66879a4de 100644 --- a/ferocious_renderer.go +++ b/ferocious_renderer.go @@ -1,8 +1,6 @@ package tea import ( - "bytes" - "image" "io" "strings" "sync" @@ -10,597 +8,120 @@ import ( "github.com/charmbracelet/colorprofile" "github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/cellbuf" - "github.com/charmbracelet/x/vt" ) -var undefPoint = image.Pt(-1, -1) - -// cursor represents a terminal cursor. -type cursor struct { - image.Point - visible bool -} - -// screen represents a terminal screen. -type screen struct { - dirty map[int]int // keeps track of dirty cells - linew []int // keeps track of the width of each line - cur cursor // cursor state - *vt.Buffer // the cell buffer -} - -// isDirty returns true if the cell at the given position is dirty. -func (s *screen) isDirty(x, y int) bool { - idx := y*s.Width() + x - v, ok := s.dirty[idx] - return ok && v == 1 +type screenRenderer struct { + w io.Writer + scr *cellbuf.Screen + lastFrame string + term string // the terminal type $TERM + width, height int + mu sync.Mutex + profile colorprofile.Profile + altScreen bool + cursorHidden bool } -// reset resets the screen to its initial state. -func (s *screen) reset() { - s.Buffer = vt.NewBuffer(0, 0) - s.dirty = make(map[int]int) - s.cur = cursor{} - s.linew = make([]int, 0) -} - -// Set implements [cellbuf.Grid] and marks changed cells as dirty. -func (s *screen) SetCell(x, y int, cell *cellbuf.Cell) (v bool) { - c := s.Cell(x, y) - if c.Equal(cell) { - // Cells are the same, no need to update. - return - } - - v = s.Buffer.SetCell(x, y, cell) - if v { - // Mark the cell as dirty. You nasty one ;) - idx := y*s.Width() + x - s.dirty[idx] = 1 - } +var _ renderer = &screenRenderer{} +func newScreenRenderer(p colorprofile.Profile, term string) (s *screenRenderer) { + s = new(screenRenderer) + s.term = term + s.profile = p return } -// ferociousRenderer is a cell-based terminal renderer. It's ferocious! -type ferociousRenderer struct { - mtx sync.Mutex - out io.Writer // we only write to the output during flush and close - buf bytes.Buffer // the internal buffer for rendering - - scrs [2]screen // Both inline and alt-screen - scr *screen // Points to the current used screen - - method cellbuf.Method - - finalCur image.Point // The final cursor position - - pen cellbuf.Style - link cellbuf.Link - - queueAbove []string - lastRenders [2]string // The last render for both inline and alt-screen buffers - lastRender *string // Points to the last render string - frame string // The current frame to render - lastHeight int // The height of the last render - - // modes - altScreen bool - cursorHidden bool - - profile colorprofile.Profile -} - -func newFerociousRenderer(p colorprofile.Profile) *ferociousRenderer { - r := &ferociousRenderer{ - // TODO: Update this if Grapheme Clustering is supported. - method: cellbuf.WcWidth, - finalCur: undefPoint, - profile: p, - } - r.reset() - return r -} - -var _ renderer = &ferociousRenderer{} - // close implements renderer. -func (c *ferociousRenderer) close() error { - c.mtx.Lock() - defer c.mtx.Unlock() - - seq := c.buf.String() - c.buf.Reset() - - y := c.scr.cur.Y - if !c.altScreen && y < c.lastHeight { - diff := c.lastHeight - y - 1 - // Ensure the cursor is at the bottom of the screen - seq += strings.Repeat("\n", diff) - y += diff - c.scr.cur.Y = y - } - - if c.scr.cur.X != 0 { - seq += "\r" - c.scr.cur.X = 0 - } - - if _, line := cellbuf.RenderLine( - c.scr, y, - cellbuf.WithRenderProfile(c.profile), - ); line != "" { - // OPTIM: We only clear the line if there's content on it. - seq += ansi.EraseEntireLine - } - - if seq == "" { - // Nothing to clear. - return nil - } - - _, err := io.WriteString(c.out, seq) - return err -} - -// clearScreen returns a string to clear the screen and moves the cursor to the -// origin location i.e. top-left. -func (c *ferociousRenderer) clearScreen() { - c.moveCursor(0, 0) - if c.altScreen { - c.buf.WriteString(ansi.EraseEntireScreen) //nolint:errcheck - return - } - - c.buf.WriteString(ansi.EraseScreenBelow) //nolint:errcheck +func (s *screenRenderer) close() (err error) { + return s.scr.Close() } // flush implements renderer. -func (c *ferociousRenderer) flush() error { - c.mtx.Lock() - defer c.mtx.Unlock() - - if c.finalCur == c.scr.cur.Point && len(c.queueAbove) == 0 && - c.frame == *c.lastRender && c.lastHeight == c.scr.Height() { - return nil - } - - queueAbove := c.queueAbove - if !c.altScreen && len(queueAbove) > 0 { - c.moveCursor(0, 0) - for _, line := range c.queueAbove { - c.buf.WriteString(line + ansi.EraseLineRight + "\r\n") - } - c.queueAbove = queueAbove[:0] - c.repaint() - } - - if *c.lastRender == "" { - // First render and repaints clear the screen. - c.clearScreen() - } - - if c.scr.cur.X > c.scr.Width()-1 { - // When the cursor is at EOL, we need to put it back to the beginning - // of line. Otherwise, the autowrap (DECAWM), which is enabled by - // default, will move the cursor to the next line on the next cell - // write. - c.buf.WriteByte(ansi.CR) - c.scr.cur.X = 0 - } - - c.changes() - - // XXX: We need to move the cursor to the final position before rendering - // the frame to avoid flickering. - shouldHideCursor := !c.cursorHidden - if c.finalCur != image.Pt(-1, -1) { - shouldMove := len(queueAbove) == 0 && c.finalCur != c.scr.cur.Point - shouldHideCursor = shouldHideCursor && shouldMove - if shouldMove { - c.moveCursor(c.finalCur.X, c.finalCur.Y) - } - } - - c.scr.dirty = make(map[int]int) - c.lastHeight = cellbuf.Height(c.frame) - *c.lastRender = c.frame - render := c.buf.String() - c.buf.Reset() - if render == "" { - return nil - } - - if shouldHideCursor { - // Hide the cursor while rendering to avoid flickering. - render = ansi.HideCursor + render + ansi.ShowCursor - } - - _, err := io.WriteString(c.out, render) - return err +func (s *screenRenderer) flush() error { + s.mu.Lock() + defer s.mu.Unlock() + s.scr.Render() + return nil } // render implements renderer. -func (c *ferociousRenderer) render(s string) { - c.mtx.Lock() - defer c.mtx.Unlock() - c.frame = s - // Ensure the buffer is at least the height of the new frame. - height := cellbuf.Height(s) - c.scr.Resize(c.scr.Width(), height) - linew := cellbuf.Paint(c.scr, c.method, s, nil) - c.scr.linew = linew -} - -// reset implements renderer. -func (c *ferociousRenderer) reset() { - c.mtx.Lock() - defer c.mtx.Unlock() +func (s *screenRenderer) render(frame string) { + if frame == s.lastFrame { + return + } - c.lastRenders[0] = "" - c.lastRenders[1] = "" - c.scrs[0].reset() - c.scrs[1].reset() - // alt-screen buffer cursor always starts from where the main buffer cursor - // is. We need to set it to (-1,-1) to force the cursor to be moved to the - // origin on the first render. - c.scrs[1].cur.Point = undefPoint - if c.altScreen { - c.scr = &c.scrs[1] - c.lastRender = &c.lastRenders[1] - } else { - c.scr = &c.scrs[0] - c.lastRender = &c.lastRenders[0] + s.lastFrame = frame + if !s.altScreen { + frameHeight := strings.Count(frame, "\n") + 1 + s.scr.Resize(s.width, frameHeight) } -} -// repaint forces a repaint of the screen. -func (c *ferociousRenderer) repaint() { - *c.lastRender = "" + cellbuf.Paint(s.scr, frame) } -// updateCursorVisibility ensures the cursor state is in sync with the -// renderer. -func (c *ferociousRenderer) updateCursorVisibility() { - if !c.cursorHidden != c.scr.cur.visible { - c.scr.cur.visible = !c.cursorHidden - // cmd.exe and other terminals keep separate cursor states for the AltScreen - // and the main buffer. We have to explicitly reset the cursor visibility - // whenever we exit AltScreen. - if c.cursorHidden { - io.WriteString(&c.buf, ansi.HideCursor) //nolint:errcheck - } else { - io.WriteString(&c.buf, ansi.ShowCursor) //nolint:errcheck - } - } +// reset implements renderer. +func (s *screenRenderer) reset() { + s.scr = cellbuf.NewScreen(s.w, &cellbuf.ScreenOptions{ + Term: s.term, + Profile: s.profile, + AltScreen: s.altScreen, + RelativeCursor: !s.altScreen, + ShowCursor: !s.cursorHidden, + Width: s.width, + Height: s.height, + }) } // update implements renderer. -func (c *ferociousRenderer) update(msg Msg) { - c.mtx.Lock() - defer c.mtx.Unlock() +func (s *screenRenderer) update(msg Msg) { switch msg := msg.(type) { case ColorProfileMsg: - c.profile = msg.Profile - - case rendererWriter: - c.out = msg.Writer - + s.profile = msg.Profile case WindowSizeMsg: - c.scrs[0].Resize(msg.Width, msg.Height) - c.scrs[1].Resize(msg.Width, msg.Height) - c.lastRenders[0] = "" - c.lastRenders[1] = "" - - case clearScreenMsg: - seq := ansi.EraseEntireScreen + ansi.HomeCursorPosition - if !c.cursorHidden { - seq = ansi.HideCursor + seq + ansi.ShowCursor + s.width, s.height = msg.Width, msg.Height + if s.altScreen { + // Resize alternate screen + s.scr.Resize(s.width, s.height) } - - io.WriteString(c.out, seq) //nolint:errcheck - c.repaint() - + case clearScreenMsg: + s.scr.Clear() + s.repaint() case repaintMsg: - c.repaint() - - case printLineMessage: - if !c.altScreen { - c.queueAbove = append(c.queueAbove, strings.Split(msg.messageBody, "\n")...) - } - + s.repaint() + case rendererWriter: + s.w = msg.Writer + s.reset() case enableModeMsg: - switch ansi.DECMode(msg) { + switch ansi.Mode(ansi.DECMode(msg)) { case ansi.AltScreenSaveCursorMode: - if c.altScreen { - return - } - - c.scr = &c.scrs[1] - c.altScreen = true - - // NOTE: Using `CSI ? 1049` clears the screen so we need to repaint - // the alt screen buffer. - c.repaint() - - // Some terminals keep separate cursor states for the AltScreen and - // the main buffer. We have to explicitly reset the cursor visibility - // whenever we enter or leave AltScreen. - c.updateCursorVisibility() - + s.altScreen = true + s.scr.EnterAltScreen() + s.scr.SetRelativeCursor(!s.altScreen) + s.scr.Resize(s.width, s.height) + s.repaint() case ansi.TextCursorEnableMode: - if !c.cursorHidden { - return - } - - c.cursorHidden = false + s.cursorHidden = false + s.scr.ShowCursor() } - case disableModeMsg: - switch ansi.DECMode(msg) { + switch ansi.Mode(ansi.DECMode(msg)) { case ansi.AltScreenSaveCursorMode: - if !c.altScreen { - return - } - - c.scr = &c.scrs[0] - c.altScreen = false - - // Some terminals keep separate cursor states for the AltScreen and - // the main buffer. We have to explicitly reset the cursor visibility - // whenever we enter or leave AltScreen. - c.updateCursorVisibility() - + s.altScreen = false + s.scr.ExitAltScreen() + s.scr.SetRelativeCursor(!s.altScreen) + s.scr.Resize(s.width, strings.Count(s.lastFrame, "\n")+1) + s.repaint() case ansi.TextCursorEnableMode: - if c.cursorHidden { - return - } - - c.cursorHidden = true + s.cursorHidden = true + s.scr.HideCursor() } - + case printLineMessage: + s.scr.InsertAbove(msg.messageBody) case setCursorPosMsg: - c.finalCur = image.Pt(clamp(msg.X, 0, c.scr.Width()-1), clamp(msg.Y, 0, c.scr.Height()-1)) - } -} - -var spaceCell = &cellbuf.Cell{Content: " ", Width: 1} - -// changes commits the changes from the cell buffer using the dirty cells map -// and writes them to the internal buffer. -func (c *ferociousRenderer) changes() { - width := c.scr.Width() - if width <= 0 { - return - } - - height := c.scr.Height() - if *c.lastRender == "" { - // We render the changes line by line to be able to get the cursor - // position using the width of each line. - var x int - for y := 0; y < height; y++ { - var line string - x, line = cellbuf.RenderLine(c.scr, y, cellbuf.WithRenderProfile(c.profile)) - c.buf.WriteString(line) - if y < height-1 { - x = 0 - c.buf.WriteString("\r\n") - } - } - - c.scr.cur.X, c.scr.cur.Y = x, height-1 - return - } - - // TODO: iterate over the dirty cells instead of the whole buffer. - // TODO: optimize continuous space-only segments i.e. concatenate them to - // erase the line instead of using spaces to erase the line. - for y := 0; y < height; y++ { - var seg *cellbuf.Segment - var segX int // The start position of the current segment. - var eraser bool // Whether we're erasing using spaces and no styles or links. - for x := 0; x < width; x++ { - cell := c.scr.Cell(x, y) - if cell.Width == 0 { - continue - } - - // Convert the cell to respect the current color profile. - // TODO: ?? - // cell.Style = cell.Style.Convert(c.profile) - // cell.Link = cell.Link.Convert(c.profile) - - if !c.scr.isDirty(x, y) { - if seg != nil { - erased := c.flushSegment(seg, image.Pt(segX, y), eraser) - seg = nil - if erased { - // If the segment erased the rest of the line, we don't need to - // render the rest of the line. - break - } - } - continue - } - - if seg == nil { - segX = x - eraser = cell.Equal(spaceCell) - seg = &cellbuf.Segment{ - Style: cell.Style, - Link: cell.Link, - Content: cell.Content, - Width: cell.Width, - } - continue - } - - if !seg.Style.Equal(cell.Style) || seg.Link != cell.Link { - erased := c.flushSegment(seg, image.Pt(segX, y), eraser) - if erased { - seg = nil - // If the segment erased the rest of the line, we don't need to - // render the rest of the line. - break - } - segX = x - eraser = cell.Equal(spaceCell) - seg = &cellbuf.Segment{ - Style: cell.Style, - Link: cell.Link, - Content: cell.Content, - Width: cell.Width, - } - continue - } - - eraser = eraser && cell.Equal(spaceCell) - seg.Content += cell.Content - seg.Width += cell.Width - } - - if seg != nil { - c.flushSegment(seg, image.Pt(segX, y), eraser) - seg = nil - } - } - - // Reset the style and hyperlink if necessary. - if c.link.URL != "" { - c.buf.WriteString(ansi.ResetHyperlink()) //nolint:errcheck - c.link.Reset() - } - if !c.pen.Empty() { - c.buf.WriteString(ansi.ResetStyle) //nolint:errcheck - c.pen.Reset() - } - - // Delete extra lines from previous render. - if c.lastHeight > height { - // Move the cursor to the last line of this render and erase the rest - // of the screen. - c.moveCursor(c.scr.cur.X, height-1) - c.buf.WriteString(ansi.EraseScreenBelow) + s.scr.MoveTo(msg.X, msg.Y) } } -// flushSegment flushes the segment to the buffer. It returns true if the -// segment the rest of the line was erased. -func (c *ferociousRenderer) flushSegment(seg *cellbuf.Segment, to image.Point, eraser bool) (erased bool) { - if c.scr.cur.Point != to { - c.renderReset(seg) - c.moveCursor(to.X, to.Y) - } - - // We use [ansi.EraseLineRight] to erase the rest of the line if the segment - // is an "eraser" i.e. it's just a bunch of spaces with no styles or links. We erase the - // rest of the line when: - // 1. The segment is an eraser. - // 2. The segment reaches the end of the line to erase i.e. the new line is shorter. - // 3. The segment takes more bytes than [ansi.EraseLineRight] to erase which is 4 bytes. - if eraser && to.Y < len(c.scr.linew) && seg.Width > 4 && (c.scr.linew)[to.Y] < seg.Width+to.X { - c.renderReset(seg) - c.buf.WriteString(ansi.EraseLineRight) //nolint:errcheck - erased = true - } else { - c.renderSegment(seg) - } - return -} - -func (c *ferociousRenderer) renderReset(seg *cellbuf.Segment) { - if seg.Link != c.link && c.link.URL != "" { - c.buf.WriteString(ansi.ResetHyperlink()) //nolint:errcheck - c.link.Reset() - } - if seg.Style.Empty() && !c.pen.Empty() { - c.buf.WriteString(ansi.ResetStyle) //nolint:errcheck - c.pen.Reset() - } -} - -func (c *ferociousRenderer) renderSegment(seg *cellbuf.Segment) { - isSpaces := strings.Trim(seg.Content, " ") == "" && c.pen.Empty() && seg.Style.Empty() - if !isSpaces && !seg.Style.Equal(c.pen) { - // We don't apply the style if the content is spaces. It's more efficient - // to just write the spaces. - c.buf.WriteString(seg.Style.DiffSequence(c.pen)) // nolint:errcheck - c.pen = seg.Style - } - if seg.Link != c.link { - c.buf.WriteString(ansi.SetHyperlink(seg.Link.URL, seg.Link.URLID)) // nolint:errcheck - c.link = seg.Link - } - - c.buf.WriteString(seg.Content) - c.scr.cur.X += seg.Width - - if c.scr.cur.X >= c.scr.Width() { - // NOTE: We need to reset the cursor when at phantom cell i.e. outside - // the screen, otherwise, the cursor position will be out of sync. - c.scr.cur.X = 0 - c.buf.WriteByte(ansi.CR) - } -} - -// moveCursor moves the cursor to the given position. -func (c *ferociousRenderer) moveCursor(x, y int) { - if c.scr.cur.X == x && c.scr.cur.Y == y { - return - } - - if c.altScreen { - // TODO: Optimize for small movements i.e. movements that cost less - // than 8 bytes in total. [ansi.MoveCursor] is at least 6 bytes long. - c.buf.WriteString(ansi.SetCursorPosition(x+1, y+1)) - } else { - if c.scr.cur.X < x { - dx := x - c.scr.cur.X - switch dx { - case 1: - // OPTIM: We write the cell content under the cursor if it's the same - // style and link. This is more efficient than moving the cursor which - // costs at least 3 bytes [ansi.CursorRight]. - cell := c.scr.Cell(c.scr.cur.X, c.scr.cur.Y) - if cell.Style.Equal(c.pen) && cell.Link == c.link { - c.buf.WriteString(cell.Content) - break - } - fallthrough - default: - c.buf.WriteString(ansi.CursorRight(dx)) - } - } else if c.scr.cur.X > x { - if x == 0 { - // We use [ansi.CR] instead of [ansi.CursorLeft] to avoid - // writing multiple bytes. - c.buf.WriteByte(ansi.CR) - } else { - dx := c.scr.cur.X - x - if dx >= 3 { - // [ansi.CursorLeft] is at least 3 bytes long, so we use [ansi.BS] - // when we can to avoid writing more bytes than necessary. - c.buf.WriteString(ansi.CursorLeft(dx)) - } else { - c.buf.WriteString(strings.Repeat("\b", dx)) - } - } - } - if c.scr.cur.Y < y { - dy := y - c.scr.cur.Y - if dy >= 3 { - // [ansi.CursorDown] is at least 3 bytes long, so we use "\n" when - // we can to avoid writing more bytes than necessary. - c.buf.WriteString(ansi.CursorDown(dy)) - } else { - c.buf.WriteString(strings.Repeat("\n", dy)) - } - } else if c.scr.cur.Y > y { - dy := c.scr.cur.Y - y - c.buf.WriteString(ansi.CursorUp(dy)) - } - } - - c.scr.cur.X, c.scr.cur.Y = x, y +func (s *screenRenderer) repaint() { + s.lastFrame = "" } diff --git a/go.mod b/go.mod index 971dd3cf9b..dce56386af 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,11 @@ module github.com/charmbracelet/bubbletea/v2 go 1.18 require ( - github.com/charmbracelet/colorprofile v0.1.8 + github.com/charmbracelet/colorprofile v0.1.9 github.com/charmbracelet/x/ansi v0.6.0 - github.com/charmbracelet/x/cellbuf v0.0.6 - github.com/charmbracelet/x/input v0.2.1-0.20241210230033-45c33cb7b57a + github.com/charmbracelet/x/cellbuf v0.0.7-0.20241209212528-0eec74ecaa6f + github.com/charmbracelet/x/input v0.3.0 github.com/charmbracelet/x/term v0.2.1 - github.com/charmbracelet/x/vt v0.0.0-20241121165045-a3720547cbb4 github.com/muesli/cancelreader v0.2.2 golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 diff --git a/go.sum b/go.sum index 13d75fd36a..56d6446b7e 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,13 @@ -github.com/charmbracelet/colorprofile v0.1.8 h1:PywDeXsiAzlPtkiiKgMEVLvb6nlEuKrMj9+FJBtj4jU= -github.com/charmbracelet/colorprofile v0.1.8/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= +github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU9pGu1jkZxLw= +github.com/charmbracelet/colorprofile v0.1.9/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.6 h1:pJUWN/G1jbt1Nj/+ILfC2/ABQoZzWu1vG73yHQEYELI= -github.com/charmbracelet/x/cellbuf v0.0.6/go.mod h1:d72o71glp8flkCz54PHLe3+nuw5u2v3UxmKqruUERWQ= -github.com/charmbracelet/x/input v0.2.1-0.20241210230033-45c33cb7b57a h1:CspqRQ5YjX8qE/3/QefJf3twGokKeM42I/fZmx72H90= -github.com/charmbracelet/x/input v0.2.1-0.20241210230033-45c33cb7b57a/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241209212528-0eec74ecaa6f h1:r/BMib+AQerOPAa39CuiTwfkk0G1CGQsquiSQ24I30E= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241209212528-0eec74ecaa6f/go.mod h1:EfpoBnbE9VTpF6D2jVg4YFDzL6Vzk+1O+LGTU6OMV7E= +github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= +github.com/charmbracelet/x/input v0.3.0/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= 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/x/vt v0.0.0-20241121165045-a3720547cbb4 h1:EacjHxcQEEgOZ7TbkAU3b84hd1Bn5NwA8YV5uyJ9EI4= -github.com/charmbracelet/x/vt v0.0.0-20241121165045-a3720547cbb4/go.mod h1:1/jFoHl7/I4br0StC9OXXEondkK9qi3nUtKoqI35HcI= github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 h1:14czE6R5CgOlvONsJYa2B1uTyLvXzGXpBqw2AyZeTh4= github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32/go.mod h1:hyua5CY63kyl7IfyIxv1SjVEqoKze/XmDkEglItuVjA= github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw= diff --git a/tea.go b/tea.go index 9907dfe52b..116f3b9667 100644 --- a/tea.go +++ b/tea.go @@ -736,7 +736,7 @@ func (p *Program) Run() (Model, error) { if p.renderer == nil { // If no renderer is set use the ferocious one. if p.startupOptions&withFerociousRenderer != 0 || p.exp.has(experimentalFerocious) { - p.renderer = newFerociousRenderer(p.profile) + p.renderer = newScreenRenderer(p.profile, p.getenv("TERM")) } else { p.renderer = newStandardRenderer(p.profile) } @@ -751,11 +751,13 @@ func (p *Program) Run() (Model, error) { return p.initialModel, err } - // Send the initial size to the program. var resizeMsg WindowSizeMsg resizeMsg.Width = w resizeMsg.Height = h + + // Send the initial size to the program. go p.Send(resizeMsg) + p.renderer.update(WindowSizeMsg{Width: w, Height: h}) } // Init the input reader and initial model. @@ -768,7 +770,6 @@ func (p *Program) Run() (Model, error) { // Hide the cursor before starting the renderer. p.modes[ansi.TextCursorEnableMode] = false - p.execute(ansi.HideCursor) p.renderer.update(disableMode(ansi.TextCursorEnableMode)) // Honor program startup options. @@ -776,7 +777,6 @@ func (p *Program) Run() (Model, error) { p.execute(ansi.SetWindowTitle(p.startupTitle)) } if p.startupOptions&withAltScreen != 0 { - p.execute(ansi.SetAltScreenSaveCursorMode) p.modes[ansi.AltScreenSaveCursorMode] = true p.renderer.update(enableMode(ansi.AltScreenSaveCursorMode)) } @@ -978,19 +978,12 @@ func (p *Program) RestoreTerminal() error { if err := p.initInputReader(); err != nil { return err } - if p.modes[ansi.AltScreenSaveCursorMode] { - p.execute(ansi.SetAltScreenSaveCursorMode) - } else { + if !p.modes[ansi.AltScreenSaveCursorMode] { // entering alt screen already causes a repaint. go p.Send(repaintMsg{}) } p.startRenderer() - if !p.modes[ansi.TextCursorEnableMode] { - p.execute(ansi.HideCursor) - } else { - p.execute(ansi.ShowCursor) - } if p.modes[ansi.BracketedPasteMode] { p.execute(ansi.SetBracketedPasteMode) } diff --git a/tty.go b/tty.go index e6dbc0467f..d1d5985407 100644 --- a/tty.go +++ b/tty.go @@ -41,9 +41,6 @@ func (p *Program) restoreTerminalState() error { if p.modes[ansi.BracketedPasteMode] { p.execute(ansi.ResetBracketedPasteMode) } - if !p.modes[ansi.TextCursorEnableMode] { - p.execute(ansi.ShowCursor) - } if p.modes[ansi.ButtonEventMouseMode] || p.modes[ansi.AnyEventMouseMode] { p.execute(ansi.ResetButtonEventMouseMode) p.execute(ansi.ResetAnyEventMouseMode) @@ -61,16 +58,6 @@ func (p *Program) restoreTerminalState() error { if p.modes[ansi.GraphemeClusteringMode] { p.execute(ansi.ResetGraphemeClusteringMode) } - if p.modes[ansi.AltScreenSaveCursorMode] { - p.execute(ansi.ResetAltScreenSaveCursorMode) - // cmd.exe and other terminals keep separate cursor states for the AltScreen - // and the main buffer. We have to explicitly reset the cursor visibility - // whenever we exit AltScreen. - p.execute(ansi.ShowCursor) - - // give the terminal a moment to catch up - time.Sleep(time.Millisecond * 10) //nolint:gomnd - } // Restore terminal colors. if p.setBg != nil { From aed1a5302e23177addb2e687e021bf50fe4c43da Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 10 Dec 2024 16:39:32 -0500 Subject: [PATCH 07/30] chore: bump cellbuf --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 032686a47e..40bea507b4 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,7 +23,7 @@ require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.7-0.20241209212528-0eec74ecaa6f // indirect + github.com/charmbracelet/x/cellbuf v0.0.7-0.20241210230033-45c33cb7b57a // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect diff --git a/examples/go.sum b/examples/go.sum index 5d3e33341d..0df9205d85 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -28,8 +28,8 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be480 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241209212528-0eec74ecaa6f h1:r/BMib+AQerOPAa39CuiTwfkk0G1CGQsquiSQ24I30E= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241209212528-0eec74ecaa6f/go.mod h1:EfpoBnbE9VTpF6D2jVg4YFDzL6Vzk+1O+LGTU6OMV7E= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241210230033-45c33cb7b57a h1:imuGlkCuGqYU0Usj2uMGSwLMTlxn5zyJ5RACVTysqiI= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241210230033-45c33cb7b57a/go.mod h1:EfpoBnbE9VTpF6D2jVg4YFDzL6Vzk+1O+LGTU6OMV7E= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U= From c12bdddd447f39d4c0632dd469db2e91fdf0440d Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 12 Dec 2024 10:58:42 -0500 Subject: [PATCH 08/30] chore: update deps --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 40bea507b4..26b034e835 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,7 +23,7 @@ require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.7-0.20241210230033-45c33cb7b57a // indirect + github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect diff --git a/examples/go.sum b/examples/go.sum index 0df9205d85..c79a2de3b4 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -28,8 +28,8 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be480 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241210230033-45c33cb7b57a h1:imuGlkCuGqYU0Usj2uMGSwLMTlxn5zyJ5RACVTysqiI= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241210230033-45c33cb7b57a/go.mod h1:EfpoBnbE9VTpF6D2jVg4YFDzL6Vzk+1O+LGTU6OMV7E= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 h1:Ip9o3gP+Af0y3PdijNgsIb9NrEw5YQFUyzfPyUaty40= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U= diff --git a/go.mod b/go.mod index dce56386af..e97153513b 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/charmbracelet/colorprofile v0.1.9 github.com/charmbracelet/x/ansi v0.6.0 - github.com/charmbracelet/x/cellbuf v0.0.7-0.20241209212528-0eec74ecaa6f + github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 github.com/charmbracelet/x/input v0.3.0 github.com/charmbracelet/x/term v0.2.1 github.com/muesli/cancelreader v0.2.2 diff --git a/go.sum b/go.sum index 56d6446b7e..c624102b32 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU github.com/charmbracelet/colorprofile v0.1.9/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241209212528-0eec74ecaa6f h1:r/BMib+AQerOPAa39CuiTwfkk0G1CGQsquiSQ24I30E= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241209212528-0eec74ecaa6f/go.mod h1:EfpoBnbE9VTpF6D2jVg4YFDzL6Vzk+1O+LGTU6OMV7E= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 h1:Ip9o3gP+Af0y3PdijNgsIb9NrEw5YQFUyzfPyUaty40= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= github.com/charmbracelet/x/input v0.3.0/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= From 8014f47a3b80fb5b3caa02430e9b066f26570e56 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 12 Dec 2024 13:08:01 -0500 Subject: [PATCH 09/30] chore: update go.mod and go.sum --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e97153513b..91935c972a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/charmbracelet/colorprofile v0.1.9 github.com/charmbracelet/x/ansi v0.6.0 - github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 + github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212170349-ad4b7ae0f25f github.com/charmbracelet/x/input v0.3.0 github.com/charmbracelet/x/term v0.2.1 github.com/muesli/cancelreader v0.2.2 diff --git a/go.sum b/go.sum index c624102b32..89a4b608e7 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU github.com/charmbracelet/colorprofile v0.1.9/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 h1:Ip9o3gP+Af0y3PdijNgsIb9NrEw5YQFUyzfPyUaty40= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212170349-ad4b7ae0f25f h1:rHJ4sjKCm7d4Mdpv64ahyy4MZNRQBToFNUvk0a6405M= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212170349-ad4b7ae0f25f/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= github.com/charmbracelet/x/input v0.3.0/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= From 04c0b438d1a920a50fb6f09f19bcc93a8232b327 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 12 Dec 2024 13:08:27 -0500 Subject: [PATCH 10/30] refactor: use the cursed renderer for rendering and remove the standard renderer This also removes the experimental flags and changes the renderer interface to be more explicit about the methods that are available. --- ferocious_renderer.go | 119 ++++++------ nil_renderer.go | 35 +++- options.go | 19 -- renderer.go | 81 ++++++++- screen.go | 48 ++--- standard_renderer.go | 408 ------------------------------------------ tea.go | 145 ++++++++------- tty.go | 13 +- 8 files changed, 284 insertions(+), 584 deletions(-) delete mode 100644 standard_renderer.go diff --git a/ferocious_renderer.go b/ferocious_renderer.go index e66879a4de..92f9b0aaa3 100644 --- a/ferocious_renderer.go +++ b/ferocious_renderer.go @@ -6,7 +6,6 @@ import ( "sync" "github.com/charmbracelet/colorprofile" - "github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/cellbuf" ) @@ -24,10 +23,11 @@ type screenRenderer struct { var _ renderer = &screenRenderer{} -func newScreenRenderer(p colorprofile.Profile, term string) (s *screenRenderer) { +func newScreenRenderer(w io.Writer, term string) (s *screenRenderer) { s = new(screenRenderer) + s.w = w s.term = term - s.profile = p + s.reset() return } @@ -52,6 +52,12 @@ func (s *screenRenderer) render(frame string) { s.lastFrame = frame if !s.altScreen { + // Inline mode resizes the screen based on the frame height and + // terminal width. This is because the frame height can change based on + // the content of the frame. For example, if the frame contains a list + // of items, the height of the frame will be the number of items in the + // list. This is different from the alt screen buffer, which has a + // fixed height and width. frameHeight := strings.Count(frame, "\n") + 1 s.scr.Resize(s.width, frameHeight) } @@ -72,56 +78,69 @@ func (s *screenRenderer) reset() { }) } -// update implements renderer. -func (s *screenRenderer) update(msg Msg) { - switch msg := msg.(type) { - case ColorProfileMsg: - s.profile = msg.Profile - case WindowSizeMsg: - s.width, s.height = msg.Width, msg.Height - if s.altScreen { - // Resize alternate screen - s.scr.Resize(s.width, s.height) - } - case clearScreenMsg: - s.scr.Clear() - s.repaint() - case repaintMsg: - s.repaint() - case rendererWriter: - s.w = msg.Writer - s.reset() - case enableModeMsg: - switch ansi.Mode(ansi.DECMode(msg)) { - case ansi.AltScreenSaveCursorMode: - s.altScreen = true - s.scr.EnterAltScreen() - s.scr.SetRelativeCursor(!s.altScreen) - s.scr.Resize(s.width, s.height) - s.repaint() - case ansi.TextCursorEnableMode: - s.cursorHidden = false - s.scr.ShowCursor() - } - case disableModeMsg: - switch ansi.Mode(ansi.DECMode(msg)) { - case ansi.AltScreenSaveCursorMode: - s.altScreen = false - s.scr.ExitAltScreen() - s.scr.SetRelativeCursor(!s.altScreen) - s.scr.Resize(s.width, strings.Count(s.lastFrame, "\n")+1) - s.repaint() - case ansi.TextCursorEnableMode: - s.cursorHidden = true - s.scr.HideCursor() - } - case printLineMessage: - s.scr.InsertAbove(msg.messageBody) - case setCursorPosMsg: - s.scr.MoveTo(msg.X, msg.Y) +// setColorProfile implements renderer. +func (s *screenRenderer) setColorProfile(p colorprofile.Profile) { + s.profile = p + s.scr.SetColorProfile(p) +} + +// resize implements renderer. +func (s *screenRenderer) resize(w, h int) { + s.width, s.height = w, h + if s.altScreen { + // We only resize the screen if we're in the alt screen buffer. Inline + // mode resizes the screen based on the frame height and terminal + // width. See [screenRenderer.render] for more details. + s.scr.Resize(s.width, s.height) } } +// clearScreen implements renderer. +func (s *screenRenderer) clearScreen() { + s.scr.Clear() + s.repaint() +} + +// enterAltScreen implements renderer. +func (s *screenRenderer) enterAltScreen() { + s.altScreen = true + s.scr.EnterAltScreen() + s.scr.SetRelativeCursor(!s.altScreen) + s.scr.Resize(s.width, s.height) + s.repaint() +} + +// exitAltScreen implements renderer. +func (s *screenRenderer) exitAltScreen() { + s.altScreen = false + s.scr.ExitAltScreen() + s.scr.SetRelativeCursor(!s.altScreen) + s.scr.Resize(s.width, strings.Count(s.lastFrame, "\n")+1) + s.repaint() +} + +// showCursor implements renderer. +func (s *screenRenderer) showCursor() { + s.cursorHidden = false + s.scr.ShowCursor() +} + +// hideCursor implements renderer. +func (s *screenRenderer) hideCursor() { + s.cursorHidden = true + s.scr.HideCursor() +} + +// insertAbove implements renderer. +func (s *screenRenderer) insertAbove(lines string) { + s.scr.InsertAbove(lines) +} + +// moveTo implements renderer. +func (s *screenRenderer) moveTo(x, y int) { + s.scr.MoveTo(x, y) +} + func (s *screenRenderer) repaint() { s.lastFrame = "" } diff --git a/nil_renderer.go b/nil_renderer.go index c6d03cd8a8..ed7341bd03 100644 --- a/nil_renderer.go +++ b/nil_renderer.go @@ -1,11 +1,43 @@ package tea +import "github.com/charmbracelet/colorprofile" + // nilRenderer is a no-op renderer. It implements the Renderer interface but // doesn't render anything to the terminal. type nilRenderer struct{} var _ renderer = nilRenderer{} +// clearScreen implements renderer. +func (n nilRenderer) clearScreen() {} + +// repaint implements renderer. +func (n nilRenderer) repaint() {} + +// enterAltScreen implements renderer. +func (n nilRenderer) enterAltScreen() {} + +// exitAltScreen implements renderer. +func (n nilRenderer) exitAltScreen() {} + +// hideCursor implements renderer. +func (n nilRenderer) hideCursor() {} + +// insertAbove implements renderer. +func (n nilRenderer) insertAbove(string) {} + +// moveTo implements renderer. +func (n nilRenderer) moveTo(int, int) {} + +// resize implements renderer. +func (n nilRenderer) resize(int, int) {} + +// setColorProfile implements renderer. +func (n nilRenderer) setColorProfile(colorprofile.Profile) {} + +// showCursor implements renderer. +func (n nilRenderer) showCursor() {} + // flush implements the Renderer interface. func (nilRenderer) flush() error { return nil } @@ -17,6 +49,3 @@ func (nilRenderer) render(string) {} // reset implements the Renderer interface. func (nilRenderer) reset() {} - -// update implements the Renderer interface. -func (nilRenderer) update(Msg) {} diff --git a/options.go b/options.go index c0ae254158..235bf6708d 100644 --- a/options.go +++ b/options.go @@ -271,25 +271,6 @@ func WithGraphemeClustering() ProgramOption { } } -// experimentalOptions are experimental features that are not yet stable. These -// features may change or be removed in future versions. -type experimentalOptions []string - -// has returns true if the experimental option is enabled. -func (e experimentalOptions) has(option string) bool { - for _, o := range e { - if o == option { - return true - } - } - return false -} - -const ( - // Ferocious enables the "ferocious" renderer. - experimentalFerocious = "ferocious" -) - // WithColorProfile sets the color profile that the program will use. This is // useful when you want to force a specific color profile. By default, Bubble // Tea will try to detect the terminal's color profile from environment diff --git a/renderer.go b/renderer.go index 871e9391c0..ae8738e37f 100644 --- a/renderer.go +++ b/renderer.go @@ -1,6 +1,17 @@ package tea -import "io" +import ( + "fmt" + + "github.com/charmbracelet/colorprofile" +) + +const ( + // defaultFramerate specifies the maximum interval at which we should + // update the view. + defaultFPS = 60 + maxFPS = 120 +) // renderer is the interface for Bubble Tea renderers. type renderer interface { @@ -16,15 +27,71 @@ type renderer interface { // reset resets the renderer's state to its initial state. reset() - // update updates the renderer's state with the given message. It returns a - // [tea.Cmd] that can be used to send messages back to the program. - update(Msg) + // insertAbove inserts unmanaged lines above the renderer. + insertAbove(string) + + // enterAltScreen enters the alternate screen buffer. + enterAltScreen() + + // exitAltScreen exits the alternate screen buffer. + exitAltScreen() + + // showCursor shows the cursor. + showCursor() + + // hideCursor hides the cursor. + hideCursor() + + // resize notify the renderer of a terminal resize. + resize(int, int) + + // setColorProfile sets the color profile. + setColorProfile(colorprofile.Profile) + + // moveTo moves the cursor to the given position. + moveTo(int, int) + + // clearScreen clears the screen. + clearScreen() + + // repaint forces a full repaint. + repaint() } // repaintMsg forces a full repaint. type repaintMsg struct{} -// rendererWriter is an internal message used to set the output of the renderer. -type rendererWriter struct { - io.Writer +type printLineMessage struct { + messageBody string +} + +// Println prints above the Program. This output is unmanaged by the program and +// will persist across renders by the Program. +// +// Unlike fmt.Println (but similar to log.Println) the message will be print on +// its own line. +// +// If the altscreen is active no output will be printed. +func Println(args ...interface{}) Cmd { + return func() Msg { + return printLineMessage{ + messageBody: fmt.Sprint(args...), + } + } +} + +// Printf prints above the Program. It takes a format template followed by +// values similar to fmt.Printf. This output is unmanaged by the program and +// will persist across renders by the Program. +// +// Unlike fmt.Printf (but similar to log.Printf) the message will be print on +// its own line. +// +// If the altscreen is active no output will be printed. +func Printf(template string, args ...interface{}) Cmd { + return func() Msg { + return printLineMessage{ + messageBody: fmt.Sprintf(template, args...), + } + } } diff --git a/screen.go b/screen.go index 8cb193c590..d7c0d3599f 100644 --- a/screen.go +++ b/screen.go @@ -32,7 +32,7 @@ type clearScreenMsg struct{} // model's Init function. To initialize your program with the altscreen enabled // use the WithAltScreen ProgramOption instead. func EnterAltScreen() Msg { - return enableMode(ansi.AltScreenSaveCursorMode) + return enableModeMsg{ansi.AltScreenSaveCursorMode} } // ExitAltScreen is a special command that tells the Bubble Tea program to exit @@ -42,7 +42,7 @@ func EnterAltScreen() Msg { // Note that the alternate screen buffer will be automatically exited when the // program quits. func ExitAltScreen() Msg { - return disableMode(ansi.AltScreenSaveCursorMode) + return disableModeMsg{ansi.AltScreenSaveCursorMode} } // EnableMouseCellMotion is a special command that enables mouse click, @@ -53,8 +53,8 @@ func ExitAltScreen() Msg { // model's Init function. Use the WithMouseCellMotion ProgramOption instead. func EnableMouseCellMotion() Msg { return sequenceMsg{ - func() Msg { return enableMode(ansi.ButtonEventMouseMode) }, - func() Msg { return enableMode(ansi.SgrExtMouseMode) }, + func() Msg { return enableModeMsg{ansi.ButtonEventMouseMode} }, + func() Msg { return enableModeMsg{ansi.SgrExtMouseMode} }, } } @@ -69,17 +69,17 @@ func EnableMouseCellMotion() Msg { // model's Init function. Use the WithMouseAllMotion ProgramOption instead. func EnableMouseAllMotion() Msg { return sequenceMsg{ - func() Msg { return enableMode(ansi.AnyEventMouseMode) }, - func() Msg { return enableMode(ansi.SgrExtMouseMode) }, + func() Msg { return enableModeMsg{ansi.AnyEventMouseMode} }, + func() Msg { return enableModeMsg{ansi.SgrExtMouseMode} }, } } // DisableMouse is a special command that stops listening for mouse events. func DisableMouse() Msg { return sequenceMsg{ - func() Msg { return disableMode(ansi.ButtonEventMouseMode) }, - func() Msg { return disableMode(ansi.AnyEventMouseMode) }, - func() Msg { return disableMode(ansi.SgrExtMouseMode) }, + func() Msg { return disableModeMsg{ansi.ButtonEventMouseMode} }, + func() Msg { return disableModeMsg{ansi.AnyEventMouseMode} }, + func() Msg { return disableModeMsg{ansi.SgrExtMouseMode} }, } } @@ -88,13 +88,13 @@ func DisableMouse() Msg { // to show the cursor, which is normally hidden for the duration of a Bubble // Tea program's lifetime. You will most likely not need to use this command. func HideCursor() Msg { - return disableMode(ansi.TextCursorEnableMode) + return disableModeMsg{ansi.TextCursorEnableMode} } // ShowCursor is a special command for manually instructing Bubble Tea to show // the cursor. func ShowCursor() Msg { - return enableMode(ansi.TextCursorEnableMode) + return enableModeMsg{ansi.TextCursorEnableMode} } // EnableBracketedPaste is a special command that tells the Bubble Tea program @@ -103,7 +103,7 @@ func ShowCursor() Msg { // Note that bracketed paste will be automatically disabled when the // program quits. func EnableBracketedPaste() Msg { - return enableMode(ansi.BracketedPasteMode) + return enableModeMsg{ansi.BracketedPasteMode} } // DisableBracketedPaste is a special command that tells the Bubble Tea program @@ -112,42 +112,32 @@ func EnableBracketedPaste() Msg { // Note that bracketed paste will be automatically disabled when the // program quits. func DisableBracketedPaste() Msg { - return disableMode(ansi.BracketedPasteMode) + return disableModeMsg{ansi.BracketedPasteMode} } // EnableGraphemeClustering is a special command that tells the Bubble Tea // program to enable grapheme clustering. This is enabled by default. func EnableGraphemeClustering() Msg { - return enableMode(ansi.GraphemeClusteringMode) + return enableModeMsg{ansi.GraphemeClusteringMode} } // DisableGraphemeClustering is a special command that tells the Bubble Tea // program to disable grapheme clustering. This mode will be disabled // automatically when the program quits. func DisableGraphemeClustering() Msg { - return disableMode(ansi.GraphemeClusteringMode) + return disableModeMsg{ansi.GraphemeClusteringMode} } // EnabledReportFocus is a special command that tells the Bubble Tea program // to enable focus reporting. -func EnabledReportFocus() Msg { return enableMode(ansi.FocusEventMode) } +func EnabledReportFocus() Msg { return enableModeMsg{ansi.FocusEventMode} } // DisabledReportFocus is a special command that tells the Bubble Tea program // to disable focus reporting. -func DisabledReportFocus() Msg { return disableMode(ansi.FocusEventMode) } +func DisabledReportFocus() Msg { return disableModeMsg{ansi.FocusEventMode} } // enableModeMsg is an internal message that signals to set a terminal mode. -type enableModeMsg ansi.DECMode - -// enableMode is an internal command that signals to set a terminal mode. -func enableMode(mode ansi.DECMode) Msg { - return enableModeMsg(mode) -} +type enableModeMsg struct{ ansi.Mode } // disableModeMsg is an internal message that signals to unset a terminal mode. -type disableModeMsg ansi.DECMode - -// disableMode is an internal command that signals to unset a terminal mode. -func disableMode(mode ansi.DECMode) Msg { - return disableModeMsg(mode) -} +type disableModeMsg struct{ ansi.Mode } diff --git a/standard_renderer.go b/standard_renderer.go deleted file mode 100644 index 465ad491fa..0000000000 --- a/standard_renderer.go +++ /dev/null @@ -1,408 +0,0 @@ -package tea - -import ( - "bytes" - "fmt" - "io" - "strings" - "sync" - - "github.com/charmbracelet/colorprofile" - "github.com/charmbracelet/x/ansi" -) - -const ( - // defaultFramerate specifies the maximum interval at which we should - // update the view. - defaultFPS = 60 - maxFPS = 120 -) - -// standardRenderer is a framerate-based terminal renderer, updating the view -// at a given framerate to avoid overloading the terminal emulator. -// -// In cases where very high performance is needed the renderer can be told -// to exclude ranges of lines, allowing them to be written to directly. -type standardRenderer struct { - mtx *sync.Mutex - out io.Writer - - // the color profile to use - profile colorprofile.Profile - - buf bytes.Buffer - queuedMessageLines []string - lastRender string - lastRenderedLines []string - linesRendered int - altLinesRendered int - useANSICompressor bool - once sync.Once - - // cursor visibility state - cursorHidden bool - - // essentially whether or not we're using the full size of the terminal - altScreenActive bool - - // renderer dimensions; usually the size of the window - width int - height int - - // lines explicitly set not to render - ignoreLines map[int]struct{} -} - -// newStandardRenderer creates a new renderer. Normally you'll want to initialize it -// with os.Stdout as the first argument. -func newStandardRenderer(p colorprofile.Profile) renderer { - r := &standardRenderer{ - mtx: &sync.Mutex{}, - queuedMessageLines: []string{}, - profile: p, - } - return r -} - -// setOutput sets the output for the renderer. -func (r *standardRenderer) setOutput(out io.Writer) { - r.mtx.Lock() - r.out = &colorprofile.Writer{ - Forward: out, - Profile: r.profile, - } - r.mtx.Unlock() -} - -// close closes the renderer and flushes any remaining data. -func (r *standardRenderer) close() (err error) { - // Move the cursor back to the beginning of the line - // NOTE: execute locks the mutex - r.execute(ansi.EraseEntireLine + "\r") - - return -} - -// execute writes the given sequence to the output. -func (r *standardRenderer) execute(seq string) { - r.mtx.Lock() - _, _ = io.WriteString(r.out, seq) - r.mtx.Unlock() -} - -// flush renders the buffer. -func (r *standardRenderer) flush() (err error) { - r.mtx.Lock() - defer r.mtx.Unlock() - - if r.buf.Len() == 0 || r.buf.String() == r.lastRender { - // Nothing to do. - return - } - - // Output buffer. - buf := &bytes.Buffer{} - - // Moving to the beginning of the section, that we rendered. - if r.altScreenActive { - buf.WriteString(ansi.CursorHomePosition) - } else if r.linesRendered > 1 { - buf.WriteString(ansi.CursorUp(r.linesRendered - 1)) - } - - newLines := strings.Split(r.buf.String(), "\n") - - // If we know the output's height, we can use it to determine how many - // lines we can render. We drop lines from the top of the render buffer if - // necessary, as we can't navigate the cursor into the terminal's scrollback - // buffer. - if r.height > 0 && len(newLines) > r.height { - newLines = newLines[len(newLines)-r.height:] - } - - flushQueuedMessages := len(r.queuedMessageLines) > 0 && !r.altScreenActive - - if flushQueuedMessages { - // Dump the lines we've queued up for printing. - for _, line := range r.queuedMessageLines { - if ansi.StringWidth(line) < r.width { - // We only erase the rest of the line when the line is shorter than - // the width of the terminal. When the cursor reaches the end of - // the line, any escape sequences that follow will only affect the - // last cell of the line. - - // Removing previously rendered content at the end of line. - line = line + ansi.EraseLineRight - } - - _, _ = buf.WriteString(line) - _, _ = buf.WriteString("\r\n") - } - // Clear the queued message lines. - r.queuedMessageLines = []string{} - } - - // Paint new lines. - for i := 0; i < len(newLines); i++ { - canSkip := !flushQueuedMessages && // Queuing messages triggers repaint -> we don't have access to previous frame content. - len(r.lastRenderedLines) > i && r.lastRenderedLines[i] == newLines[i] // Previously rendered line is the same. - - if _, ignore := r.ignoreLines[i]; ignore || canSkip { - // Unless this is the last line, move the cursor down. - if i < len(newLines)-1 { - buf.WriteString(ansi.CUD1) - } - continue - } - - if i == 0 && r.lastRender == "" { - // On first render, reset the cursor to the start of the line - // before writing anything. - buf.WriteByte('\r') - } - - line := newLines[i] - - // Truncate lines wider than the width of the window to avoid - // wrapping, which will mess up rendering. If we don't have the - // width of the window this will be ignored. - // - // Note that on Windows we only get the width of the window on - // program initialization, so after a resize this won't perform - // correctly (signal SIGWINCH is not supported on Windows). - if r.width > 0 { - line = ansi.Truncate(line, r.width, "") - } - - if ansi.StringWidth(line) < r.width { - // We only erase the rest of the line when the line is shorter than - // the width of the terminal. When the cursor reaches the end of - // the line, any escape sequences that follow will only affect the - // last cell of the line. - - // Removing previously rendered content at the end of line. - line = line + ansi.EraseLineRight - } - - _, _ = buf.WriteString(line) - - if i < len(newLines)-1 { - _, _ = buf.WriteString("\r\n") - } - } - - // Clearing left over content from last render. - if r.lastLinesRendered() > len(newLines) { - buf.WriteString(ansi.EraseScreenBelow) - } - - if r.altScreenActive { - r.altLinesRendered = len(newLines) - } else { - r.linesRendered = len(newLines) - } - - // Make sure the cursor is at the start of the last line to keep rendering - // behavior consistent. - if r.altScreenActive { - // This case fixes a bug in macOS terminal. In other terminals the - // other case seems to do the job regardless of whether or not we're - // using the full terminal window. - buf.WriteString(ansi.CursorPosition(0, len(newLines))) - } else { - buf.WriteString(ansi.CursorBackward(r.width)) - } - - _, err = r.out.Write(buf.Bytes()) - r.lastRender = r.buf.String() - - // Save previously rendered lines for comparison in the next render. If we - // don't do this, we can't skip rendering lines that haven't changed. - // See https://github.com/charmbracelet/bubbletea/pull/1233 - r.lastRenderedLines = newLines - r.buf.Reset() - return -} - -// lastLinesRendered returns the number of lines rendered lastly. -func (r *standardRenderer) lastLinesRendered() int { - if r.altScreenActive { - return r.altLinesRendered - } - return r.linesRendered -} - -// render renders the frame to the internal buffer. The buffer will be -// outputted via the ticker which calls flush(). -func (r *standardRenderer) render(s string) { - r.mtx.Lock() - defer r.mtx.Unlock() - r.buf.Reset() - - // If an empty string was passed we should clear existing output and - // rendering nothing. Rather than introduce additional state to manage - // this, we render a single space as a simple (albeit less correct) - // solution. - if s == "" { - s = " " - } - - _, _ = r.buf.WriteString(s) -} - -// repaint forces a full repaint. -func (r *standardRenderer) repaint() { - r.lastRender = "" - r.lastRenderedLines = nil -} - -// reset resets the standardRenderer to its initial state. -func (r *standardRenderer) reset() { - r.repaint() -} - -func (r *standardRenderer) clearScreen() { - r.execute(ansi.EraseEntireScreen + ansi.HomeCursorPosition) - r.repaint() -} - -// setAltScreenBuffer restores the terminal screen buffer state. -func (r *standardRenderer) setAltScreenBuffer(on bool) { - if on { - // Ensure that the terminal is cleared, even when it doesn't support - // alt screen (or alt screen support is disabled, like GNU screen by - // default). - r.execute(ansi.EraseEntireScreen) - r.execute(ansi.HomeCursorPosition) - } - - // cmd.exe and other terminals keep separate cursor states for the AltScreen - // and the main buffer. We have to explicitly reset the cursor visibility - // whenever we exit AltScreen. - if r.cursorHidden { - r.execute(ansi.HideCursor) - } else { - r.execute(ansi.ShowCursor) - } -} - -// update handles internal messages for the renderer. -func (r *standardRenderer) update(msg Msg) { - switch msg := msg.(type) { - case ColorProfileMsg: - r.profile = msg.Profile - - case enableModeMsg: - switch ansi.DECMode(msg) { - case ansi.AltScreenSaveCursorMode: - if r.altScreenActive { - return - } - - r.setAltScreenBuffer(true) - r.altScreenActive = true - r.repaint() - case ansi.TextCursorEnableMode: - if !r.cursorHidden { - return - } - - r.cursorHidden = false - } - - case disableModeMsg: - switch ansi.DECMode(msg) { - case ansi.AltScreenSaveCursorMode: - if !r.altScreenActive { - return - } - - r.setAltScreenBuffer(false) - r.altScreenActive = false - r.repaint() - case ansi.TextCursorEnableMode: - if r.cursorHidden { - return - } - - r.cursorHidden = true - } - - case rendererWriter: - r.setOutput(msg.Writer) - - case WindowSizeMsg: - r.resize(msg.Width, msg.Height) - - case clearScreenMsg: - r.clearScreen() - - case printLineMessage: - r.insertAbove(msg.messageBody) - - case repaintMsg: - // Force a repaint by clearing the render cache as we slide into a - // render. - r.mtx.Lock() - r.repaint() - r.mtx.Unlock() - } -} - -// resize sets the size of the terminal. -func (r *standardRenderer) resize(w int, h int) { - r.mtx.Lock() - r.width = w - r.height = h - r.repaint() - r.mtx.Unlock() -} - -// insertAbove inserts lines above the current frame. This only works in -// inline mode. -func (r *standardRenderer) insertAbove(s string) { - if r.altScreenActive { - return - } - - lines := strings.Split(s, "\n") - r.mtx.Lock() - r.queuedMessageLines = append(r.queuedMessageLines, lines...) - r.repaint() - r.mtx.Unlock() -} - -type printLineMessage struct { - messageBody string -} - -// Println prints above the Program. This output is unmanaged by the program and -// will persist across renders by the Program. -// -// Unlike fmt.Println (but similar to log.Println) the message will be print on -// its own line. -// -// If the altscreen is active no output will be printed. -func Println(args ...interface{}) Cmd { - return func() Msg { - return printLineMessage{ - messageBody: fmt.Sprint(args...), - } - } -} - -// Printf prints above the Program. It takes a format template followed by -// values similar to fmt.Printf. This output is unmanaged by the program and -// will persist across renders by the Program. -// -// Unlike fmt.Printf (but similar to log.Printf) the message will be print on -// its own line. -// -// If the altscreen is active no output will be printed. -func Printf(template string, args ...interface{}) Cmd { - return func() Msg { - return printLineMessage{ - messageBody: fmt.Sprintf(template, args...), - } - } -} diff --git a/tea.go b/tea.go index 116f3b9667..94529676c3 100644 --- a/tea.go +++ b/tea.go @@ -20,7 +20,6 @@ import ( "runtime" "runtime/debug" "strconv" - "strings" "sync" "sync/atomic" "syscall" @@ -201,7 +200,7 @@ type Program struct { readLoopDone chan struct{} // modes keeps track of terminal modes that have been enabled or disabled. - modes map[ansi.DECMode]bool + modes ansi.Modes ignoreSignals uint32 filter func(Model, Msg) Msg @@ -226,8 +225,8 @@ type Program struct { // when the program is resumed. setBg, setFg, setCc color.Color - // exp stores program experimental features. - exp experimentalOptions + // Initial window size. Mainly used for testing. + width, height int } // Quit is a special command that tells the Bubble Tea program to exit. @@ -276,8 +275,7 @@ func NewProgram(model Model, opts ...ProgramOption) *Program { initialModel: model, msgs: make(chan Msg), rendererDone: make(chan struct{}), - modes: make(map[ansi.DECMode]bool), - exp: experimentalOptions{}, + modes: ansi.Modes{}, } // Apply all options to the program. @@ -327,12 +325,6 @@ func NewProgram(model Model, opts ...ProgramOption) *Program { } } - // Experimental features. Right now, we only have one experimental feature - // to use the new cell buffer as a default renderer. - if exp := p.getenv("TEA_EXPERIMENTAL"); exp != "" { - p.exp = strings.Split(exp, ",") - } - return p } @@ -479,32 +471,46 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { switch msg.Mode { case ansi.GraphemeClusteringMode: // 1 means mode is set (see DECRPM). - p.modes[ansi.GraphemeClusteringMode] = msg.Value == 1 || msg.Value == 3 + p.modes[ansi.GraphemeClusteringMode] = msg.Value } case enableModeMsg: - mode := ansi.DECMode(msg) - if on, ok := p.modes[mode]; ok && on { + mode := p.modes.Get(msg.Mode) + if mode.IsSet() { break } - p.execute(fmt.Sprintf("\x1b[?%dh", mode.Mode())) - p.modes[mode] = true - switch mode { + p.modes.Set(msg.Mode) + + switch msg.Mode { + case ansi.AltScreenSaveCursorMode: + p.renderer.enterAltScreen() + case ansi.TextCursorEnableMode: + p.renderer.showCursor() case ansi.GraphemeClusteringMode: // We store the state of grapheme clustering after we enable it // and get a response in the eventLoop. - p.execute(ansi.RequestGraphemeClusteringMode) + p.execute(ansi.SetGraphemeClusteringMode + ansi.RequestGraphemeClusteringMode) + default: + p.execute(ansi.SetMode(msg.Mode)) } case disableModeMsg: - mode := ansi.DECMode(msg) - if on, ok := p.modes[mode]; ok && !on { + mode := p.modes.Get(msg.Mode) + if mode.IsReset() { break } - p.execute(fmt.Sprintf("\x1b[?%dl", mode)) - p.modes[mode] = false + p.modes.Reset(msg.Mode) + + switch msg.Mode { + case ansi.AltScreenSaveCursorMode: + p.renderer.exitAltScreen() + case ansi.TextCursorEnableMode: + p.renderer.hideCursor() + default: + p.execute(ansi.ResetMode(msg.Mode)) + } case readClipboardMsg: p.execute(ansi.RequestSystemClipboard) @@ -644,15 +650,30 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { case setWindowTitleMsg: p.execute(ansi.SetWindowTitle(string(msg))) + case WindowSizeMsg: + p.renderer.resize(msg.Width, msg.Height) + case windowSizeMsg: go p.checkResize() case requestCursorPosMsg: p.execute(ansi.RequestCursorPosition) - } - // Process internal messages for the renderer. - p.renderer.update(msg) + case setCursorPosMsg: + p.renderer.moveTo(msg.X, msg.Y) + + case printLineMessage: + p.renderer.insertAbove(msg.messageBody) + + case repaintMsg: + p.renderer.repaint() + + case clearScreenMsg: + p.renderer.clearScreen() + + case ColorProfileMsg: + p.renderer.setColorProfile(msg.Profile) + } var cmd Cmd model, cmd = model.Update(msg) // run update @@ -727,23 +748,22 @@ func (p *Program) Run() (Model, error) { return p.initialModel, err } + go p.Send(ColorProfileMsg{p.profile}) + if p.renderer == nil { + // If no renderer is set use the ferocious one. + p.renderer = newScreenRenderer(p.output, p.getenv("TERM")) + } + // Get the color profile and send it to the program. if !p.startupOptions.has(withColorProfile) { p.profile = colorprofile.Detect(p.output.Writer(), p.environ) } - go p.Send(ColorProfileMsg{p.profile}) - if p.renderer == nil { - // If no renderer is set use the ferocious one. - if p.startupOptions&withFerociousRenderer != 0 || p.exp.has(experimentalFerocious) { - p.renderer = newScreenRenderer(p.profile, p.getenv("TERM")) - } else { - p.renderer = newStandardRenderer(p.profile) - } - } + // Set the color profile on the renderer. + p.renderer.setColorProfile(p.profile) - // Set the renderer output. - p.renderer.update(rendererWriter{p.output}) + // Get the initial window size. + resizeMsg := WindowSizeMsg{Width: p.width, Height: p.height} if p.ttyOutput != nil { // Set the initial size of the terminal. w, h, err := term.GetSize(p.ttyOutput.Fd()) @@ -751,15 +771,13 @@ func (p *Program) Run() (Model, error) { return p.initialModel, err } - var resizeMsg WindowSizeMsg - resizeMsg.Width = w - resizeMsg.Height = h - - // Send the initial size to the program. - go p.Send(resizeMsg) - p.renderer.update(WindowSizeMsg{Width: w, Height: h}) + resizeMsg.Width, resizeMsg.Height = w, h } + // Send the initial size to the program. + go p.Send(resizeMsg) + p.renderer.resize(resizeMsg.Width, resizeMsg.Height) + // Init the input reader and initial model. model := p.initialModel if p.input != nil { @@ -768,21 +786,24 @@ func (p *Program) Run() (Model, error) { } } - // Hide the cursor before starting the renderer. - p.modes[ansi.TextCursorEnableMode] = false - p.renderer.update(disableMode(ansi.TextCursorEnableMode)) + // Hide the cursor before starting the renderer. This is handled by the + // renderer so we don't need to write the sequence here. + p.modes.Reset(ansi.TextCursorEnableMode) + p.renderer.hideCursor() // Honor program startup options. if p.startupTitle != "" { p.execute(ansi.SetWindowTitle(p.startupTitle)) } if p.startupOptions&withAltScreen != 0 { - p.modes[ansi.AltScreenSaveCursorMode] = true - p.renderer.update(enableMode(ansi.AltScreenSaveCursorMode)) + // Enter alternate screen mode. This is handled by the renderer so we + // don't need to write the sequence here. + p.modes.Set(ansi.AltScreenSaveCursorMode) + p.renderer.enterAltScreen() } if p.startupOptions&withoutBracketedPaste == 0 { p.execute(ansi.SetBracketedPasteMode) - p.modes[ansi.BracketedPasteMode] = true + p.modes.Set(ansi.BracketedPasteMode) } if p.startupOptions&withGraphemeClustering != 0 { p.execute(ansi.SetGraphemeClusteringMode) @@ -791,20 +812,16 @@ func (p *Program) Run() (Model, error) { // a response in the eventLoop. } if p.startupOptions&withMouseCellMotion != 0 { - p.execute(ansi.SetButtonEventMouseMode) - p.execute(ansi.SetSgrExtMouseMode) - p.modes[ansi.ButtonEventMouseMode] = true - p.modes[ansi.SgrExtMouseMode] = true + p.execute(ansi.SetButtonEventMouseMode + ansi.SetSgrExtMouseMode) + p.modes.Set(ansi.ButtonEventMouseMode, ansi.SgrExtMouseMode) } else if p.startupOptions&withMouseAllMotion != 0 { - p.execute(ansi.SetAnyEventMouseMode) - p.execute(ansi.SetSgrExtMouseMode) - p.modes[ansi.AnyEventMouseMode] = true - p.modes[ansi.SgrExtMouseMode] = true + p.execute(ansi.SetAnyEventMouseMode + ansi.SetSgrExtMouseMode) + p.modes.Set(ansi.AnyEventMouseMode, ansi.SgrExtMouseMode) } if p.startupOptions&withReportFocus != 0 { p.execute(ansi.SetFocusEventMode) - p.modes[ansi.FocusEventMode] = true + p.modes.Set(ansi.FocusEventMode) } if p.startupOptions&withKeyboardEnhancements != 0 && runtime.GOOS != "windows" { // We use the Windows Console API which supports keyboard @@ -978,13 +995,13 @@ func (p *Program) RestoreTerminal() error { if err := p.initInputReader(); err != nil { return err } - if !p.modes[ansi.AltScreenSaveCursorMode] { + if p.modes.IsReset(ansi.AltScreenSaveCursorMode) { // entering alt screen already causes a repaint. go p.Send(repaintMsg{}) } p.startRenderer() - if p.modes[ansi.BracketedPasteMode] { + if p.modes.IsSet(ansi.BracketedPasteMode) { p.execute(ansi.SetBracketedPasteMode) } if p.keyboard.modifyOtherKeys != 0 { @@ -993,10 +1010,10 @@ func (p *Program) RestoreTerminal() error { if p.keyboard.kittyFlags != 0 { p.execute(ansi.PushKittyKeyboard(p.keyboard.kittyFlags)) } - if p.modes[ansi.FocusEventMode] { + if p.modes.IsSet(ansi.FocusEventMode) { p.execute(ansi.SetFocusEventMode) } - if p.modes[ansi.ButtonEventMouseMode] || p.modes[ansi.AnyEventMouseMode] { + if p.modes.IsSet(ansi.ButtonEventMouseMode) || p.modes.IsSet(ansi.AnyEventMouseMode) { if p.startupOptions&withMouseCellMotion != 0 { p.execute(ansi.SetButtonEventMouseMode) p.execute(ansi.SetSgrExtMouseMode) @@ -1005,7 +1022,7 @@ func (p *Program) RestoreTerminal() error { p.execute(ansi.SetSgrExtMouseMode) } } - if p.modes[ansi.GraphemeClusteringMode] { + if p.modes.IsSet(ansi.GraphemeClusteringMode) { p.execute(ansi.SetGraphemeClusteringMode) } diff --git a/tty.go b/tty.go index d1d5985407..a3edbdbd62 100644 --- a/tty.go +++ b/tty.go @@ -38,10 +38,15 @@ func (p *Program) initTerminal() error { // restoreTerminalState restores the terminal to the state prior to running the // Bubble Tea program. func (p *Program) restoreTerminalState() error { - if p.modes[ansi.BracketedPasteMode] { + // We don't need to reset [ansi.AltScreenSaveCursorMode] and + // [ansi.TextCursorEnableMode] because they are automatically reset when we + // close the renderer. See [screenRenderer.close] and + // [cellbuf.Screen.Close]. + + if p.modes.IsSet(ansi.BracketedPasteMode) { p.execute(ansi.ResetBracketedPasteMode) } - if p.modes[ansi.ButtonEventMouseMode] || p.modes[ansi.AnyEventMouseMode] { + if p.modes.IsSet(ansi.ButtonEventMouseMode) || p.modes.IsSet(ansi.AnyEventMouseMode) { p.execute(ansi.ResetButtonEventMouseMode) p.execute(ansi.ResetAnyEventMouseMode) p.execute(ansi.ResetSgrExtMouseMode) @@ -52,10 +57,10 @@ func (p *Program) restoreTerminalState() error { if p.keyboard.kittyFlags != 0 { p.execute(ansi.DisableKittyKeyboard) } - if p.modes[ansi.FocusEventMode] { + if p.modes.IsSet(ansi.FocusEventMode) { p.execute(ansi.ResetFocusEventMode) } - if p.modes[ansi.GraphemeClusteringMode] { + if p.modes.IsSet(ansi.GraphemeClusteringMode) { p.execute(ansi.ResetGraphemeClusteringMode) } From 26be4d4138ddc0e68d201be991a68321d9e19f13 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 12 Dec 2024 13:14:33 -0500 Subject: [PATCH 11/30] feat: tests: use golden files to test screen sequences --- examples/go.mod | 4 +- examples/go.sum | 8 +- examples/simple/main_test.go | 3 + screen_test.go | 95 ++++++++----------- testdata/TestClearMsg/altscreen.golden | 4 + .../TestClearMsg/altscreen_autoexit.golden | 1 + testdata/TestClearMsg/bg_fg_cur_color.golden | 4 + testdata/TestClearMsg/bg_set_color.golden | 4 + testdata/TestClearMsg/bp_stop_start.golden | 4 + testdata/TestClearMsg/clear_screen.golden | 4 + testdata/TestClearMsg/cursor_hide.golden | 4 + testdata/TestClearMsg/cursor_hideshow.golden | 4 + .../TestClearMsg/grapheme_clustering.golden | 4 + .../TestClearMsg/kitty_start_other.golden | 4 + .../TestClearMsg/kitty_start_windows.golden | 4 + testdata/TestClearMsg/mouse_allmotion.golden | 4 + testdata/TestClearMsg/mouse_cellmotion.golden | 4 + testdata/TestClearMsg/mouse_disable.golden | 4 + .../TestClearMsg/read_set_clipboard.golden | 4 + 19 files changed, 107 insertions(+), 60 deletions(-) create mode 100644 testdata/TestClearMsg/altscreen.golden create mode 100644 testdata/TestClearMsg/altscreen_autoexit.golden create mode 100644 testdata/TestClearMsg/bg_fg_cur_color.golden create mode 100644 testdata/TestClearMsg/bg_set_color.golden create mode 100644 testdata/TestClearMsg/bp_stop_start.golden create mode 100644 testdata/TestClearMsg/clear_screen.golden create mode 100644 testdata/TestClearMsg/cursor_hide.golden create mode 100644 testdata/TestClearMsg/cursor_hideshow.golden create mode 100644 testdata/TestClearMsg/grapheme_clustering.golden create mode 100644 testdata/TestClearMsg/kitty_start_other.golden create mode 100644 testdata/TestClearMsg/kitty_start_windows.golden create mode 100644 testdata/TestClearMsg/mouse_allmotion.golden create mode 100644 testdata/TestClearMsg/mouse_cellmotion.golden create mode 100644 testdata/TestClearMsg/mouse_disable.golden create mode 100644 testdata/TestClearMsg/read_set_clipboard.golden diff --git a/examples/go.mod b/examples/go.mod index 26b034e835..44f8e931b2 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,8 +23,8 @@ require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 // indirect - github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect + github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212170349-ad4b7ae0f25f // indirect + github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f // indirect github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 // indirect diff --git a/examples/go.sum b/examples/go.sum index c79a2de3b4..f10783a7a3 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -28,10 +28,10 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be480 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 h1:Ip9o3gP+Af0y3PdijNgsIb9NrEw5YQFUyzfPyUaty40= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= -github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= -github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212170349-ad4b7ae0f25f h1:rHJ4sjKCm7d4Mdpv64ahyy4MZNRQBToFNUvk0a6405M= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212170349-ad4b7ae0f25f/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= +github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233/go.mod h1:cw9df32BXdkcd0LzAHsFMmvXOsrrlDKazIW8PCq0cPM= github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= diff --git a/examples/simple/main_test.go b/examples/simple/main_test.go index e489658608..2ea67adba6 100644 --- a/examples/simple/main_test.go +++ b/examples/simple/main_test.go @@ -54,6 +54,9 @@ func TestApp(t *testing.T) { } func TestAppInteractive(t *testing.T) { + t.Skip("This test is flaky and needs to be fixed.\n" + + "We need a more concrete way to set the initial terminal size") + m := model(10) tm := teatest.NewTestModel( t, m, diff --git a/screen_test.go b/screen_test.go index 2265bd91db..c244c417f3 100644 --- a/screen_test.go +++ b/screen_test.go @@ -7,94 +7,79 @@ import ( "testing" "github.com/charmbracelet/colorprofile" + "github.com/charmbracelet/x/exp/golden" ) func TestClearMsg(t *testing.T) { type test struct { - name string - cmds sequenceMsg - expected string + name string + cmds sequenceMsg } tests := []test{ { - name: "clear_screen", - cmds: []Cmd{ClearScreen}, - expected: "\x1b[?25l\x1b[?2004h\x1b[2J\x1b[H\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h", + name: "clear_screen", + cmds: []Cmd{ClearScreen}, }, { - name: "altscreen", - cmds: []Cmd{EnterAltScreen, ExitAltScreen}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?1049h\x1b[2J\x1b[H\x1b[?25l\x1b[?1049l\x1b[?25l\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h", + name: "altscreen", + cmds: []Cmd{EnterAltScreen, ExitAltScreen}, }, { - name: "altscreen_autoexit", - cmds: []Cmd{EnterAltScreen}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?1049h\x1b[2J\x1b[H\x1b[?25l\x1b[H\rsuccess\r\n\x1b[2;H\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[?1049l\x1b[?25h", + name: "altscreen_autoexit", + cmds: []Cmd{EnterAltScreen}, }, { - name: "mouse_cellmotion", - cmds: []Cmd{EnableMouseCellMotion}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?1002h\x1b[?1006h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", + name: "mouse_cellmotion", + cmds: []Cmd{EnableMouseCellMotion}, }, { - name: "mouse_allmotion", - cmds: []Cmd{EnableMouseAllMotion}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?1003h\x1b[?1006h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", + name: "mouse_allmotion", + cmds: []Cmd{EnableMouseAllMotion}, }, { - name: "mouse_disable", - cmds: []Cmd{EnableMouseAllMotion, DisableMouse}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?1003h\x1b[?1006h\x1b[?1002l\x1b[?1003l\x1b[?1006l\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h", + name: "mouse_disable", + cmds: []Cmd{EnableMouseAllMotion, DisableMouse}, }, { - name: "cursor_hide", - cmds: []Cmd{HideCursor}, - expected: "\x1b[?25l\x1b[?2004h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h", + name: "cursor_hide", + cmds: []Cmd{HideCursor}, }, { - name: "cursor_hideshow", - cmds: []Cmd{HideCursor, ShowCursor}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?25h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l", + name: "cursor_hideshow", + cmds: []Cmd{HideCursor, ShowCursor}, }, { - name: "bp_stop_start", - cmds: []Cmd{DisableBracketedPaste, EnableBracketedPaste}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?2004l\x1b[?2004h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h", + name: "bp_stop_start", + cmds: []Cmd{DisableBracketedPaste, EnableBracketedPaste}, }, { - name: "read_set_clipboard", - cmds: []Cmd{ReadClipboard, SetClipboard("success")}, - expected: "\x1b[?25l\x1b[?2004h\x1b]52;c;?\a\x1b]52;c;c3VjY2Vzcw==\a\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h", + name: "read_set_clipboard", + cmds: []Cmd{ReadClipboard, SetClipboard("success")}, }, { - name: "bg_fg_cur_color", - cmds: []Cmd{RequestForegroundColor, RequestBackgroundColor, RequestCursorColor}, - expected: "\x1b[?25l\x1b[?2004h\x1b]10;?\a\x1b]11;?\a\x1b]12;?\a\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h", + name: "bg_fg_cur_color", + cmds: []Cmd{RequestForegroundColor, RequestBackgroundColor, RequestCursorColor}, }, { - name: "bg_set_color", - cmds: []Cmd{SetBackgroundColor(color.RGBA{255, 255, 255, 255})}, - expected: "\x1b[?25l\x1b[?2004h\x1b]11;#ffffff\a\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b]111\a", + name: "bg_set_color", + cmds: []Cmd{SetBackgroundColor(color.RGBA{255, 255, 255, 255})}, }, { - name: "grapheme_clustering", - cmds: []Cmd{EnableGraphemeClustering}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[?2027l", + name: "grapheme_clustering", + cmds: []Cmd{EnableGraphemeClustering}, }, } if runtime.GOOS == "windows" { // Windows supports enhanced keyboard features through the Windows API, not through ANSI sequences. tests = append(tests, test{ - name: "kitty_start", - cmds: []Cmd{DisableKeyboardEnhancements, EnableKeyboardEnhancements(WithKeyReleases)}, - expected: "\x1b[?25l\x1b[?2004h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h", + name: "kitty_start_windows", + cmds: []Cmd{DisableKeyboardEnhancements, EnableKeyboardEnhancements(WithKeyReleases)}, }) } else { tests = append(tests, test{ - name: "kitty_start", - cmds: []Cmd{DisableKeyboardEnhancements, EnableKeyboardEnhancements(WithKeyReleases)}, - expected: "\x1b[?25l\x1b[?2004h\x1b[>4;1m\x1b[>3u\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[>4;0m\x1b[>0u", + name: "kitty_start_other", + cmds: []Cmd{DisableKeyboardEnhancements, EnableKeyboardEnhancements(WithKeyReleases)}, }) } @@ -105,19 +90,21 @@ func TestClearMsg(t *testing.T) { m := &testModel{} p := NewProgram(m, WithInput(&in), WithOutput(&buf), + WithEnvironment([]string{ + "TERM=xterm-256color", // always use xterm and 256 colors for tests + }), // Use ANSI256 to increase test coverage. WithColorProfile(colorprofile.ANSI256)) - test.cmds = append(test.cmds, Quit) - go p.Send(test.cmds) + // Set the initial window size for the program. + p.width, p.height = 80, 24 + + go p.Send(append(test.cmds, Quit)) if _, err := p.Run(); err != nil { t.Fatal(err) } - - if buf.String() != test.expected { - t.Errorf("expected embedded sequence:\n%q\ngot:\n%q", test.expected, buf.String()) - } + golden.RequireEqual(t, buf.Bytes()) }) } } diff --git a/testdata/TestClearMsg/altscreen.golden b/testdata/TestClearMsg/altscreen.golden new file mode 100644 index 0000000000..c15fd0c0a7 --- /dev/null +++ b/testdata/TestClearMsg/altscreen.golden @@ -0,0 +1,4 @@ +[?2004h[?25l  + +Msuccess +[?25h[?2004l \ No newline at end of file diff --git a/testdata/TestClearMsg/altscreen_autoexit.golden b/testdata/TestClearMsg/altscreen_autoexit.golden new file mode 100644 index 0000000000..e7a2aaa72b --- /dev/null +++ b/testdata/TestClearMsg/altscreen_autoexit.golden @@ -0,0 +1 @@ +[?2004h[?1049h[?25lsuccess [?1049l[?25h[?2004l \ No newline at end of file diff --git a/testdata/TestClearMsg/bg_fg_cur_color.golden b/testdata/TestClearMsg/bg_fg_cur_color.golden new file mode 100644 index 0000000000..9a9bd029ef --- /dev/null +++ b/testdata/TestClearMsg/bg_fg_cur_color.golden @@ -0,0 +1,4 @@ +[?2004h]10;?]11;?]12;?[?25l + +Msuccess +[?25h[?2004l \ No newline at end of file diff --git a/testdata/TestClearMsg/bg_set_color.golden b/testdata/TestClearMsg/bg_set_color.golden new file mode 100644 index 0000000000..80a10499f1 --- /dev/null +++ b/testdata/TestClearMsg/bg_set_color.golden @@ -0,0 +1,4 @@ +[?2004h]11;#ffffff[?25l + +Msuccess +[?25h[?2004l]111 \ No newline at end of file diff --git a/testdata/TestClearMsg/bp_stop_start.golden b/testdata/TestClearMsg/bp_stop_start.golden new file mode 100644 index 0000000000..cfedd50d68 --- /dev/null +++ b/testdata/TestClearMsg/bp_stop_start.golden @@ -0,0 +1,4 @@ +[?2004h[?2004l[?2004h[?25l + +Msuccess +[?25h[?2004l \ No newline at end of file diff --git a/testdata/TestClearMsg/clear_screen.golden b/testdata/TestClearMsg/clear_screen.golden new file mode 100644 index 0000000000..c15fd0c0a7 --- /dev/null +++ b/testdata/TestClearMsg/clear_screen.golden @@ -0,0 +1,4 @@ +[?2004h[?25l  + +Msuccess +[?25h[?2004l \ No newline at end of file diff --git a/testdata/TestClearMsg/cursor_hide.golden b/testdata/TestClearMsg/cursor_hide.golden new file mode 100644 index 0000000000..f74ac40471 --- /dev/null +++ b/testdata/TestClearMsg/cursor_hide.golden @@ -0,0 +1,4 @@ +[?2004h[?25l + +Msuccess +[?25h[?2004l \ No newline at end of file diff --git a/testdata/TestClearMsg/cursor_hideshow.golden b/testdata/TestClearMsg/cursor_hideshow.golden new file mode 100644 index 0000000000..911a2a5ae4 --- /dev/null +++ b/testdata/TestClearMsg/cursor_hideshow.golden @@ -0,0 +1,4 @@ +[?2004h[?25l + +Msuccess [?25h +[?2004l \ No newline at end of file diff --git a/testdata/TestClearMsg/grapheme_clustering.golden b/testdata/TestClearMsg/grapheme_clustering.golden new file mode 100644 index 0000000000..3776fd72b5 --- /dev/null +++ b/testdata/TestClearMsg/grapheme_clustering.golden @@ -0,0 +1,4 @@ +[?2004h[?2027h[?2027$p[?25l + +Msuccess +[?25h[?2004l[?2027l \ No newline at end of file diff --git a/testdata/TestClearMsg/kitty_start_other.golden b/testdata/TestClearMsg/kitty_start_other.golden new file mode 100644 index 0000000000..e6f6ee8f4e --- /dev/null +++ b/testdata/TestClearMsg/kitty_start_other.golden @@ -0,0 +1,4 @@ +[?2004h[>4;1m[>3u[?25l + +Msuccess +[?25h[?2004l[>4;0m[>0u \ No newline at end of file diff --git a/testdata/TestClearMsg/kitty_start_windows.golden b/testdata/TestClearMsg/kitty_start_windows.golden new file mode 100644 index 0000000000..f74ac40471 --- /dev/null +++ b/testdata/TestClearMsg/kitty_start_windows.golden @@ -0,0 +1,4 @@ +[?2004h[?25l + +Msuccess +[?25h[?2004l \ No newline at end of file diff --git a/testdata/TestClearMsg/mouse_allmotion.golden b/testdata/TestClearMsg/mouse_allmotion.golden new file mode 100644 index 0000000000..49c404de82 --- /dev/null +++ b/testdata/TestClearMsg/mouse_allmotion.golden @@ -0,0 +1,4 @@ +[?2004h[?1003h[?1006h[?25l + +Msuccess +[?25h[?2004l[?1002l[?1003l[?1006l \ No newline at end of file diff --git a/testdata/TestClearMsg/mouse_cellmotion.golden b/testdata/TestClearMsg/mouse_cellmotion.golden new file mode 100644 index 0000000000..7c02630d68 --- /dev/null +++ b/testdata/TestClearMsg/mouse_cellmotion.golden @@ -0,0 +1,4 @@ +[?2004h[?1002h[?1006h[?25l + +Msuccess +[?25h[?2004l[?1002l[?1003l[?1006l \ No newline at end of file diff --git a/testdata/TestClearMsg/mouse_disable.golden b/testdata/TestClearMsg/mouse_disable.golden new file mode 100644 index 0000000000..4e3548ea45 --- /dev/null +++ b/testdata/TestClearMsg/mouse_disable.golden @@ -0,0 +1,4 @@ +[?2004h[?1003h[?1006h[?1002l[?1003l[?1006l[?25l + +Msuccess +[?25h[?2004l \ No newline at end of file diff --git a/testdata/TestClearMsg/read_set_clipboard.golden b/testdata/TestClearMsg/read_set_clipboard.golden new file mode 100644 index 0000000000..4a040cc9f9 --- /dev/null +++ b/testdata/TestClearMsg/read_set_clipboard.golden @@ -0,0 +1,4 @@ +[?2004h]52;c;?]52;c;c3VjY2Vzcw==[?25l + +Msuccess +[?25h[?2004l \ No newline at end of file From 98eed90359ab0be528b57d86412c9957d68f6847 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 12 Dec 2024 13:22:36 -0500 Subject: [PATCH 12/30] chore: remove unused --- utils.go | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 utils.go diff --git a/utils.go b/utils.go deleted file mode 100644 index 5de3fc7ead..0000000000 --- a/utils.go +++ /dev/null @@ -1,11 +0,0 @@ -package tea - -func clamp(x, min, max int) int { - if x < min { - return min - } - if x > max { - return max - } - return x -} From 3d79aff7ed2c9de19cfc68dd9eb0f94e93597b9c Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 12 Dec 2024 13:47:28 -0500 Subject: [PATCH 13/30] perf: only reset mouse modes that are set --- testdata/TestClearMsg/mouse_allmotion.golden | 2 +- testdata/TestClearMsg/mouse_cellmotion.golden | 2 +- tty.go | 13 ++++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/testdata/TestClearMsg/mouse_allmotion.golden b/testdata/TestClearMsg/mouse_allmotion.golden index 49c404de82..62fa670604 100644 --- a/testdata/TestClearMsg/mouse_allmotion.golden +++ b/testdata/TestClearMsg/mouse_allmotion.golden @@ -1,4 +1,4 @@ [?2004h[?1003h[?1006h[?25l Msuccess -[?25h[?2004l[?1002l[?1003l[?1006l \ No newline at end of file +[?25h[?2004l[?1003l[?1006l \ No newline at end of file diff --git a/testdata/TestClearMsg/mouse_cellmotion.golden b/testdata/TestClearMsg/mouse_cellmotion.golden index 7c02630d68..fcb3f3737e 100644 --- a/testdata/TestClearMsg/mouse_cellmotion.golden +++ b/testdata/TestClearMsg/mouse_cellmotion.golden @@ -1,4 +1,4 @@ [?2004h[?1002h[?1006h[?25l Msuccess -[?25h[?2004l[?1002l[?1003l[?1006l \ No newline at end of file +[?25h[?2004l[?1002l[?1006l \ No newline at end of file diff --git a/tty.go b/tty.go index a3edbdbd62..ec04ac8907 100644 --- a/tty.go +++ b/tty.go @@ -46,9 +46,16 @@ func (p *Program) restoreTerminalState() error { if p.modes.IsSet(ansi.BracketedPasteMode) { p.execute(ansi.ResetBracketedPasteMode) } - if p.modes.IsSet(ansi.ButtonEventMouseMode) || p.modes.IsSet(ansi.AnyEventMouseMode) { - p.execute(ansi.ResetButtonEventMouseMode) - p.execute(ansi.ResetAnyEventMouseMode) + + btnEvents := p.modes.IsSet(ansi.ButtonEventMouseMode) + allEvents := p.modes.IsSet(ansi.AnyEventMouseMode) + if btnEvents || allEvents { + if btnEvents { + p.execute(ansi.ResetButtonEventMouseMode) + } + if allEvents { + p.execute(ansi.ResetAnyEventMouseMode) + } p.execute(ansi.ResetSgrExtMouseMode) } if p.keyboard.modifyOtherKeys != 0 { From ea8e8e734ae78a51bc2267a43252805b3745571b Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 12 Dec 2024 13:59:26 -0500 Subject: [PATCH 14/30] chore: bump cellbuf --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- go.mod | 4 +++- go.sum | 8 ++++++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 44f8e931b2..0c02bdf947 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,7 +23,7 @@ require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212170349-ad4b7ae0f25f // indirect + github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212185810-02f3cd570d47 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f // indirect github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect diff --git a/examples/go.sum b/examples/go.sum index f10783a7a3..3f8be48257 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -28,8 +28,8 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be480 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212170349-ad4b7ae0f25f h1:rHJ4sjKCm7d4Mdpv64ahyy4MZNRQBToFNUvk0a6405M= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212170349-ad4b7ae0f25f/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212185810-02f3cd570d47 h1:Jn+URnqorsx72nnfQ8MnMJeLAFam6d8LFN5hyS99diA= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212185810-02f3cd570d47/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U= diff --git a/go.mod b/go.mod index 91935c972a..8e7015953f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,8 @@ go 1.18 require ( github.com/charmbracelet/colorprofile v0.1.9 github.com/charmbracelet/x/ansi v0.6.0 - github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212170349-ad4b7ae0f25f + github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212185810-02f3cd570d47 + github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f github.com/charmbracelet/x/input v0.3.0 github.com/charmbracelet/x/term v0.2.1 github.com/muesli/cancelreader v0.2.2 @@ -14,6 +15,7 @@ require ( ) require ( + github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 // indirect github.com/charmbracelet/x/windows v0.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect diff --git a/go.sum b/go.sum index 89a4b608e7..72b54c48b2 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,13 @@ +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU9pGu1jkZxLw= github.com/charmbracelet/colorprofile v0.1.9/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212170349-ad4b7ae0f25f h1:rHJ4sjKCm7d4Mdpv64ahyy4MZNRQBToFNUvk0a6405M= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212170349-ad4b7ae0f25f/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212185810-02f3cd570d47 h1:Jn+URnqorsx72nnfQ8MnMJeLAFam6d8LFN5hyS99diA= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212185810-02f3cd570d47/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= +github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= github.com/charmbracelet/x/input v0.3.0/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= From 3e7802ac3112f4fa6d85b481160993d7e50c862a Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 12 Dec 2024 14:59:36 -0500 Subject: [PATCH 15/30] fix: renderer: attempt to resolve race condition --- ferocious_renderer.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ferocious_renderer.go b/ferocious_renderer.go index 92f9b0aaa3..a734ae2ae5 100644 --- a/ferocious_renderer.go +++ b/ferocious_renderer.go @@ -33,6 +33,8 @@ func newScreenRenderer(w io.Writer, term string) (s *screenRenderer) { // close implements renderer. func (s *screenRenderer) close() (err error) { + s.mu.Lock() + defer s.mu.Unlock() return s.scr.Close() } From f3438373a1c9884fd4050e5b0dc0b45731a0e5f0 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 20 Dec 2024 10:24:08 +0300 Subject: [PATCH 16/30] fix: renderer: lastFrame should be a pointer to string --- ferocious_renderer.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ferocious_renderer.go b/ferocious_renderer.go index a734ae2ae5..31eaa501b7 100644 --- a/ferocious_renderer.go +++ b/ferocious_renderer.go @@ -12,7 +12,7 @@ import ( type screenRenderer struct { w io.Writer scr *cellbuf.Screen - lastFrame string + lastFrame *string term string // the terminal type $TERM width, height int mu sync.Mutex @@ -48,11 +48,11 @@ func (s *screenRenderer) flush() error { // render implements renderer. func (s *screenRenderer) render(frame string) { - if frame == s.lastFrame { + if s.lastFrame != nil && frame == *s.lastFrame { return } - s.lastFrame = frame + s.lastFrame = &frame if !s.altScreen { // Inline mode resizes the screen based on the frame height and // terminal width. This is because the frame height can change based on @@ -117,7 +117,7 @@ func (s *screenRenderer) exitAltScreen() { s.altScreen = false s.scr.ExitAltScreen() s.scr.SetRelativeCursor(!s.altScreen) - s.scr.Resize(s.width, strings.Count(s.lastFrame, "\n")+1) + s.scr.Resize(s.width, strings.Count(*s.lastFrame, "\n")+1) s.repaint() } @@ -144,5 +144,5 @@ func (s *screenRenderer) moveTo(x, y int) { } func (s *screenRenderer) repaint() { - s.lastFrame = "" + s.lastFrame = nil } From 974dc31947e8b369adf35de3bacbb84e9d76bb04 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 20 Dec 2024 10:25:37 +0300 Subject: [PATCH 17/30] chore: bump cellbuf to latest --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 0c02bdf947..ee91e18b2f 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,7 +23,7 @@ require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212185810-02f3cd570d47 // indirect + github.com/charmbracelet/x/cellbuf v0.0.7-0.20241220071422-df2a7c69829a // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f // indirect github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect diff --git a/examples/go.sum b/examples/go.sum index 3f8be48257..0711e07e56 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -28,8 +28,8 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be480 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212185810-02f3cd570d47 h1:Jn+URnqorsx72nnfQ8MnMJeLAFam6d8LFN5hyS99diA= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212185810-02f3cd570d47/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241220071422-df2a7c69829a h1:tIR5nz8EXkqagZvmGFwWUozfxeon8+UzolURNKPCMT0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241220071422-df2a7c69829a/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U= diff --git a/go.mod b/go.mod index 8e7015953f..9a973e9630 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/charmbracelet/colorprofile v0.1.9 github.com/charmbracelet/x/ansi v0.6.0 - github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212185810-02f3cd570d47 + github.com/charmbracelet/x/cellbuf v0.0.7-0.20241220071422-df2a7c69829a github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f github.com/charmbracelet/x/input v0.3.0 github.com/charmbracelet/x/term v0.2.1 diff --git a/go.sum b/go.sum index 72b54c48b2..76069e047e 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU github.com/charmbracelet/colorprofile v0.1.9/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212185810-02f3cd570d47 h1:Jn+URnqorsx72nnfQ8MnMJeLAFam6d8LFN5hyS99diA= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241212185810-02f3cd570d47/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241220071422-df2a7c69829a h1:tIR5nz8EXkqagZvmGFwWUozfxeon8+UzolURNKPCMT0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241220071422-df2a7c69829a/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= From 8d5202da5255af9e0b379bc0b21261331fb1bea3 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 20 Dec 2024 10:57:56 +0300 Subject: [PATCH 18/30] fix(examples): split-editors: add CursorLineNumber style --- examples/split-editors/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/split-editors/main.go b/examples/split-editors/main.go index 1267f61e9b..bb9ed36ec3 100644 --- a/examples/split-editors/main.go +++ b/examples/split-editors/main.go @@ -55,6 +55,7 @@ func newTextarea() textarea.Model { t.Styles.Focused.Placeholder = focusedPlaceholderStyle t.Styles.Blurred.Placeholder = placeholderStyle t.Styles.Focused.CursorLine = cursorLineStyle + t.Styles.Focused.CursorLineNumber = cursorLineStyle t.Styles.Focused.Base = focusedBorderStyle t.Styles.Blurred.Base = blurredBorderStyle t.Styles.Focused.EndOfBuffer = endOfBufferStyle From 9a2354980ca68cbe508129fdd56b285bdff5565f Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 20 Dec 2024 11:13:39 +0300 Subject: [PATCH 19/30] chore: bump cellbuf to latest main --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index ee91e18b2f..defa267dbe 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,7 +23,7 @@ require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.7-0.20241220071422-df2a7c69829a // indirect + github.com/charmbracelet/x/cellbuf v0.0.7-0.20241222074819-0d34f0e6a347 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f // indirect github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect diff --git a/examples/go.sum b/examples/go.sum index 0711e07e56..d06281e58d 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -28,8 +28,8 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be480 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241220071422-df2a7c69829a h1:tIR5nz8EXkqagZvmGFwWUozfxeon8+UzolURNKPCMT0= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241220071422-df2a7c69829a/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241222074819-0d34f0e6a347 h1:bxol2qdY+qQG4+aGlawoiBdf43p6sfusP/TJLQ2sdxk= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241222074819-0d34f0e6a347/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U= diff --git a/go.mod b/go.mod index 9a973e9630..f735a963ff 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/charmbracelet/colorprofile v0.1.9 github.com/charmbracelet/x/ansi v0.6.0 - github.com/charmbracelet/x/cellbuf v0.0.7-0.20241220071422-df2a7c69829a + github.com/charmbracelet/x/cellbuf v0.0.7-0.20241222074819-0d34f0e6a347 github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f github.com/charmbracelet/x/input v0.3.0 github.com/charmbracelet/x/term v0.2.1 diff --git a/go.sum b/go.sum index 76069e047e..53f2eed282 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU github.com/charmbracelet/colorprofile v0.1.9/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241220071422-df2a7c69829a h1:tIR5nz8EXkqagZvmGFwWUozfxeon8+UzolURNKPCMT0= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241220071422-df2a7c69829a/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241222074819-0d34f0e6a347 h1:bxol2qdY+qQG4+aGlawoiBdf43p6sfusP/TJLQ2sdxk= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20241222074819-0d34f0e6a347/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= From 05ffb5409992a744a0728e276a07ab376ad2c71c Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Sun, 22 Dec 2024 11:18:29 +0300 Subject: [PATCH 20/30] fix: send color profile message after setting it on the renderer --- tea.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tea.go b/tea.go index 94529676c3..56ced7a957 100644 --- a/tea.go +++ b/tea.go @@ -747,8 +747,6 @@ func (p *Program) Run() (Model, error) { if err := p.initTerminal(); err != nil { return p.initialModel, err } - - go p.Send(ColorProfileMsg{p.profile}) if p.renderer == nil { // If no renderer is set use the ferocious one. p.renderer = newScreenRenderer(p.output, p.getenv("TERM")) @@ -759,8 +757,9 @@ func (p *Program) Run() (Model, error) { p.profile = colorprofile.Detect(p.output.Writer(), p.environ) } - // Set the color profile on the renderer. + // Set the color profile on the renderer and send it to the program. p.renderer.setColorProfile(p.profile) + go p.Send(ColorProfileMsg{p.profile}) // Get the initial window size. resizeMsg := WindowSizeMsg{Width: p.width, Height: p.height} From 777675cbe5627806893c6a097ed1627162cd698c Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 6 Jan 2025 16:11:51 +0300 Subject: [PATCH 21/30] feat: use hard tabs to optimize cursor movements --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- ferocious_renderer.go | 9 +++++++-- go.mod | 2 +- go.sum | 4 ++-- tea.go | 5 ++++- tty_unix.go | 5 +++++ 7 files changed, 22 insertions(+), 9 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index defa267dbe..d4b0d3c1bd 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,7 +23,7 @@ require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.7-0.20241222074819-0d34f0e6a347 // indirect + github.com/charmbracelet/x/cellbuf v0.0.7-0.20250106131004-d62699029fca // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f // indirect github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect diff --git a/examples/go.sum b/examples/go.sum index d06281e58d..d0e72663c8 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -28,8 +28,8 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be480 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241222074819-0d34f0e6a347 h1:bxol2qdY+qQG4+aGlawoiBdf43p6sfusP/TJLQ2sdxk= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241222074819-0d34f0e6a347/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250106131004-d62699029fca h1:QbvgHLU2w+Ea5odnGIqUexHjw6xthiggdbI+kQd2AcE= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250106131004-d62699029fca/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U= diff --git a/ferocious_renderer.go b/ferocious_renderer.go index 31eaa501b7..ea8abf79d3 100644 --- a/ferocious_renderer.go +++ b/ferocious_renderer.go @@ -19,14 +19,16 @@ type screenRenderer struct { profile colorprofile.Profile altScreen bool cursorHidden bool + hardTabs bool // whether to use hard tabs to optimize cursor movements } var _ renderer = &screenRenderer{} -func newScreenRenderer(w io.Writer, term string) (s *screenRenderer) { +func newScreenRenderer(w io.Writer, term string, hardTabs bool) (s *screenRenderer) { s = new(screenRenderer) s.w = w s.term = term + s.hardTabs = hardTabs s.reset() return } @@ -64,7 +66,9 @@ func (s *screenRenderer) render(frame string) { s.scr.Resize(s.width, frameHeight) } - cellbuf.Paint(s.scr, frame) + if ctx := s.scr.DefaultWindow(); ctx != nil { + ctx.SetContent(frame) + } } // reset implements renderer. @@ -77,6 +81,7 @@ func (s *screenRenderer) reset() { ShowCursor: !s.cursorHidden, Width: s.width, Height: s.height, + HardTabs: s.hardTabs, }) } diff --git a/go.mod b/go.mod index f735a963ff..4e4efdf355 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/charmbracelet/colorprofile v0.1.9 github.com/charmbracelet/x/ansi v0.6.0 - github.com/charmbracelet/x/cellbuf v0.0.7-0.20241222074819-0d34f0e6a347 + github.com/charmbracelet/x/cellbuf v0.0.7-0.20250106131004-d62699029fca github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f github.com/charmbracelet/x/input v0.3.0 github.com/charmbracelet/x/term v0.2.1 diff --git a/go.sum b/go.sum index 53f2eed282..cfe291b9f9 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU github.com/charmbracelet/colorprofile v0.1.9/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241222074819-0d34f0e6a347 h1:bxol2qdY+qQG4+aGlawoiBdf43p6sfusP/TJLQ2sdxk= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20241222074819-0d34f0e6a347/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250106131004-d62699029fca h1:QbvgHLU2w+Ea5odnGIqUexHjw6xthiggdbI+kQd2AcE= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250106131004-d62699029fca/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= diff --git a/tea.go b/tea.go index 56ced7a957..43aa93008a 100644 --- a/tea.go +++ b/tea.go @@ -227,6 +227,9 @@ type Program struct { // Initial window size. Mainly used for testing. width, height int + + // whether to use hard tabs to optimize cursor movements + useHardTabs bool } // Quit is a special command that tells the Bubble Tea program to exit. @@ -749,7 +752,7 @@ func (p *Program) Run() (Model, error) { } if p.renderer == nil { // If no renderer is set use the ferocious one. - p.renderer = newScreenRenderer(p.output, p.getenv("TERM")) + p.renderer = newScreenRenderer(p.output, p.getenv("TERM"), p.useHardTabs) } // Get the color profile and send it to the program. diff --git a/tty_unix.go b/tty_unix.go index 36c9b3688d..6134bbcb8d 100644 --- a/tty_unix.go +++ b/tty_unix.go @@ -10,6 +10,7 @@ import ( "syscall" "github.com/charmbracelet/x/term" + "golang.org/x/sys/unix" ) func (p *Program) initInput() (err error) { @@ -20,6 +21,10 @@ func (p *Program) initInput() (err error) { if err != nil { return fmt.Errorf("error entering raw mode: %w", err) } + + // OPTIM: We can use hard tabs to optimize cursor movements if the + // terminal doesn't have tab expansion enabled. + p.useHardTabs = p.previousTtyInputState.Oflag&unix.TABDLY == 0 } if f, ok := p.output.Writer().(term.File); ok && term.IsTerminal(f.Fd()) { From f53262089248467d6da5a46e078499545b5ae1da Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 7 Jan 2025 13:03:36 +0300 Subject: [PATCH 22/30] fix: renderer: repaint screen after resize --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- ferocious_renderer.go | 2 ++ go.mod | 2 +- go.sum | 4 ++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index d4b0d3c1bd..60212f2877 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,7 +23,7 @@ require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.7-0.20250106131004-d62699029fca // indirect + github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107095959-cbdcc21a06e3 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f // indirect github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect diff --git a/examples/go.sum b/examples/go.sum index d0e72663c8..24f854afc8 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -28,8 +28,8 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be480 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250106131004-d62699029fca h1:QbvgHLU2w+Ea5odnGIqUexHjw6xthiggdbI+kQd2AcE= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250106131004-d62699029fca/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107095959-cbdcc21a06e3 h1:j1SE6Ish2pxU0J6z/FAsPbtXoorKFWxRqZdkxdFClM0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107095959-cbdcc21a06e3/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U= diff --git a/ferocious_renderer.go b/ferocious_renderer.go index ea8abf79d3..edebd96ce6 100644 --- a/ferocious_renderer.go +++ b/ferocious_renderer.go @@ -100,6 +100,8 @@ func (s *screenRenderer) resize(w, h int) { // width. See [screenRenderer.render] for more details. s.scr.Resize(s.width, s.height) } + + s.repaint() } // clearScreen implements renderer. diff --git a/go.mod b/go.mod index 4e4efdf355..73da0d3879 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/charmbracelet/colorprofile v0.1.9 github.com/charmbracelet/x/ansi v0.6.0 - github.com/charmbracelet/x/cellbuf v0.0.7-0.20250106131004-d62699029fca + github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107095959-cbdcc21a06e3 github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f github.com/charmbracelet/x/input v0.3.0 github.com/charmbracelet/x/term v0.2.1 diff --git a/go.sum b/go.sum index cfe291b9f9..c6bc922520 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU github.com/charmbracelet/colorprofile v0.1.9/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250106131004-d62699029fca h1:QbvgHLU2w+Ea5odnGIqUexHjw6xthiggdbI+kQd2AcE= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250106131004-d62699029fca/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107095959-cbdcc21a06e3 h1:j1SE6Ish2pxU0J6z/FAsPbtXoorKFWxRqZdkxdFClM0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107095959-cbdcc21a06e3/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= From cdd901d3a8aa57bc4742bd93edf99898dcbec968 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 7 Jan 2025 14:17:04 +0300 Subject: [PATCH 23/30] chore: bump cellbuf to main --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 60212f2877..cb1c865503 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,7 +23,7 @@ require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107095959-cbdcc21a06e3 // indirect + github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f // indirect github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect diff --git a/examples/go.sum b/examples/go.sum index 24f854afc8..2f5e74f4c8 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -28,8 +28,8 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be480 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107095959-cbdcc21a06e3 h1:j1SE6Ish2pxU0J6z/FAsPbtXoorKFWxRqZdkxdFClM0= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107095959-cbdcc21a06e3/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 h1:Ip9o3gP+Af0y3PdijNgsIb9NrEw5YQFUyzfPyUaty40= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U= diff --git a/go.mod b/go.mod index 73da0d3879..bed8c3df92 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/charmbracelet/colorprofile v0.1.9 github.com/charmbracelet/x/ansi v0.6.0 - github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107095959-cbdcc21a06e3 + github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f github.com/charmbracelet/x/input v0.3.0 github.com/charmbracelet/x/term v0.2.1 diff --git a/go.sum b/go.sum index c6bc922520..360adc769c 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU github.com/charmbracelet/colorprofile v0.1.9/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107095959-cbdcc21a06e3 h1:j1SE6Ish2pxU0J6z/FAsPbtXoorKFWxRqZdkxdFClM0= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107095959-cbdcc21a06e3/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 h1:Ip9o3gP+Af0y3PdijNgsIb9NrEw5YQFUyzfPyUaty40= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= From a5ff03ab9d9e159e4e1cef2e8d3b81d0345b301a Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 8 Jan 2025 15:15:07 +0300 Subject: [PATCH 24/30] fix: renderer: guard against concurrent access to screenRenderer --- ferocious_renderer.go | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/ferocious_renderer.go b/ferocious_renderer.go index edebd96ce6..27c0364fe4 100644 --- a/ferocious_renderer.go +++ b/ferocious_renderer.go @@ -50,6 +50,9 @@ func (s *screenRenderer) flush() error { // render implements renderer. func (s *screenRenderer) render(frame string) { + s.mu.Lock() + defer s.mu.Unlock() + if s.lastFrame != nil && frame == *s.lastFrame { return } @@ -73,6 +76,7 @@ func (s *screenRenderer) render(frame string) { // reset implements renderer. func (s *screenRenderer) reset() { + s.mu.Lock() s.scr = cellbuf.NewScreen(s.w, &cellbuf.ScreenOptions{ Term: s.term, Profile: s.profile, @@ -83,16 +87,20 @@ func (s *screenRenderer) reset() { Height: s.height, HardTabs: s.hardTabs, }) + s.mu.Unlock() } // setColorProfile implements renderer. func (s *screenRenderer) setColorProfile(p colorprofile.Profile) { + s.mu.Lock() s.profile = p s.scr.SetColorProfile(p) + s.mu.Unlock() } // resize implements renderer. func (s *screenRenderer) resize(w, h int) { + s.mu.Lock() s.width, s.height = w, h if s.altScreen { // We only resize the screen if we're in the alt screen buffer. Inline @@ -101,55 +109,76 @@ func (s *screenRenderer) resize(w, h int) { s.scr.Resize(s.width, s.height) } - s.repaint() + repaint(s) + s.mu.Unlock() } // clearScreen implements renderer. func (s *screenRenderer) clearScreen() { + s.mu.Lock() s.scr.Clear() - s.repaint() + repaint(s) + s.mu.Unlock() } // enterAltScreen implements renderer. func (s *screenRenderer) enterAltScreen() { + s.mu.Lock() s.altScreen = true s.scr.EnterAltScreen() s.scr.SetRelativeCursor(!s.altScreen) s.scr.Resize(s.width, s.height) - s.repaint() + s.lastFrame = nil + s.mu.Unlock() } // exitAltScreen implements renderer. func (s *screenRenderer) exitAltScreen() { + s.mu.Lock() s.altScreen = false s.scr.ExitAltScreen() s.scr.SetRelativeCursor(!s.altScreen) s.scr.Resize(s.width, strings.Count(*s.lastFrame, "\n")+1) - s.repaint() + repaint(s) + s.mu.Unlock() } // showCursor implements renderer. func (s *screenRenderer) showCursor() { + s.mu.Lock() s.cursorHidden = false s.scr.ShowCursor() + s.mu.Unlock() } // hideCursor implements renderer. func (s *screenRenderer) hideCursor() { + s.mu.Lock() s.cursorHidden = true s.scr.HideCursor() + s.mu.Unlock() } // insertAbove implements renderer. func (s *screenRenderer) insertAbove(lines string) { + s.mu.Lock() s.scr.InsertAbove(lines) + s.mu.Unlock() } // moveTo implements renderer. func (s *screenRenderer) moveTo(x, y int) { + s.mu.Lock() s.scr.MoveTo(x, y) + s.mu.Unlock() } func (s *screenRenderer) repaint() { + s.mu.Lock() + repaint(s) + s.mu.Unlock() +} + +func repaint(s *screenRenderer) { s.lastFrame = nil } From ffa5bf427ac41ac9deb670c09ff15750bca4d552 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 13 Jan 2025 09:55:02 +0300 Subject: [PATCH 25/30] chore: bump cellbuf to fix divide by zero --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index cb1c865503..8b8643e695 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,7 +23,7 @@ require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 // indirect + github.com/charmbracelet/x/cellbuf v0.0.7-0.20250113065325-800d48271e72 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f // indirect github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect diff --git a/examples/go.sum b/examples/go.sum index 2f5e74f4c8..f11ebd5727 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -28,8 +28,8 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be480 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 h1:Ip9o3gP+Af0y3PdijNgsIb9NrEw5YQFUyzfPyUaty40= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250113065325-800d48271e72 h1:P90NI2rZuBISjB1HIHdkBDE+riKtVzIOi6Xun3qjUn8= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250113065325-800d48271e72/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U= diff --git a/go.mod b/go.mod index bed8c3df92..658edf697e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/charmbracelet/colorprofile v0.1.9 github.com/charmbracelet/x/ansi v0.6.0 - github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 + github.com/charmbracelet/x/cellbuf v0.0.7-0.20250113065325-800d48271e72 github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f github.com/charmbracelet/x/input v0.3.0 github.com/charmbracelet/x/term v0.2.1 diff --git a/go.sum b/go.sum index 360adc769c..8203c44785 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU github.com/charmbracelet/colorprofile v0.1.9/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5 h1:Ip9o3gP+Af0y3PdijNgsIb9NrEw5YQFUyzfPyUaty40= -github.com/charmbracelet/x/cellbuf v0.0.7-0.20250107110353-48b574af22a5/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250113065325-800d48271e72 h1:P90NI2rZuBISjB1HIHdkBDE+riKtVzIOi6Xun3qjUn8= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250113065325-800d48271e72/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= From e2768ff84369daa5898e04260b7134c06c58bb76 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 13 Jan 2025 09:57:44 +0300 Subject: [PATCH 26/30] chore: testdata: update golden files --- testdata/TestClearMsg/altscreen.golden | 2 +- testdata/TestClearMsg/clear_screen.golden | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testdata/TestClearMsg/altscreen.golden b/testdata/TestClearMsg/altscreen.golden index c15fd0c0a7..95737454f2 100644 --- a/testdata/TestClearMsg/altscreen.golden +++ b/testdata/TestClearMsg/altscreen.golden @@ -1,4 +1,4 @@ -[?2004h[?25l  +[?2004h[?25l Msuccess [?25h[?2004l \ No newline at end of file diff --git a/testdata/TestClearMsg/clear_screen.golden b/testdata/TestClearMsg/clear_screen.golden index c15fd0c0a7..95737454f2 100644 --- a/testdata/TestClearMsg/clear_screen.golden +++ b/testdata/TestClearMsg/clear_screen.golden @@ -1,4 +1,4 @@ -[?2004h[?25l  +[?2004h[?25l Msuccess [?25h[?2004l \ No newline at end of file From 73843abd916d0b21dc10f14cc8a44f11a8787303 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:46:09 +0000 Subject: [PATCH 27/30] chore(deps): bump golang.org/x/net from 0.27.0 to 0.33.0 in /examples (#1289) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.27.0 to 0.33.0. - [Commits](https://github.com/golang/net/compare/v0.27.0...v0.33.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/go.mod | 8 ++++---- examples/go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index cd6ed04179..5b52d5f287 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -40,11 +40,11 @@ require ( github.com/sahilm/fuzzy v0.1.1 // indirect github.com/yuin/goldmark v1.7.4 // indirect github.com/yuin/goldmark-emoji v1.0.3 // indirect - golang.org/x/net v0.27.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect ) replace github.com/charmbracelet/bubbletea => ../ diff --git a/examples/go.sum b/examples/go.sum index 25280ddfeb..38db805db7 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -74,15 +74,15 @@ github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4= github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= From c1cd8bba561eacea8f0985cad4a718c1a9cde61d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 17:38:13 +0000 Subject: [PATCH 28/30] chore(deps): bump github.com/charmbracelet/x/ansi from 0.6.0 to 0.7.0 (#1290) Bumps [github.com/charmbracelet/x/ansi](https://github.com/charmbracelet/x) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/charmbracelet/x/releases) - [Commits](https://github.com/charmbracelet/x/compare/ansi/v0.6.0...ansi/v0.7.0) --- updated-dependencies: - dependency-name: github.com/charmbracelet/x/ansi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cc93713ed1..017983f8cc 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/charmbracelet/lipgloss v1.0.0 - github.com/charmbracelet/x/ansi v0.6.0 + github.com/charmbracelet/x/ansi v0.7.0 github.com/charmbracelet/x/term v0.2.1 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f github.com/mattn/go-localereader v0.0.1 diff --git a/go.sum b/go.sum index 45c802d307..9b7dff2b30 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= -github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= -github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= +github.com/charmbracelet/x/ansi v0.7.0 h1:/QfFmiXOGGwN6fRbzvQaYp7fu1pkxpZ3qFBZWBsP404= +github.com/charmbracelet/x/ansi v0.7.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= From 9f703251e0d79306004310ed595642e933fb74b0 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Tue, 14 Jan 2025 15:30:54 -0300 Subject: [PATCH 29/30] chore(deps): update Signed-off-by: Carlos Alexandro Becker --- go.mod | 4 ++-- go.sum | 8 ++++---- screen_test.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 971dd3cf9b..93380d150d 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.18 require ( github.com/charmbracelet/colorprofile v0.1.8 - github.com/charmbracelet/x/ansi v0.6.0 + github.com/charmbracelet/x/ansi v0.7.0 github.com/charmbracelet/x/cellbuf v0.0.6 - github.com/charmbracelet/x/input v0.2.1-0.20241210230033-45c33cb7b57a + github.com/charmbracelet/x/input v0.3.0 github.com/charmbracelet/x/term v0.2.1 github.com/charmbracelet/x/vt v0.0.0-20241121165045-a3720547cbb4 github.com/muesli/cancelreader v0.2.2 diff --git a/go.sum b/go.sum index 13d75fd36a..2d79b1c426 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ github.com/charmbracelet/colorprofile v0.1.8 h1:PywDeXsiAzlPtkiiKgMEVLvb6nlEuKrMj9+FJBtj4jU= github.com/charmbracelet/colorprofile v0.1.8/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= -github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= -github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= +github.com/charmbracelet/x/ansi v0.7.0 h1:/QfFmiXOGGwN6fRbzvQaYp7fu1pkxpZ3qFBZWBsP404= +github.com/charmbracelet/x/ansi v0.7.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= github.com/charmbracelet/x/cellbuf v0.0.6 h1:pJUWN/G1jbt1Nj/+ILfC2/ABQoZzWu1vG73yHQEYELI= github.com/charmbracelet/x/cellbuf v0.0.6/go.mod h1:d72o71glp8flkCz54PHLe3+nuw5u2v3UxmKqruUERWQ= -github.com/charmbracelet/x/input v0.2.1-0.20241210230033-45c33cb7b57a h1:CspqRQ5YjX8qE/3/QefJf3twGokKeM42I/fZmx72H90= -github.com/charmbracelet/x/input v0.2.1-0.20241210230033-45c33cb7b57a/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= +github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= +github.com/charmbracelet/x/input v0.3.0/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= 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/x/vt v0.0.0-20241121165045-a3720547cbb4 h1:EacjHxcQEEgOZ7TbkAU3b84hd1Bn5NwA8YV5uyJ9EI4= diff --git a/screen_test.go b/screen_test.go index 2265bd91db..b151eabf8b 100644 --- a/screen_test.go +++ b/screen_test.go @@ -94,7 +94,7 @@ func TestClearMsg(t *testing.T) { tests = append(tests, test{ name: "kitty_start", cmds: []Cmd{DisableKeyboardEnhancements, EnableKeyboardEnhancements(WithKeyReleases)}, - expected: "\x1b[?25l\x1b[?2004h\x1b[>4;1m\x1b[>3u\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[>4;0m\x1b[>0u", + expected: "\x1b[?25l\x1b[?2004h\x1b[>4;1m\x1b[>3u\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[>4;0m\x1b[>u", }) } From 4f0e1e15a2b2f40d9d1b33c04872f4e5b2edc7ec Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Tue, 14 Jan 2025 15:35:56 -0300 Subject: [PATCH 30/30] chore(deps): update --- examples/go.mod | 10 +++++----- examples/go.sum | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index a603068e18..334b631375 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -3,13 +3,13 @@ module examples go 1.23.1 require ( - github.com/charmbracelet/bubbles/v2 v2.0.0-alpha.2.0.20241121172047-bd415b4ebae8 - github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241121171714-fbd5423ea935 + github.com/charmbracelet/bubbles/v2 v2.0.0-alpha.2.0.20250114183437-fbe642df174c + github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250114183054-9f703251e0d7 github.com/charmbracelet/colorprofile v0.1.8 github.com/charmbracelet/glamour v0.8.0 github.com/charmbracelet/harmonica v0.2.0 - github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804 - github.com/charmbracelet/x/ansi v0.6.0 + github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250114171829-b67eb015d607 + github.com/charmbracelet/x/ansi v0.7.0 github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 github.com/lucasb-eyer/go-colorful v1.2.0 @@ -25,7 +25,7 @@ require ( github.com/charmbracelet/lipgloss v0.13.0 // indirect github.com/charmbracelet/x/cellbuf v0.0.6 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect - github.com/charmbracelet/x/input v0.2.1-0.20241210230033-45c33cb7b57a // indirect + github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/vt v0.0.0-20241121165045-a3720547cbb4 // indirect github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 // indirect diff --git a/examples/go.sum b/examples/go.sum index f7ad5ef170..9320d9aecb 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -14,8 +14,8 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/charmbracelet/bubbles/v2 v2.0.0-alpha.2.0.20241121172047-bd415b4ebae8 h1:CRhvWh0cIainbY47znHAxzohyXDNmcmrp9ggjvn1cJk= -github.com/charmbracelet/bubbles/v2 v2.0.0-alpha.2.0.20241121172047-bd415b4ebae8/go.mod h1:fKcC1zxdgRjgg21XbRIf/bkSELpd9D9XKlHRCrhR1Tk= +github.com/charmbracelet/bubbles/v2 v2.0.0-alpha.2.0.20250114183437-fbe642df174c h1:hhR5M/3Wt/mKLTPF/MyvA4/WWtnTmIzLXo69pW/9S5s= +github.com/charmbracelet/bubbles/v2 v2.0.0-alpha.2.0.20250114183437-fbe642df174c/go.mod h1:M271uOSMoLQsiVV1yhFZx6JprPQCVXgLYpSEbWXtidM= github.com/charmbracelet/colorprofile v0.1.8 h1:PywDeXsiAzlPtkiiKgMEVLvb6nlEuKrMj9+FJBtj4jU= github.com/charmbracelet/colorprofile v0.1.8/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs= @@ -24,18 +24,18 @@ github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw= github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= -github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804 h1:7CYjb9YMZA4kMhLgGdtlXvq+nu1oyENpMyMQlTvqSFw= -github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= -github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= -github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= +github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250114171829-b67eb015d607 h1:lERE4ow371r5WMqQAt7Eqlg1A4tBNA8T4RLwdXnKyBo= +github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250114171829-b67eb015d607/go.mod h1:MD7Vb+O1zFRgBo+F94JHHuME7df8XBByNKuX5k/L/qs= +github.com/charmbracelet/x/ansi v0.7.0 h1:/QfFmiXOGGwN6fRbzvQaYp7fu1pkxpZ3qFBZWBsP404= +github.com/charmbracelet/x/ansi v0.7.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= github.com/charmbracelet/x/cellbuf v0.0.6 h1:pJUWN/G1jbt1Nj/+ILfC2/ABQoZzWu1vG73yHQEYELI= github.com/charmbracelet/x/cellbuf v0.0.6/go.mod h1:d72o71glp8flkCz54PHLe3+nuw5u2v3UxmKqruUERWQ= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U= github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233/go.mod h1:cw9df32BXdkcd0LzAHsFMmvXOsrrlDKazIW8PCq0cPM= -github.com/charmbracelet/x/input v0.2.1-0.20241210230033-45c33cb7b57a h1:CspqRQ5YjX8qE/3/QefJf3twGokKeM42I/fZmx72H90= -github.com/charmbracelet/x/input v0.2.1-0.20241210230033-45c33cb7b57a/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= +github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= +github.com/charmbracelet/x/input v0.3.0/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= 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/x/vt v0.0.0-20241121165045-a3720547cbb4 h1:EacjHxcQEEgOZ7TbkAU3b84hd1Bn5NwA8YV5uyJ9EI4=