Terminal UI library with rich, interactive widgets — written in Golang

Rich Interactive Widgets for Terminal UIs

PkgGoDev Go Report

This Go package provides commonly needed components for terminal based user interfaces.

Screenshot

Among these components are:

  • Input forms (include input/password fields, drop-down selections, checkboxes, and buttons)
  • Navigable multi-color text views
  • Sophisticated navigable table views
  • Flexible tree views
  • Selectable lists
  • Grid, Flexbox and page layouts
  • Modal message windows
  • An application wrapper

They come with lots of customization options and can be easily extended to fit your needs.

Installation

go get github.com/rivo/tview

Hello World

This basic example creates a box titled "Hello, World!" and displays it in your terminal:

package main

import (
	"github.com/rivo/tview"
)

func main() {
	box := tview.NewBox().SetBorder(true).SetTitle("Hello, world!")
	if err := tview.NewApplication().SetRoot(box, true).Run(); err != nil {
		panic(err)
	}
}

Check out the GitHub Wiki for more examples along with screenshots. Or try the examples in the "demos" subdirectory.

For a presentation highlighting this package, compile and run the program found in the "demos/presentation" subdirectory.

Projects using tview

Documentation

Refer to https://pkg.go.dev/github.com/rivo/tview for the package's documentation. Also check out the Wiki.

Dependencies

This package is based on github.com/gdamore/tcell (and its dependencies) as well as on github.com/rivo/uniseg.

Versioning and Backwards-Compatibility

I try really hard to keep this project backwards compatible. Your software should not break when you upgrade tview. But this also means that some of its shortcomings that were present in the initial versions will remain. In addition, at least for the time being, you won't find any version tags in this repo. The newest version should be the one to upgrade to. It has all the bugfixes and latest features. Having said that, backwards compatibility may still break when:

  • a new version of an imported package (most likely tcell) changes in such a way that forces me to make changes in tview as well,
  • I fix something that I consider a bug, rather than a feature, something that does not work as originally intended,
  • I make changes to "internal" interfaces such as Primitive. You shouldn't need these interfaces unless you're writing your own primitives for tview. (Yes, I realize these are public interfaces. This has advantages as well as disadvantages. For the time being, it is what it is.)

Your Feedback

Add your issue here on GitHub. Feel free to get in touch if you have any questions.

