[GH-ISSUE #645] Is there a way to make a box a "terminal view" ? #470

Closed
opened 2026-03-04 01:05:16 +03:00 by kerem · 10 comments
Owner

Originally created by @khepin on GitHub (Sep 13, 2021).
Original GitHub issue: https://github.com/rivo/tview/issues/645

I was trying to create a UI that would look something like:

------------ Info About Files In Sync To Server -------------
| detected file change                                      |
| uploading file to remote server                           |
| successfully uploaded                                     |
-------------------------------------------------------------
-------------- Bash Session On Remote Server ----------------
| welcome to remote server                                  |
| $> _                                                      |
|                                                           |
|                                                           |
|                                                           |
|                                                           |
|                                                           |
|                                                           |
|                                                           |
-------------------------------------------------------------

Where the top box is just information about detecting local file changes and syncing those upstream and the bottom one is a bash terminal / ssh session to said upstream. Something where the bottom window could receive an stdin, stderr, stdout.

At first glance I don't see a way to make something like this happen. Am I missing something?

Originally created by @khepin on GitHub (Sep 13, 2021). Original GitHub issue: https://github.com/rivo/tview/issues/645 I was trying to create a UI that would look something like: ``` ------------ Info About Files In Sync To Server ------------- | detected file change | | uploading file to remote server | | successfully uploaded | ------------------------------------------------------------- -------------- Bash Session On Remote Server ---------------- | welcome to remote server | | $> _ | | | | | | | | | | | | | | | ------------------------------------------------------------- ``` Where the top box is just information about detecting local file changes and syncing those upstream and the bottom one is a bash terminal / ssh session to said upstream. Something where the bottom window could receive an `stdin, stderr, stdout`. At first glance I don't see a way to make something like this happen. Am I missing something?
kerem closed this issue 2026-03-04 01:05:17 +03:00
Author
Owner

@rgrannell1 commented on GitHub (Sep 14, 2021):

I believe this is possible, I'm doing something similar:

github.com/rgrannell1/rl@f24ac01815/process.go (L45)

  • Create a TextView to hold terminal stderr, stdout output
  • Execute an SSH connection using exec.Cmd, and redirect output to the TextView component writer
  • You might need to play around with /dev/tty and handling / passing keystrokes through

Sorry about the vague answer, but I hope it helps

<!-- gh-comment-id:918712753 --> @rgrannell1 commented on GitHub (Sep 14, 2021): I believe this is possible, I'm doing something similar: https://github.com/rgrannell1/rl/blob/f24ac01815571b17a4ba59906c7942a1bceb04e3/process.go#L45 - Create a `TextView` to hold terminal stderr, stdout output - Execute an SSH connection using `exec.Cmd`, and redirect output to the TextView component writer - You might need to play around with `/dev/tty` and handling / passing keystrokes through Sorry about the vague answer, but I hope it helps
Author
Owner

@khepin commented on GitHub (Sep 14, 2021):

Yup, stdout, stderr I got alright indeed. The sticky part seems to be

play around with /dev/tty and handling / passing keystrokes through

<!-- gh-comment-id:918763116 --> @khepin commented on GitHub (Sep 14, 2021): Yup, stdout, stderr I got alright indeed. The sticky part seems to be > play around with /dev/tty and handling / passing keystrokes through
Author
Owner

@rgrannell1 commented on GitHub (Sep 14, 2021):

I figured; I would have been more exact if this was something I'd implemented (note I'm a user, not the package dev). My well-commented CLI RL does most of what you're trying, so it might be a useful reference. It:

  • Spins up the UI
  • Uses tview listeners like SetInputCapture and SetDoneFunc and an InputField to capture user-entered text
  • Stores this in a UI-wide state object
  • Launches a bash process (in process.go:StartCommand) based on TUI state and user-input, and intercepts output. It pipes stdout to a TextViewer

What I see as difficult in your case is working around

Pseudo-terminal will not be allocated because stdin is not a terminal

However you can force TTY use using ssh -tt <ip> which works for RL. SSH accepts commands from stdin so

echo "ls" | ssh -tt <ip>

Run from exec.Cmd will achieve what you want. For example, when I run this command from RL it SSH's into my multipass VM, runs commands like list (awkwardly, rl is not an SSH client really) and returns standard-output (only, RL handles stderr badly at the moment) to a textview component. It even renders catimg images correctly!

Screenshot from 2021-09-14 14-06-10

