[GH-ISSUE #70] Run 3rd party program #50

Closed
opened 2026-03-04 01:01:27 +03:00 by kerem · 11 comments
Owner

Originally created by @lesovsky on GitHub (Mar 10, 2018).
Original GitHub issue: https://github.com/rivo/tview/issues/70

Hi,

I'm trying to run 3rd party program such as "less", "vi", "psql" but when 3rd party program is finished I can't safely return to the main UI mode. When it's finished, I've got different artifacts in UI mode and I need to completely redraw all primitives.

Could you assist me to solve this issue?

I have a following example and problem is near the runPager() function.

package main

/* DONE: make basic layout */ /* DONE: remove borders */ /* TODO: fit interfaces details */
/* DONE: add text into textview areas */
/* DONE: refresh textviews in the loop */
/* DONE: add hotkeys */ /* DONE: print message when hotkeys are pressed */
/* DONE: add additional textview are at hotkey */
/* TODO: run 3rd part program and return back */

import (
	ui "github.com/rivo/tview"
	"github.com/gdamore/tcell"
	"time"
	"os"
	"fmt"
	"os/exec"
)

var (
	do_pause = make(chan int)
	addt_toggle = false

	app = ui.NewApplication()
	sys = ui.NewTextView().SetTextAlign(ui.AlignLeft).SetChangedFunc(func() { app.Draw() })
	pgo = ui.NewTextView().SetTextAlign(ui.AlignLeft).SetChangedFunc(func() { app.Draw() })
	cmdl = ui.NewTextView().SetTextAlign(ui.AlignLeft).SetChangedFunc(func() { app.Draw() })
	stat = ui.NewTextView().SetTextAlign(ui.AlignLeft).SetChangedFunc(func() { app.Draw() })
	footer = ui.NewTextView().SetTextAlign(ui.AlignLeft).SetChangedFunc(func() { app.Draw() })

	header = ui.NewFlex().SetDirection(ui.FlexColumn).
			AddItem(sys, 0, 1, false).
			AddItem(pgo, 0, 1, false)

	flex = ui.NewFlex().SetDirection(ui.FlexRow).
			AddItem(header, 5, 1, false).
			AddItem(cmdl, 1, 1, false).
			AddItem(stat, 0, 1, false)
)

func main() {
	app.SetInputCapture(keybindings)

	go showTime()

	if err := app.SetRoot(flex, true).Run(); err != nil {
		panic(err)
	}
}

func keybindings(event *tcell.EventKey) *tcell.EventKey {
	switch event.Key() {
	case tcell.KeyCtrlQ:
		app.Stop()
		os.Exit(0)
	case tcell.KeyRune:
		switch event.Rune() {
		case 'b':
			if addt_toggle {
				flex.RemoveItem(footer)
				addt_toggle = !addt_toggle
				printCmd("cmd: remove additional")
			} else {
				flex.AddItem(footer, 0, 4, false)
				addt_toggle = !addt_toggle
				printCmd("cmd: show additional")
			}
		case 'c':
			if err := runPager(); err != nil { panic(err) }
		case 'p':
			printCmd("cmd: pause exec")
			do_pause <-1
		default:
			printCmd("cmd: unknown command")
		}
	}
	return event
}

func runPager() error {
	do_pause <- 1
	cmd := exec.Command("less", "/etc/sysctl.conf")
	cmd.Stdout = os.Stdout
	err := cmd.Run()
	do_pause <- 1
	return err
}

func showTime() {
	var pause = false

	for {
		select {
		case <-do_pause:
			pause = !pause
		case <-time.After(1 * time.Second):
			if pause { continue }

			cmdl.Clear()
			printSys()
			printPgo()
			printStat()
			if addt_toggle {
				printAddt()
			}
		}
	}
}

func printSys() {
	sys.Clear()
	fmt.Fprintf(sys, "pgcenter: %s\n", time.Now().Format("2006-01-02 15:04:05.000"))
	fmt.Fprintln(sys, "   %%cpu:")
	fmt.Fprintln(sys, " MiB mem:")
	fmt.Fprintln(sys, "MiB swap:")
}

func printPgo() {
	pgo.Clear()
	fmt.Fprintf(pgo, "     conn1: %s\n", time.Now().Format("2006-01-02 15:04:05.000"))
	fmt.Fprintln(pgo, "  activity:")
	fmt.Fprintln(pgo, "autovacuum:")
	fmt.Fprintln(pgo, "statements:")
}

func printCmd(s string) {
	fmt.Fprintln(cmdl, s)
}

func printStat() {
	stat.Clear()
	fmt.Fprintf(stat, "stat: %s\n", time.Now().Format("2006-01-02 15:04:05.000"))
}

func printAddt() {
	footer.Clear()
	fmt.Fprintf(footer, "stat: %s\n", time.Now().Format("2006-01-02 15:04:05.000"))
}
Originally created by @lesovsky on GitHub (Mar 10, 2018). Original GitHub issue: https://github.com/rivo/tview/issues/70 Hi, I'm trying to run 3rd party program such as "less", "vi", "psql" but when 3rd party program is finished I can't safely return to the main UI mode. When it's finished, I've got different artifacts in UI mode and I need to completely redraw all primitives. Could you assist me to solve this issue? I have a following example and problem is near the runPager() function. ``` package main /* DONE: make basic layout */ /* DONE: remove borders */ /* TODO: fit interfaces details */ /* DONE: add text into textview areas */ /* DONE: refresh textviews in the loop */ /* DONE: add hotkeys */ /* DONE: print message when hotkeys are pressed */ /* DONE: add additional textview are at hotkey */ /* TODO: run 3rd part program and return back */ import ( ui "github.com/rivo/tview" "github.com/gdamore/tcell" "time" "os" "fmt" "os/exec" ) var ( do_pause = make(chan int) addt_toggle = false app = ui.NewApplication() sys = ui.NewTextView().SetTextAlign(ui.AlignLeft).SetChangedFunc(func() { app.Draw() }) pgo = ui.NewTextView().SetTextAlign(ui.AlignLeft).SetChangedFunc(func() { app.Draw() }) cmdl = ui.NewTextView().SetTextAlign(ui.AlignLeft).SetChangedFunc(func() { app.Draw() }) stat = ui.NewTextView().SetTextAlign(ui.AlignLeft).SetChangedFunc(func() { app.Draw() }) footer = ui.NewTextView().SetTextAlign(ui.AlignLeft).SetChangedFunc(func() { app.Draw() }) header = ui.NewFlex().SetDirection(ui.FlexColumn). AddItem(sys, 0, 1, false). AddItem(pgo, 0, 1, false) flex = ui.NewFlex().SetDirection(ui.FlexRow). AddItem(header, 5, 1, false). AddItem(cmdl, 1, 1, false). AddItem(stat, 0, 1, false) ) func main() { app.SetInputCapture(keybindings) go showTime() if err := app.SetRoot(flex, true).Run(); err != nil { panic(err) } } func keybindings(event *tcell.EventKey) *tcell.EventKey { switch event.Key() { case tcell.KeyCtrlQ: app.Stop() os.Exit(0) case tcell.KeyRune: switch event.Rune() { case 'b': if addt_toggle { flex.RemoveItem(footer) addt_toggle = !addt_toggle printCmd("cmd: remove additional") } else { flex.AddItem(footer, 0, 4, false) addt_toggle = !addt_toggle printCmd("cmd: show additional") } case 'c': if err := runPager(); err != nil { panic(err) } case 'p': printCmd("cmd: pause exec") do_pause <-1 default: printCmd("cmd: unknown command") } } return event } func runPager() error { do_pause <- 1 cmd := exec.Command("less", "/etc/sysctl.conf") cmd.Stdout = os.Stdout err := cmd.Run() do_pause <- 1 return err } func showTime() { var pause = false for { select { case <-do_pause: pause = !pause case <-time.After(1 * time.Second): if pause { continue } cmdl.Clear() printSys() printPgo() printStat() if addt_toggle { printAddt() } } } } func printSys() { sys.Clear() fmt.Fprintf(sys, "pgcenter: %s\n", time.Now().Format("2006-01-02 15:04:05.000")) fmt.Fprintln(sys, " %%cpu:") fmt.Fprintln(sys, " MiB mem:") fmt.Fprintln(sys, "MiB swap:") } func printPgo() { pgo.Clear() fmt.Fprintf(pgo, " conn1: %s\n", time.Now().Format("2006-01-02 15:04:05.000")) fmt.Fprintln(pgo, " activity:") fmt.Fprintln(pgo, "autovacuum:") fmt.Fprintln(pgo, "statements:") } func printCmd(s string) { fmt.Fprintln(cmdl, s) } func printStat() { stat.Clear() fmt.Fprintf(stat, "stat: %s\n", time.Now().Format("2006-01-02 15:04:05.000")) } func printAddt() { footer.Clear() fmt.Fprintf(footer, "stat: %s\n", time.Now().Format("2006-01-02 15:04:05.000")) } ```
kerem closed this issue 2026-03-04 01:01:28 +03:00
Author
Owner

@rivo commented on GitHub (Mar 10, 2018):

Hi, tcell (and therefore tview) currently doesn't work well when you send your own output to stdout. This messes up the cell buffer.

Wouldn't it be better to pipe the output of less into its own TextView instead of stdout? When TextView has focus, you can also navigate it. Here's what I mean:

out    = ui.NewTextView().SetChangedFunc(func() { app.Draw() })
// ...
flex = ui.NewFlex().SetDirection(ui.FlexRow).
	AddItem(header, 5, 1, false).
	AddItem(cmdl, 1, 1, false).
	AddItem(stat, 0, 1, false).
	AddItem(out, 0, 4, true)
// ...
cmd := exec.Command("less", "/etc/sysctl.conf")
cmd.Stdout = out
err := cmd.Run()

This way, you don't need to leave the application.

Let me know if this works for you.

<!-- gh-comment-id:372026049 --> @rivo commented on GitHub (Mar 10, 2018): Hi, `tcell` (and therefore `tview`) currently doesn't work well when you send your own output to stdout. This messes up the cell buffer. Wouldn't it be better to pipe the output of `less` into its own `TextView` instead of stdout? When `TextView` has focus, you can also navigate it. Here's what I mean: ```go out = ui.NewTextView().SetChangedFunc(func() { app.Draw() }) // ... flex = ui.NewFlex().SetDirection(ui.FlexRow). AddItem(header, 5, 1, false). AddItem(cmdl, 1, 1, false). AddItem(stat, 0, 1, false). AddItem(out, 0, 4, true) // ... cmd := exec.Command("less", "/etc/sysctl.conf") cmd.Stdout = out err := cmd.Run() ``` This way, you don't need to leave the application. Let me know if this works for you.
Author
Owner

@lesovsky commented on GitHub (Mar 10, 2018):

It may works with pager, but with more complex programs like psql (postgresql interactive terminal) or vi it will not work.

Maybe it's possible to realize it as it done in C ncurses with endwin() and refresh() calls?
editor example
psql example

<!-- gh-comment-id:372032931 --> @lesovsky commented on GitHub (Mar 10, 2018): It may works with pager, but with more complex programs like psql (postgresql interactive terminal) or vi it will not work. Maybe it's possible to realize it as it done in C ncurses with endwin() and refresh() calls? [editor example](https://github.com/lesovsky/pgcenter/blob/master/src/hotkeys.c#L659-L687) [psql example](https://github.com/lesovsky/pgcenter/blob/master/src/hotkeys.c#L1262-L1289)
Author
Owner

@lesovsky commented on GitHub (Mar 10, 2018):

Yep, I've done it with app.Stop() and wrapping app.SetRoot() into infinte loop with additional do_quit flag.

do_quit = false
// ...
for {
		if err := app.SetRoot(flex, true).Run(); err != nil {
			panic(err)
		}
		if do_quit {
			break
		}
	}
// ...
func runPager() error {
	app.Stop()
	cmd := exec.Command("less", "/etc/sysctl.conf")
	cmd.Stdout = os.Stdout
	err := cmd.Run()
	return err
}

func runEditor() error {
	do_pause <-1
	app.Stop()
	cmd := exec.Command("vi", "/tmp/strace.out")
	cmd.Stdout = os.Stdout
	cmd.Stdin = os.Stdin
	err := cmd.Run()
	do_pause <-1
	return err
}

func runPsql() error {
	do_pause <-1
	app.Stop()
	cmd := exec.Command("psql", "-U", "postgres")
	cmd.Stdout = os.Stdout
	cmd.Stdin = os.Stdin
	err := cmd.Run()
	do_pause <-1
	return err
}
<!-- gh-comment-id:372035866 --> @lesovsky commented on GitHub (Mar 10, 2018): Yep, I've done it with app.Stop() and wrapping app.SetRoot() into infinte loop with additional `do_quit` flag. ``` do_quit = false // ... for { if err := app.SetRoot(flex, true).Run(); err != nil { panic(err) } if do_quit { break } } // ... func runPager() error { app.Stop() cmd := exec.Command("less", "/etc/sysctl.conf") cmd.Stdout = os.Stdout err := cmd.Run() return err } func runEditor() error { do_pause <-1 app.Stop() cmd := exec.Command("vi", "/tmp/strace.out") cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin err := cmd.Run() do_pause <-1 return err } func runPsql() error { do_pause <-1 app.Stop() cmd := exec.Command("psql", "-U", "postgres") cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin err := cmd.Run() do_pause <-1 return err } ```
Author
Owner

@rivo commented on GitHub (Mar 10, 2018):

Interesting. I wasn't sure if this was going to work. But it's good that you were able to make it work.

You probably won't even need to call SetRoot() in the loop. Run() will start a tcell session and Stop() will end it.

If I had given you a dedicated function for this purpose (e.g. something like Escape()), it probably would have done the same thing internally. I can still do this for you if you let me know that you need it.

<!-- gh-comment-id:372069242 --> @rivo commented on GitHub (Mar 10, 2018): Interesting. I wasn't sure if this was going to work. But it's good that you were able to make it work. You probably won't even need to call `SetRoot()` in the loop. `Run()` will start a `tcell` session and `Stop()` will end it. If I had given you a dedicated function for this purpose (e.g. something like `Escape()`), it probably would have done the same thing internally. I can still do this for you if you let me know that you need it.
Author
Owner

@lesovsky commented on GitHub (Mar 11, 2018):

If I had given you a dedicated function for this purpose (e.g. something like Escape()), it probably would have done the same thing internally. I can still do this for you if you let me know that you need it.

Of course, It would be great and this function will be convenient for me.

<!-- gh-comment-id:372090842 --> @lesovsky commented on GitHub (Mar 11, 2018): > If I had given you a dedicated function for this purpose (e.g. something like Escape()), it probably would have done the same thing internally. I can still do this for you if you let me know that you need it. Of course, It would be great and this function will be convenient for me.
Author
Owner

@rivo commented on GitHub (Mar 13, 2018):

Please have a look at Application.Suspend() which should do what you need.

There appears to be a bug in tcell (gdamore/tcell#194) though that leads to one key event being lost of the application comes back from "suspended mode". Once that is fixed, this should work as expected.

<!-- gh-comment-id:372570051 --> @rivo commented on GitHub (Mar 13, 2018): Please have a look at [`Application.Suspend()`](https://godoc.org/github.com/rivo/tview#Application.Suspend) which should do what you need. There appears to be a bug in `tcell` (gdamore/tcell#194) though that leads to one key event being lost of the application comes back from "suspended mode". Once that is fixed, this should work as expected.
Author
Owner

@lesovsky commented on GitHub (Mar 13, 2018):

Thanks a lot!
Even with mentioned bug, Application.Suspend() works as I expect and there is no need to use Application.Stop() + extra-loop for Application.Run()

<!-- gh-comment-id:372573595 --> @lesovsky commented on GitHub (Mar 13, 2018): Thanks a lot! Even with mentioned bug, `Application.Suspend()` works as I expect and there is no need to use `Application.Stop()` + extra-loop for `Application.Run()`
Author
Owner

@rivo commented on GitHub (Feb 20, 2021):

Reopening this as a reminder to switch to the new tcell API, see https://github.com/gdamore/tcell/issues/194.

<!-- gh-comment-id:782753957 --> @rivo commented on GitHub (Feb 20, 2021): Reopening this as a reminder to switch to the new `tcell` API, see https://github.com/gdamore/tcell/issues/194.
Author
Owner

@rivo commented on GitHub (Mar 11, 2021):

github.com/rivo/tview@b2dec96e1a implements tcell's new methods but fails when Application.Stop() is called while the app is suspended. Waiting for https://github.com/gdamore/tcell/issues/440 to fix this, so this issue is still open for now.

<!-- gh-comment-id:796978199 --> @rivo commented on GitHub (Mar 11, 2021): https://github.com/rivo/tview/commit/b2dec96e1a720d04e17745fbfb8d77214d8bee88 implements `tcell`'s new methods but fails when `Application.Stop()` is called while the app is suspended. Waiting for https://github.com/gdamore/tcell/issues/440 to fix this, so this issue is still open for now.
Author
Owner

@gdamore commented on GitHub (May 19, 2021):

I'm pretty sure that bug is fixed now. Please retest and let me know -- the only reason it's still open is for feedback.

<!-- gh-comment-id:844541903 --> @gdamore commented on GitHub (May 19, 2021): I'm pretty sure that bug is fixed now. Please retest and let me know -- the only reason it's still open is for feedback.
Author
Owner

@rivo commented on GitHub (May 20, 2021):

It works! Thanks!

<!-- gh-comment-id:845273375 --> @rivo commented on GitHub (May 20, 2021): It works! Thanks!
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#50
No description provided.