Comments
  • virtual Table view #question

    virtual Table view #question

    Does the library support virtual table?

    I'd like to build a simple log viewer with this lib, but the log is quite huge, so I don't think I'd like to load the whole logfile to memory.

  • Feature request: textarea

    Feature request: textarea

    Hi,

    I have just found your package. It is very interessting and I am trying to write an application with it. I would like to know if there is any plan to create a texarea in the near future ?

    Thanks. NT

  • panic: tview.(*Table).Draw.func1

    panic: tview.(*Table).Draw.func1

    I am getting a panic in tview. It looks like it might be in table.go line 469, but it is really hard to troubleshoot because of the way the panic is being printed.

    panic

    (Click image for bigger view)

  • Mouse support

    Mouse support

    Hi, this pull request adds mouse support. Here are some notes on the changes:

    • app.EnableMouse() is needed in order to enable the mouse. Mouse enabled does cause extra traffic and redrawing. Because it's disabled by default, there should be no behavior impact to existing programs.
    • Mouse enabled prevents normal selecting text in most terminals. Workaround is to use the shift key while selecting. It's the developer's decision if they want to impose this on their users.
    • Buttons, checkboxes and list items can be clicked.
    • InputField sets focus on click, it could potentially also allow you to click to a specific character position.
    • DropDown list responds to clicks, and clicking outside closes the list.
    • Clicks on TreeView and Table not implemented yet.
    • Primitives could have a new hover draw state, such as changing the background color on hover. Needs color/theming decision. (I experimented with focusing on hover, it works well but I'm not sure it's a good default)
    • Try the the presentation demo to test the mouse. You can't click to switch slides, that would be nice.

    Relevant issues: #13 #361 #338 #337

  • TextView: limiting the maximum number of lines

    TextView: limiting the maximum number of lines

    Hi,

    I am using TextView to display scrolling log output, but as soon as the text in the buffer becomes very big, CPU usage shoots up.

    I propose solving it by adding a new SetMaxLines() method that makes sure the buffer slice never contains more lines than the set maximum. By default, it is set to infinite, therefore this would be a backward-compatible change.

    Proposed PR: #452

  • Install error - cannot convert b.borderAttributes

    Install error - cannot convert b.borderAttributes

    go get fails with following error

    go get github.com/rivo/tview
    # github.com/rivo/tview
    Workspace/go/src/github.com/rivo/tview/box.go:343:63: cannot convert b.borderAttributes (type tcell.AttrMask) to type tcell.Style
    Workspace/go/src/github.com/rivo/tview/table.go:331:108: cannot convert attributes (type tcell.AttrMask) to type tcell.Style
    Workspace/go/src/github.com/rivo/tview/table.go:888:150: cannot convert cell.Attributes (type tcell.AttrMask) to type tcell.Style
    Workspace/go/src/github.com/rivo/tview/table.go:957:63: cannot convert a (type tcell.AttrMask) to type tcell.Style
    Workspace/go/src/github.com/rivo/tview/table.go:1020:24: invalid operation: t.selectedStyle != 0 (mismatched types tcell.Style and int)
    
  • Feature: popup/docked menu

    Feature: popup/docked menu

    The reason I am asking for popup menu is that the list using as menu taking too much space if laid vertically, it just doesn't look proper as a habit if laid horizontally.

    Two solutions:

    • A
    1. Introduce a modal window, allow this modal window to be used as container, similar like flex. So user can add the list into it.
    2. Allow a triggering key(eg: m for menu) from current focused gui component, eg: page/table/textview etc to trigger the popup modal
    3. In this use case, user trigger 2 keys: m -> f to trigger a function call

    I tried current modal, it seems the way of using it is too much mind twisted. I don't like it for a few reasons:

    1. User need to put it into a page, then hide it, you will have to switch to that page in order to show it. This way, the whole page of modal normally hide the view of your working page. It is not a popup, at least the way using it does not feel intuitive

    2. This modal page is taking a tab page and it is not naturally belongs to the tab pages. If I need one modal window as menu for a tab page for some operations, there would be too many pages used for the modal windows. To avoid displaying the modal window page, when I render the tab number and switch logic, I will have to put special logic for it

    • B

    Not sure if this is good. But if it's doable and easy to implement, then it should be cool

    1. Allow a triggering key(eg: m for menu) from current focused gui component, eg: page/table/textview etc to trigger the popup modal

    2. For a tab page structured as: flex [(hidden list) | table]. The hidden list won't be showing and take any space. Only when the triggering key is pressed, then the list will be showing and take the space, the table component will take the space as normal. After user select the list item to trigger required function, the list will be hidden again.

    Just a thought for your consideration. tview will get better and even cooler :)

  • Forms can grow beyond terminal dimensions

    Forms can grow beyond terminal dimensions

    If you add too many form items to a form, it can vertically grow beyond the terminal's height. It would be nice if one could scroll within a form in such a case.

  • please publish a release

    please publish a release

    It's crucial for some (including me) to freeze their dependencies. I have my way around by running

    go get github.com/rivo/tview@8e06c826b3a513204a61e9a89a52e1226f1a03ac 
    

    still, please publish a release

    https://github.com/rivo/tview/releases

    image

  • Example of popup Modal by shortcut

    Example of popup Modal by shortcut

    https://github.com/rivo/tview/wiki/Modal example list modal hardwired to root or Pages elements. But I already have List as my root element, and I want to show modal (without buttons) when I press i and close it on Esc or clicking outside. Would be nice to have an example that demonstrates how that's possible.

    The straightforward approach I tried that doesn't work.

            // Create UI elements
            list := tview.NewList()
            // Set Box properties on the List component
            list.SetBorder(true).SetTitle(" package updates ")
            // Set List properties
            list.ShowSecondaryText(false)
    
            infobox := tview.NewModal().
                    AddButtons([]string{"Quit", "Cancel"}).
                    SetText("Lorem Ipsum Is A Pain")
    
            // Attach elements and start UI app
            app := tview.NewApplication()
            app.SetRoot(list, true) // SetRoot(root Primitive, fullscreen bool)
            app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
                    if event.Key() == tcell.KeyESC {
                            app.Stop()
                    }
                    // Key is a character
                    if event.Key() == tcell.KeyRune {
                            if event.Rune() == 'q' {
                                    app.Stop()
                            }
                            if event.Rune() == 'i' {
                                    app.SetFocus(infobox)
                            }
                    }
                    return event
            })
    

    After pressing i the List border becomes single line, and it stops responding to events. Nothing else is shown. Pressing Esc doesn't revert to previous state, but exits the application.

  • Handling keys in InputField -> autocomplete list

    Handling keys in InputField -> autocomplete list

    I have an InputField with an autocomplete function...


    I'd like more control over handling key input inside this list. Ive looked at the source but it seems autocomplete list is internal to InputField, and theres no way to access it or add input handlers to it

    1) Handling tab and page up/down keys
    2) When an autocomplete list item is selected (using arrow keys), I do not want to update the TextInput text. This should only happen when I hit Enter

    Maybe I need to write a custom component, or try to handle it from App.SetInputHandler() ?

    Any advice much appreciated, thanks

    package main
    
    import (
    	"strings"
    
    	"github.com/gdamore/tcell/v2"
    	"github.com/rivo/tview"
    )
    
    func UI_test() {
    	app := tview.NewApplication()
    	testlist := []string{
    		"Taxes: Import Tax",
    		"Taxes: Income Tax",
    		"Taxes: National Insurance",
    		"Taxes: Road Tax",
    		"Taxes: VAT Bill",
    		"TaxDummy: foobar",
    		"TaxDummy: lorem ipsum",
    		"TaxFoo: foo",
    		"Random: TaxFoobar",
    	}
    
    	category_search_input := tview.NewInputField().
    		SetLabel("Category: ").
    		SetFieldWidth(30)
    
    	category_search_input.SetDoneFunc(func(key tcell.Key) {
    		if key == tcell.KeyTab {
    			category_search_input.SetText("TABBED")
    		} else if key == tcell.KeyEscape {
    			category_search_input.SetText("ESCAPED")
    		} else if key == tcell.KeyEnter {
    			category_search_input.SetText("ENTER")
    		}
    	})
    
    	category_search_input.SetAutocompleteFunc(func(currentText string) (entries []string) {
    		for i := range testlist {
    			if strings.Contains(strings.ToUpper(testlist[i]), strings.ToUpper(currentText)) {
    				entries = append(entries, testlist[i])
    			}
    		}
    		if len(entries) < 1 {
    			entries = nil
    		}
    		return
    	})
    
    	form := tview.NewForm().
    		AddFormItem(category_search_input).
    		AddInputField("Dummy Input", "", 0, nil, nil)
    
    	if err := app.SetRoot(form, true).SetFocus(form).Run(); err != nil {
    		panic(err)
    	}
    }
    
  • Build Semantic Versioning Releases

    Build Semantic Versioning Releases

    For better use in production it would be helpful if the tview project builds releases according to Semantic Versioning. Something like "v1.2.3".

  • STDIN of tview process skipping some characters

    STDIN of tview process skipping some characters

    I have the following program, which tries to get a shell access into a running kubernetes pod. I am trying to achieve the same functionality but using tview. With below program i am able sync stdout, stdin, stderr of the kubectl process with tview UI.

    But while inputting something (as it is a shell). I have to press the same key multiple times to view it on the UI.

    package main
    
    import (
    	"fmt"
    	"os"
    	"os/exec"
    	"time"
    
    	"github.com/rivo/tview"
    )
    
    func main() {
    	arr := []string{"exec", "-it", "-n", "db", "postgres-dev-6844b6cb77-hqh9r", "--", "bash"}
    	cc := exec.Command("kubectl", arr...)
    
    	app := tview.NewApplication()
    
    	textArea := tview.NewTextView()
    
    	cc.Stdin = os.Stdin
    	cc.Stdout = os.Stdout
    	cc.Stderr = os.Stderr
    
    	if err := cc.Start(); err != nil {
    		fmt.Println("failed to start", err)
    		return
    	}
    
    	go func() {
    		if err := cc.Wait(); err != nil {
    			fmt.Println("failed to wait", err)
    			return
    		}
    	}()
    
    	if err := app.SetRoot(textArea,
    		true).EnableMouse(false).Run(); err != nil {
    		panic(err)
    	}
    }
    

    image

    For example, In this image I had to press l 2 times, s 2 times & enter key 2 times to execute the ls command. And also i am not able to view the cursor of shell.

    What am I doing wrong here?

  • Deadlock when waiting for draw inside goroutines

    Deadlock when waiting for draw inside goroutines

    So, I have a scenario where I need to wait for something to happen to be able to continue the code. Take the code below as an example:

    package main
    
    import (
    	"fmt"
    	"time"
    
    	"github.com/gdamore/tcell/v2"
    	"github.com/rivo/tview"
    )
    
    func update(input1 string) bool {
        time.Sleep(3 * time.Second)
    
        tviewApp.QueueUpdateDraw(func ()  {
            fmt.Println("draw")
        })
    
        counter = 3
    
        if input1 == "true" {
            return true
        } else {
            return false
        }
    }
    
    var input string
    var counter int
    var tviewApp *tview.Application
    
    func main() {
            tviewApp = tview.NewApplication()
    
            counter = 0
    	form := tview.NewForm()
            form.AddInputField("Input: ", "", 15, nil, nil).
    		SetFieldTextColor(tcell.ColorBlack.TrueColor()).
    		AddButton("Save", func() {
                            input = form.GetFormItem(0).(*tview.InputField).GetText()
                    }).
    		AddButton("Process", func() {
    			var done bool = false
    			go func() {
    				for !done {
    					done = update(input)
    				}
    			}()
    			fmt.Println(counter)
    		})
    
    	if err := tviewApp.SetRoot(form, true).SetFocus(form).Run(); err != nil {
    		panic(err)
    	}
    }
    

    With "true" as input, I'd like to get "3" when printing the counter right after executing the goroutine, but it prints "0", because the goroutine didn't finished. In order to make this, the first thing I thought is to create a waiting group to wait for this goroutine. So, I did this:

            AddButton("Process", func() {
    	    var done bool = false
                wg := new(sync.WaitGroup)
                wg.Add(1)
    	    go func() {
    	        for !done {
    		    done = update(input)
    		}
                    wg.Done()
    	    }()
                wg.Wait()
    	    fmt.Println(counter)
    	})
    
    

    But this ends in a deadlock, and I don't know why. How can I achieve this?

    Disclaimer: I've created this code purely to simulate my scenario, I know it's weird.

  • [QUESTION] About automatically coloring specific word patterns in TextView

    [QUESTION] About automatically coloring specific word patterns in TextView

    Hello Rivo,

    I usually use the same method as in here to color specific words. For example, making the word "error" shown in red and the word "success" shown in green.

    I was wondering, is there a way to make it happen without explicitly reformat each instance of these words? Maybe to give an example, when I use a terminal like MobaXterm, it has this functionality of coloring specific words with a unique color automatically.

    If this is can be already achieved, then I would really appreciate you pointing me to the document covering it and I will read more about it.

    Kind regards

  • Unable to

    Unable to "refresh" the form primitive on a page based on state changes

    Hello. New to tview and very thankful for the effort in producing a great TUI - with forms :-)! Sorry for the long post.

    I have a page based tview application. There is a single main menu page with a List primitive, and many other pages that use form primitives. As you may guess, the list items in the menu page switch to the form pages.

    I have one use case I'm struggling with where the editing of a form could require rebuilding the form (e.g. user presses a button and after the action happens the form should change to reflect the results of the action). I can't find a way to do this within the tview framework.

    I should note that as a solution to resetting the form item buffers in the event of a "cancel", I implemented a simple Navigator with Activate() (and Deactivate()) operations. The Activate() is called before the page is switched to. (Perhaps there is a better way to do this?)

    // GotoPage manages the active shell page state
    // the OnDeactivate callback is called for the page becoming inactive
    // the Activate callback is called for the page becoming active
    //
    // if the onShow callback returns an error, an error is displayed and
    // the active page is set to the main menu page
    func (n *NavigatorImpl) GotoPage(name string) {
        page := n.FindShellPage(name)
        if page != nil {
            if n.activePage != nil {
                n.activePage.Deactivate(n)
                n.activePage = nil
            }
            if err := page.Activate(n); err == nil {
                n.ClearStatus()
                n.activePage = page
                 n.pages.SwitchToPage(name)
            } else {
                n.SetStatusError(err.Error())
                n.activePage = n.FindShellPage(MainMenuPage)
                n.pages.SwitchToPage(MainMenuPage)
            }
        }
    }
    

    The implementation of Activate() in the page is to do a form.Clear() and rebuild the form. Below is the code for this.

    func (sp *somePage) Activate(n Navigator) (err error) {
        // sp.data is the true state of the form's model
        // sp.buffer is where the form items get the values
        // sb.buffer always gets reset to the model values before the form is rebuilt and 
        // the page is displayed
        *sp.buffer = *sp.data
    
        sp.createFormFields(n)
        return nil
    }
    
    func (sp *somePage) RefreshPage(n Navigator) {
    	rsp.createFormFields(n)
    }
    
    func (sp *somePage) createFormFields(n Navigator) {
        form := sp.primitive.(*tview.Form)
        form.Clear(true)
       form.AddTextView( ...)
       ...
    }
    

    I have tried several approaches to solve the need to rebuild (redraw) the page's form when the state changes (without switching to another page first) but cannot get this fully working.

    func (n *NavigatorImpl) RefreshPage() {
    	if n.activePage != nil {
    		go func() {
    			n.app.QueueUpdate(func() {
    				n.activePage.RefreshPage(n)
    			})
    		}()
    	}
    }
    

    Setting breakpoints, I see that the RefreshPage() does get called and presumably the form is cleared and rebuilt (so the Update is not deadlocked?), but the form is not redrawn and the keyboard no longer responds.

    So, if you've read this far, thank you. Any help or advice would be greatly appreciated.

    Thank you. Peter

