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

Run 3rd party program #70

Closed
lesovsky opened this issue Mar 10, 2018 · 11 comments
Closed

Run 3rd party program #70

lesovsky opened this issue Mar 10, 2018 · 11 comments

Comments

@lesovsky
Copy link

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"))
}
@rivo
Copy link
Owner

rivo commented 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.

@lesovsky
Copy link
Author

lesovsky commented 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

@lesovsky
Copy link
Author

lesovsky commented 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
}

@rivo
Copy link
Owner

rivo commented 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.

@lesovsky
Copy link
Author

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.

rivo added a commit that referenced this issue Mar 13, 2018
@rivo
Copy link
Owner

rivo commented 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.

@lesovsky
Copy link
Author

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()

@rivo
Copy link
Owner

rivo commented Feb 20, 2021

Reopening this as a reminder to switch to the new tcell API, see gdamore/tcell#194.

@rivo rivo reopened this Feb 20, 2021
@rivo
Copy link
Owner

rivo commented Mar 11, 2021

b2dec96 implements tcell's new methods but fails when Application.Stop() is called while the app is suspended. Waiting for gdamore/tcell#440 to fix this, so this issue is still open for now.

@gdamore
Copy link
Sponsor

gdamore commented 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.

@rivo rivo closed this as completed in c723ed0 May 20, 2021
@rivo
Copy link
Owner

rivo commented May 20, 2021

It works! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants