-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs: add truecolor info #273
base: main
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Wish Docs | ||
|
||
Welcome to the Wish official documentation. If you're new to the project, make | ||
sure you check out the [README][README] for a high-level overview of the | ||
project and how to get started. From there, take a look at the | ||
[examples][examples]. They're ordered by complexity and will show you how to | ||
use Wish. If you're looking for additional functionality not shown in the | ||
examples, I'd highly recommend checking out the [godoc at | ||
pkg.go.dev][pkg.go.dev]. The godoc showcases all of the functionality available | ||
to you with Wish with descriptions on what those functions do. | ||
|
||
If you want *more* examples from projects of various sizes, | ||
[sourcegraph][sourcegraph] is a great code search tool that can help you find | ||
other projects that use Wish. A great way to leverage this tool is when looking | ||
at our API (in the godoc), do a global search for that specific function. e.g. | ||
`wish.NewServer`. If you have questions about the limitations of Wish, we're | ||
happy to help, though given we're a small team of maintainers, it may take some | ||
time. In the meantime, you may find that [phorm.ai][phorm] is helpful in | ||
answering your questions about Charm projects. We've found it to be quite | ||
helpful so far. | ||
|
||
This directory includes additional information about how Wish works. The topics | ||
chosen for this section are based on frequently asked questions in the | ||
community. If there's a topic you'd like to see covered in this section, please | ||
submit an [issue][issue] with links to or quotes of questions on that topic. | ||
That will allow us to ensure we're answering the question effectively. | ||
|
||
[README]: https://github.com/charmbracelet/wish?tab=readme-ov-file#wish | ||
[examples]: https://github.com/charmbracelet/wish/tree/main/examples#wish-examples | ||
[pkg.go.dev]:https://pkg.go.dev/github.com/charmbracelet/wish | ||
[issue]: https://github.com/charmbracelet/wish/issues/new/choose | ||
[phorm]: https://www.phorm.ai | ||
[sourcegraph]: https://sourcegraph.com/search?q=context:global+wish.NewServer&patternType=keyword&sm=0 |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,176 @@ | ||||||
|
||||||
# Truecolor Over SSH With Wish | ||||||
|
||||||
## Color profiles? What, like it's hard? | ||||||
|
||||||
In an SSH session, the client only sends the `TERM` environment variable, which | ||||||
can only detect `256 color` support. If you run `echo $COLORTERM` in your shell | ||||||
you'll likely see `truecolor` as the result, which is what you want for ultra | ||||||
colorful terminal outputs. If you don't see that as a result, it might be time | ||||||
to try a new [terminal emulator][supported-emulators]. | ||||||
|
||||||
Unfortunately, there is no standard way for terminals to detect `truecolor` | ||||||
support in an SSH session, hence why it defaults to `256 color`. Most terminals | ||||||
express their `truecolor` support with the `COLORTERM` environment variable, | ||||||
but this doesn't get sent when connecting over SSH. One workaround is to make | ||||||
SSH send this environment variable using `SendEnv COLORTERM` in the [`ssh | ||||||
config`][truecolor-ssh]. By default, the OpenSSH client (what you're using when | ||||||
you run `ssh`) will only send the `TERM` to the remote, so other variables must | ||||||
be configured. In the future, we hope to solve this problem by querying the | ||||||
terminal for support if `COLORTERM` is not detected. | ||||||
|
||||||
> [!NOTE] | ||||||
> Wish uses [termenv][termenv] under the hood, which will set truecolor for a few | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't even know if this sentence makes sense 😂 may need a lil tidy later There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the chain of deps is more like wish -> lipgloss -> termenv There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. going to leave this part out for now given we're going to intro term changes soon which might include different default behaviour There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
> terminal emulators that are known to support truecolor. | ||||||
|
||||||
You can learn more about [checking for `COLORTERM`][colorterm-issue]. | ||||||
|
||||||
Because of this, the color options are limited and your experience running the | ||||||
app locally will differ to how it presents over SSH. You're probably wondering | ||||||
*how much* of a difference this makes. Well, `256 color` support uses a palette | ||||||
with 256 colors available. By contrast, `truecolor` supports a whopping 16 | ||||||
**million** different colors. | ||||||
|
||||||
[Learn more about color standards for terminal emulators][termstandard] | ||||||
|
||||||
## Solving this in Wish | ||||||
|
||||||
### What is Wish | ||||||
|
||||||
Wish is an SSH server that allows you to make your apps accessible over SSH. It | ||||||
uses SSH middleware to handle connections, so you can serve specific actions to | ||||||
the user, then call the next middleware. | ||||||
|
||||||
Wish uses the SSH protocol to authenticate users, then allows the developer to | ||||||
specify how to handle these connections. We've used wish to serve both TUIs | ||||||
(textual UIs) *and* CLIs. If you've hosted your own [Soft Serve][soft] git | ||||||
server, then you'll have seen this first hand. Soft Serve uses a middleware to | ||||||
serve the TUI and another middleware for its CLI, allowing users to interact | ||||||
with the server through either interface. In this case, the CLI is useful for | ||||||
any server administration tasks, while the TUI provides a great interface to | ||||||
view your repositories. Note that both of these options are accessible through | ||||||
the same port (pretty neat). | ||||||
|
||||||
Similar to a website, this process runs on the server, freeing up your computer's | ||||||
resources for other things. What's great about this is it also gives you a | ||||||
consistent state no matter where you connect from (as long as you've got your | ||||||
authorized SSH keys with you). | ||||||
|
||||||
### Workarounds | ||||||
|
||||||
Alright, back to the `truecolor` issue. To force `truecolor` support in Wish to | ||||||
give you all those **precious** color choices, you'll want to use the | ||||||
`bm.MiddlewareWithColorProfile(handler, termenv.TrueColor)` as a middleware for | ||||||
your `wish` server. This will force `truecolor` support. For example: | ||||||
|
||||||
TODO: ayman confirm this example is correct | ||||||
```go | ||||||
import ( | ||||||
// ... | ||||||
tea "github.com/charmbracelet/bubbletea" | ||||||
"github.com/charmbracelet/lipgloss" | ||||||
"github.com/charmbracelet/log" | ||||||
"github.com/charmbracelet/ssh" | ||||||
"github.com/charmbracelet/wish" | ||||||
"github.com/charmbracelet/wish/activeterm" | ||||||
"github.com/charmbracelet/wish/bubbletea" | ||||||
"github.com/charmbracelet/wish/logging" | ||||||
) | ||||||
|
||||||
// ... | ||||||
|
||||||
func main() { | ||||||
s, err := wish.NewServer( | ||||||
wish.WithAddress(net.JoinHostPort(host, port)), | ||||||
wish.WithHostKeyPath(".ssh/id_ed25519"), | ||||||
wish.WithMiddleware( | ||||||
bubbletea.MiddlewareWithColorProfile(teaHandler, termenv.TrueColor) // Force truecolor. | ||||||
activeterm.Middleware(), // Bubble Tea apps usually require a PTY. | ||||||
logging.Middleware(), | ||||||
), | ||||||
) ), | ||||||
) | ||||||
// ... | ||||||
``` | ||||||
|
||||||
Alternatively, you can use a custom renderer for each session. | ||||||
|
||||||
```go | ||||||
// You can wire any Bubble Tea model up to the middleware with a function that | ||||||
// handles the incoming ssh.Session. Here we just grab the terminal info and | ||||||
// pass it to the new model. You can also return tea.ProgramOptions (such as | ||||||
// tea.WithAltScreen) on a session by session basis. | ||||||
func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) { | ||||||
// This should never fail, as we are using the activeterm middleware. | ||||||
pty, _, _ := s.Pty() | ||||||
|
||||||
// When running a Bubble Tea app over SSH, you shouldn't use the default | ||||||
// lipgloss.NewStyle function. | ||||||
// That function will use the color profile from the os.Stdin, which is the | ||||||
// server, not the client. | ||||||
// We provide a MakeRenderer function in the bubbletea middleware package, | ||||||
// so you can easily get the correct renderer for the current session, and | ||||||
// use it to create the styles. | ||||||
// The recommended way to use these styles is to then pass them down to | ||||||
// your Bubble Tea model. | ||||||
renderer := bubbletea.MakeRenderer(s) | ||||||
txtStyle := renderer.NewStyle().Foreground(lipgloss.Color("10")) | ||||||
quitStyle := renderer.NewStyle().Foreground(lipgloss.Color("8")) | ||||||
|
||||||
m := model{ | ||||||
term: pty.Term, | ||||||
width: pty.Window.Width, | ||||||
height: pty.Window.Height, | ||||||
txtStyle: txtStyle, | ||||||
quitStyle: quitStyle, | ||||||
} | ||||||
return m, []tea.ProgramOption{tea.WithAltScreen()} | ||||||
} | ||||||
``` | ||||||
|
||||||
[see more][examples-bubbletea] | ||||||
|
||||||
It's up to you to decide which solution works better for you. In the case of | ||||||
forcing `truecolor` this may cause issues for users accessing your Wish app | ||||||
through a terminal emulator that is not `truecolor` compatible (e.g. Apple's | ||||||
Terminal app). | ||||||
|
||||||
TODO: are there any performance differences between these two options? | ||||||
|
||||||
### Roadmap for improvement | ||||||
|
||||||
**Is there a best practice?** | ||||||
|
||||||
> Not right now. Both are viable options. There's still work to be done to | ||||||
> support it properly with Bubble Tea. We're working on a solution to make it | ||||||
> easier to pass a custom renderer to `tea` Programs, but the launch date is | ||||||
> still TBD. | ||||||
|
||||||
## Noteworthy environment variables (for debugging) | ||||||
|
||||||
`TERM` - provides information about your terminal emulator's capabilities. This | ||||||
is the only environment variable out of this list that is sent in an SSH | ||||||
session. The rest are included for debugging purposes only. | ||||||
|
||||||
`COLORTERM` - provides information about your terminal emulator's color | ||||||
capabilities. Used primarily to specify if your emulator has `truecolor` | ||||||
support. | ||||||
|
||||||
`NO_COLOR` - turns colors on and off. `NO_COLOR=1` for non-colored text | ||||||
outputs, `NO_COLOR=0` for colored text outputs. | ||||||
|
||||||
`CLICOLOR` - turns colors on and off. `CLICOLOR=1` for colored text outputs, | ||||||
`CLICOLOR=0` for non-colored text outputs. | ||||||
|
||||||
`CLICOLOR_FORCE` - overrides `CLICOLOR`. | ||||||
|
||||||
NO_COLOR vs CLICOLOR: if NO_COLOR is set or CLICOLOR=0 then the output should | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
not be colored. Otherwise, the output can include ansi sequences. | ||||||
|
||||||
[termstandard]: https://github.com/termstandard/colors | ||||||
[supported-emulators]: https://github.com/termstandard/colors?tab=readme-ov-file#terminal-emulators | ||||||
[truecolor-ssh]: https://fixnum.org/2023-03-22-helix-truecolor-ssh-screen/ | ||||||
[colorterm-issue]: https://github.com/termstandard/colors?tab=readme-ov-file#truecolor-detection | ||||||
[examples-bubbletea]: https://github.com/charmbracelet/wish/blob/main/examples/bubbletea/main.go#L35 | ||||||
[soft]: https://github.com/charmbracelet/soft-serve | ||||||
[termenv]: https://github.com/muesli/termenv/blob/345783024a348cbb893bf6f08f1d7ab79d2e22ff/termenv_unix.go#L53 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh HA, I can add this back in. I thought I had accidentally added this