GOCUI - Go Console User Interface
Minimalist Go package aimed at creating Console User Interfaces. A community fork based on the amazing work of jroimartin For v0 to v1 mirgration help read: migrate-to-v1.md
Features
- Minimalist API.
- Views (the "windows" in the GUI) implement the interface io.ReadWriter.
- Support for overlapping views.
- The GUI can be modified at runtime (concurrent-safe).
- Global and view-level keybindings.
- Mouse support.
- Colored text.
- Customizable editing mode.
- Easy to build reusable widgets, complex layouts...
About fork
This fork has many improvements over the original work from jroimartin.
- Written ontop of TCell
- Better wide character support
- Support for 1 Line height views
- Support for running in docker container
- Better cursor handling
- Customize frame colors
- Improved code comments and quality
- Many small improvements
- Change Visibility of views
- Requires Go 1.13 or newer
For information about this org see: awesome-gocui/about.
Installation
Execute:
$ go get github.com/GoryMoon/gocui
Documentation
Execute:
$ go doc github.com/GoryMoon/gocui
Or visit godoc.org to read it online.
Example
See the _example folder for more examples
package main
import (
"fmt"
"log"
"github.com/GoryMoon/gocui"
)
func main() {
g, err := gocui.NewGui(gocui.OutputNormal, true)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetManagerFunc(layout)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) {
log.Panicln(err)
}
}
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0); err != nil {
if !errors.Is(err, gocui.ErrUnknownView) {
return err
}
if _, err := g.SetCurrentView("hello"); err != nil {
return err
}
fmt.Fprintln(v, "Hello world!")
}
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
Testing example
You can write simple tests for gocui
which let you simulate keyboard and then validate the output drawn to the screen.
- Create an instance of
gui
withOutputSimulator
set as the modeg, err := NewGui(OutputSimulator, true)
- Call
GetTestingScreen
to get atestingScreen
instance. - On this you can use
SendKey
to simulate input andGetViewContent
to evaluate what is drawn.
Warning: Timing plays a part here, key bindings don't fire synchronously and drawing isn't instant. Here we used
time.After
to pause,gomega
's asynchronous assertions are likely a better alternative for more complex tests.
Here is a simple example showing how this can be used to validate what a view shows and that a key binding is handled correctly:
func TestTestingScreenReturnsCorrectContent(t *testing.T) {
// Track what happened in the view, we'll assert on these
didCallCTRLC := false
expectedViewContent := "Hello world!"
viewName := "testView1"
// Create a view specifying the "OutputSimulator" mode
g, err := NewGui(OutputSimulator, true)
if err != nil {
log.Panicln(err)
}
g.SetManagerFunc(func(g *Gui) error {
maxX, maxY := g.Size()
if v, err := g.SetView(viewName, maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0); err != nil {
if !errors.Is(err, ErrUnknownView) {
return err
}
if _, err := g.SetCurrentView(viewName); err != nil {
return err
}
// Have the view draw "Hello world!"
fmt.Fprintln(v, expectedViewContent)
}
return nil
})
// Create a key binding which sets "didCallCTRLC" when triggered
exampleBindingToTest := func(g *Gui, v *View) error {
didCallCTRLC = true
return nil
}
if err := g.SetKeybinding("", KeyCtrlC, ModNone, exampleBindingToTest); err != nil {
log.Panicln(err)
}
// Create a test screen and start gocui
testingScreen := g.GetTestingScreen()
cleanup := testingScreen.StartGui()
defer cleanup()
// Send a key to gocui
testingScreen.SendKey(KeyCtrlC)
// Wait for key to be processed
<-time.After(time.Millisecond * 50)
// Test that the keybinding fired and set "didCallCTRLC" to true
if !didCallCTRLC {
t.Error("Expect the simulator to invoke the key handler for CTRLC")
}
// Get the content from the testing screen
actualContent, err := testingScreen.GetViewContent(viewName)
if err != nil {
t.Error(err)
}
// Test that it contains the "Hello World!" we thought the view should draw
if strings.TrimSpace(actualContent) != expectedViewContent {
t.Error(fmt.Printf("Expected view content to be: %q got: %q", expectedViewContent, actualContent))
}
}
Note: Under the covers this is using the
tcell
SimulationScreen
.
Screenshots
Projects using gocui
- omnivore: Distributed SSH commands with output matching.
- komanda-cli: IRC Client For Developers.
- vuls: Agentless vulnerability scanner for Linux/FreeBSD.
- wuzz: Interactive cli tool for HTTP inspection.
- httplab: Interactive web server.
- domainr: Tool that checks the availability of domains based on keywords.
- gotime: Time tracker for projects and tasks.
- claws: Interactive command line client for testing websockets.
- terminews: Terminal based RSS reader.
- diagram: Tool to convert ascii arts into hand drawn diagrams.
- pody: CLI app to manage Pods in a Kubernetes cluster.
- kubexp: Kubernetes client.
- kcli: Tool for inspecting kafka topics/partitions/messages.
- fac: git merge conflict resolver
- jsonui: Interactive JSON explorer for your terminal.
- cointop: Interactive terminal based UI application for tracking cryptocurrencies.
- lazygit: simple terminal UI for git commands.
- lazydocker: The lazier way to manage everything docker.
Note: if your project is not listed here, let us know! :)