A rich tool for parsing flags and values in pure Golang

A rich tool for parsing flags and values in pure Golang. No additional library is required and you can use everywhere.

Jan 25, 2022
A golang library for building interactive prompts with full support for windows and posix terminals.
A golang library for building interactive prompts with full support for windows and posix terminals.

Survey A library for building interactive prompts on terminals supporting ANSI escape sequences. package main import ( "fmt" "github.com/Alec

Jan 6, 2023
Secure, private and feature-rich CLI password manager
Secure, private and feature-rich CLI password manager

Kure Kure is a free and open-source password manager for the command-line. This project aims to offer the most secure and private way of operating wit

Nov 17, 2022
Stonks is a terminal based stock visualizer and tracker that displays realtime stocks in graph format in a terminal.
Stonks is a terminal based stock visualizer and tracker that displays realtime stocks in graph format in a terminal.

Stonks is a terminal based stock visualizer and tracker. Installation Requirements: golang >= 1.13 Manual Clone the repo Run make && make install Pack

Dec 16, 2022
Abacus is a simple interactive calculator CLI

Abacus is a simple interactive calculator CLI with support for variables, comparison checks, and math functions abacus -

Sep 15, 2022
🔥 [WIP] Interactive Jira Command Line
🔥 [WIP] Interactive Jira Command Line

JiraCLI Interactive Jira CLI ?? This project is still a work in progress ?? This tool mostly focuses on issue search and navigation at the moment. How

Jan 4, 2023
Building powerful interactive prompts in Go, inspired by python-prompt-toolkit.
Building powerful interactive prompts in Go, inspired by python-prompt-toolkit.

go-prompt A library for building powerful interactive prompts inspired by python-prompt-toolkit, making it easier to build cross-platform command line

Jan 3, 2023
Simplistic interactive filtering tool
Simplistic interactive filtering tool

peco Simplistic interactive filtering tool NOTE: If you are viewing this on GitHub, this document refers to the state of peco in whatever current bran

Dec 30, 2022
Interactive prompt for command-line applications
Interactive prompt for command-line applications

promptui Interactive prompt for command-line applications. We built Promptui because we wanted to make it easy and fun to explore cloud services with

Jan 8, 2023
Build an interactive CLI application with Go, Cobra and promptui. Video tutorial available on the Div Rhino YouTube channel.

Build an interactive CLI app with Go, Cobra and promptui Text tutorial: https://divrhino.com/articles/build-interactive-cli-app-with-go-cobra-promptui

Dec 8, 2022
Interactive CLI helper for creating git branches with JIRA Links and some text

bb (better-branch) Interactive CLI helper for creating git branches with JIRA Links and some text Still in development? Yes How it works? This tiny ut

Aug 18, 2022
Interactive cli tool for HTTP inspection
Interactive cli tool for HTTP inspection

Wuzz command line arguments are similar to cURL's arguments, so it can be used to inspect/modify requests copied from the browser's network inspector with the "copy as cURL" feature.

Dec 27, 2022
Interactive prompt to set and push a semver tag
Interactive prompt to set and push a semver tag

cutver For when you know what version to tag, and you want to cut a release in the annotated tag format. Installation go install github.com/roryq/cutv

Nov 15, 2022
An interactive command-line tool to manage your environments
An interactive command-line tool to manage your environments

goto An interactive command-line tool to manage your environments Overview You always need to login to some Linux machine or connect to a MySQL instan

Jul 11, 2022
This tool is a CLI-interactive tool for TA who use eeclass platform

NTHU eeclass TA helper. This tool is a CLI-interactive tool for TA who use eeclass platform. It helps TA to download all the submitted homework, and use CSV to record the score and comment, and upload CSV score directly to the eeclass platform with just 2 Enter key!

Dec 11, 2021
🧨 Interactive Process Killer CLI made with Go!
🧨 Interactive Process Killer CLI made with Go!

proc-manager is an interactive CLI to kill processes made with Go, currently supports Linux, Mac OS X, Solaris, and Windows.

Dec 2, 2022
Generate an interactive, autocompleting shell for any Cobra CLI
Generate an interactive, autocompleting shell for any Cobra CLI

cobra-shell Description Leverages the Cobra completion API to generate an interactive shell for any Cobra CLI, powered by go-prompt. On-the-fly autoco

Dec 19, 2022
The missing git branch --interactive

git branch-i I got cross that there's no git branch --interactive, so I made this. It's a very (very) simple curses-mode git branch/git checkout alter

Nov 2, 2022
An interactive shell for go application

goshell An interactive shell for go application in normal mode ctrl-c break exec

Dec 15, 2022