Skip to content
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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,6 @@ useradd --system --user-group --create-home myapp

That should do it.

###
Copy link
Member Author

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


## Feedback

We’d love to hear your thoughts on this project. Feel free to drop us a note!
Expand Down
33 changes: 33 additions & 0 deletions docs/README.md
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
176 changes: 176 additions & 0 deletions docs/truecolor.md
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
Copy link
Member Author

Choose a reason for hiding this comment

The 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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the chain of deps is more like wish -> lipgloss -> termenv

Copy link
Member Author

Choose a reason for hiding this comment

The 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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> Wish uses [termenv][termenv] under the hood, which will set truecolor for a few
> Wish uses [termenv][termenv] under the hood, which will set `truecolor` for a few

> 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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
NO_COLOR vs CLICOLOR: if NO_COLOR is set or CLICOLOR=0 then the output should
`NO_COLOR` vs `CLICOLOR`: if `NO_COLOR `is set or `CLICOLOR=0` then the output should

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
Loading