I hope this helps, reach out via this ticket (if the package-owner doesn't object) and I'll try to help

<!-- gh-comment-id:919135482 --> @rgrannell1 commented on GitHub (Sep 14, 2021): I figured; I would have been more exact if this was something I'd implemented (note I'm a user, not the package dev). My well-commented CLI [RL](https://github.com/rgrannell1/rl/blob/master/rl.go) does _most_ of what you're trying, so it might be a useful reference. It: - Spins up the UI - Uses tview listeners like `SetInputCapture` and `SetDoneFunc` and an `InputField` to capture user-entered text - Stores this in a UI-wide state object - Launches a bash process (in `process.go:StartCommand)` based on TUI state and user-input, and intercepts output. It pipes `stdout` to a TextViewer What I see as difficult in your case is working around `Pseudo-terminal will not be allocated because stdin is not a terminal` However you can force TTY use using `ssh -tt <ip>` which works for RL. SSH accepts commands from `stdin` so ```bash echo "ls" | ssh -tt <ip> ``` Run from `exec.Cmd` will achieve what you want. For example, when I run this command from RL it SSH's into my `multipass` VM, runs commands like list (awkwardly, rl is not an SSH client really) and returns standard-output (only, RL handles stderr badly at the moment) to a textview component. It even renders `catimg` images correctly! ![Screenshot from 2021-09-14 14-06-10](https://user-images.githubusercontent.com/835618/133262972-7e477b7c-590a-4636-ab88-aa8ddfc887bc.png) I hope this helps, reach out via this ticket (if the package-owner doesn't object) and I'll try to help
Author
Owner

@khepin commented on GitHub (Sep 14, 2021):

I think I was able to approach my goal using https://github.com/creack/pty

But capturing STDIN and io.Copy-ing it to a TextView seems to suffer from a lot of performance drag and characters are missed out entirely. The same code outside of a TextView, using a buffer or stdout as the place to write works "just fine™".

If I could figure out the perf issue there, then the remaining piece would be figuring out how control sequences are sent. That doesn't seem to work with the PTY based solution I tried so far.

<!-- gh-comment-id:919297203 --> @khepin commented on GitHub (Sep 14, 2021): I think I was able to approach my goal using https://github.com/creack/pty But capturing STDIN and io.Copy-ing it to a TextView seems to suffer from a lot of performance drag and characters are missed out entirely. The same code outside of a TextView, using a buffer or stdout as the place to write works "just fine™". If I could figure out the perf issue there, then the remaining piece would be figuring out how control sequences are sent. That doesn't seem to work with the PTY based solution I tried so far.
Author
Owner

@khepin commented on GitHub (Sep 14, 2021):

Also, thanks, I'll dig into SetInputCapture and your CLI today if I can.

<!-- gh-comment-id:919298109 --> @khepin commented on GitHub (Sep 14, 2021): Also, thanks, I'll dig into `SetInputCapture` and your CLI today if I can.
Author
Owner

@rgrannell1 commented on GitHub (Sep 14, 2021):

one gotcha I spent a lot of time root-causing. When commands are run non-interactively, they may buffer output and not work as expected (for me, grep would not output). You may need to use the stdbuf and unbuffer shell commands (or a Golang equivalent) if you run into similar issues

<!-- gh-comment-id:919481997 --> @rgrannell1 commented on GitHub (Sep 14, 2021): one gotcha I spent a lot of time root-causing. When commands are run non-interactively, they may buffer output and not work as expected (for me, grep would not output). You may need to use the `stdbuf` and `unbuffer` shell commands (or a Golang equivalent) if you run into similar issues
Author
Owner

@rivo commented on GitHub (Sep 26, 2021):

@khepin You'll definitely get a performance hit. Characters from the external application will be stored in a buffer, that buffer will be formatted to fit the TextView, the resulting characters will be saved to a tcell screen buffer, and tcell will then output Escape sequences to your terminal. If you use ANSIWriter, e.g. for coloured output, then there is even one more step.

But that's just due to the architecture of the whole project. There's no way to bypass this (except by suspending the application temporarily). What you can do, however, is to space out your TextView redraws so the steps above don't happen after each character sent by your application.

<!-- gh-comment-id:927339852 --> @rivo commented on GitHub (Sep 26, 2021): @khepin You'll definitely get a performance hit. Characters from the external application will be stored in a buffer, that buffer will be formatted to fit the `TextView`, the resulting characters will be saved to a `tcell` screen buffer, and `tcell` will then output Escape sequences to your terminal. If you use [`ANSIWriter`](https://pkg.go.dev/github.com/rivo/tview#ANSIWriter), e.g. for coloured output, then there is even one more step. But that's just due to the architecture of the whole project. There's no way to bypass this (except by [suspending](https://pkg.go.dev/github.com/rivo/tview#Application.Suspend) the application temporarily). What you can do, however, is to space out your `TextView` redraws so the steps above don't happen after each character sent by your application.
Author
Owner

@khepin commented on GitHub (Sep 27, 2021):

Yup, this was using an ANSIWriter. I'll see if I can give a shot at spacing the TextView redraws a bit more and if that helps enough.

<!-- gh-comment-id:928087542 --> @khepin commented on GitHub (Sep 27, 2021): Yup, this was using an ANSIWriter. I'll see if I can give a shot at spacing the `TextView` redraws a bit more and if that helps enough.
Author
Owner

@tedsmitt commented on GitHub (Oct 27, 2022):

@khepin did you ever find a solution to this? Would like to achieve the same functionality!

<!-- gh-comment-id:1292855072 --> @tedsmitt commented on GitHub (Oct 27, 2022): @khepin did you ever find a solution to this? Would like to achieve the same functionality!
Author
Owner

@khepin commented on GitHub (Oct 27, 2022):

Nope, I stopped working on what I needed it for too so I don't have much to offer.

<!-- gh-comment-id:1294214865 --> @khepin commented on GitHub (Oct 27, 2022): Nope, I stopped working on what I needed it for too so I don't have much to offer.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/tview#470
No description provided.