[GH-ISSUE #632] feature request: collapsable list or list-like tree view #460

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

Originally created by @SamWhited on GitHub (Aug 3, 2021).
Original GitHub issue: https://github.com/rivo/tview/issues/632

Several times I have had lists where I wanted to group the items in the list under a collapsible heading. For example, in a contact list I might have "groups" that I put my subscriptions into, the groups could then be collapsed or expanded to make the overall view of the list shorter or longer.

I have done this with a custom list widget in the past that adds a concept of "headers", but it could also be a change to the tree view (since it already supports collapsing at multiple levels) and it could just render itself as if it were a list.

Originally created by @SamWhited on GitHub (Aug 3, 2021). Original GitHub issue: https://github.com/rivo/tview/issues/632 Several times I have had lists where I wanted to group the items in the list under a collapsible heading. For example, in a contact list I might have "groups" that I put my subscriptions into, the groups could then be collapsed or expanded to make the overall view of the list shorter or longer. I have done this with a custom list widget in the past that adds a concept of "headers", but it could also be a change to the tree view (since it already supports collapsing at multiple levels) and it could just render itself as if it were a list.
kerem closed this issue 2026-03-04 01:05:12 +03:00
Author
Owner

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

That's exactly what TreeView is for. You can use SetPrefixes() to make it look more like a list:

image

<!-- gh-comment-id:927792488 --> @rivo commented on GitHub (Sep 27, 2021): That's exactly what [`TreeView`](https://pkg.go.dev/github.com/rivo/tview#TreeView) is for. You can use [`SetPrefixes()`](https://pkg.go.dev/github.com/rivo/tview#TreeView.SetPrefixes) to make it look more like a list: ![image](https://user-images.githubusercontent.com/480930/134901455-fa060682-0fef-4f60-86d1-ed09df47d049.png)
Author
Owner

@SamWhited commented on GitHub (Sep 28, 2021):

TreeView doesn't really work the same way though, as far as I can tell. No primary/secondary text, for example, and no easy way to move to the "next" thing in the tree that I see (eg. no way to get an index and nodes aren't linked horizontally).

<!-- gh-comment-id:928543295 --> @SamWhited commented on GitHub (Sep 28, 2021): TreeView doesn't really work the same way though, as far as I can tell. No primary/secondary text, for example, and no easy way to move to the "next" thing in the tree that I see (eg. no way to get an index and nodes aren't linked horizontally).
Author
Owner

@rivo commented on GitHub (Feb 15, 2022):

True, there is no primary/secondary text. Moving up or down will shift the focus to the "next" thing in the tree. But you're right, there is no function (yet?) that gives you an index for the next item. There was some discussion about this is #432.

<!-- gh-comment-id:1040337250 --> @rivo commented on GitHub (Feb 15, 2022): True, there is no primary/secondary text. Moving up or down will shift the focus to the "next" thing in the tree. But you're right, there is no function (yet?) that gives you an index for the next item. There was some discussion about this is #432.
Author
Owner

@rivo commented on GitHub (Dec 17, 2022):

Coming back to this, there is now a function TreeView.Move() which lets you move the selection up/down. Also, secondary text can be implemented using a combination of prefixes and the "selectable" flag. I've modified. Here's an example, based on the file browser demo in the package:

// Demo code for the TreeView primitive.
package main

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"time"

	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
)

// Show a navigable tree view of the current directory.
func main() {
	app := tview.NewApplication()

	// Set up the tree view.
	rootDir := "."
	root := tview.NewTreeNode(rootDir).
		SetColor(tcell.ColorRed)
	tree := tview.NewTreeView().
		SetRoot(root).
		SetCurrentNode(root).
		SetGraphics(false).
		SetPrefixes([]string{""})

	// A helper function which adds the files and directories of the given path
	// to the given target node.
	add := func(target *tview.TreeNode, path string) {
		files, err := ioutil.ReadDir(path)
		if err != nil {
			panic(err)
		}
		for _, file := range files {
			node := tview.NewTreeNode("- " + file.Name()).
				SetReference(filepath.Join(path, file.Name())).
				SetSelectable(file.IsDir())
			if file.IsDir() {
				node.SetColor(tcell.ColorGreen)
			}
			target.AddChild(node)

			// For files, we add the file size, too.
			if !file.IsDir() {
				size := tview.NewTreeNode(fmt.Sprintf("  %d kB", file.Size()/1024)).
					SetSelectable(false).
					SetColor(tcell.ColorBlue)
				target.AddChild(size)
			}
		}
	}

	// Add the current directory to the root node.
	add(root, rootDir)

	// Deactivate all input handling.
	tree.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		return nil
	})
	tree.SetMouseCapture(func(action tview.MouseAction, event *tcell.EventMouse) (tview.MouseAction, *tcell.EventMouse) {
		return 0, nil
	})

	// If a directory was selected, open it.
	tree.SetChangedFunc(func(node *tview.TreeNode) {
		reference := node.GetReference()
		if reference == nil {
			return // Selecting the root node does nothing.
		}
		children := node.GetChildren()
		if len(children) == 0 {
			// Load and show files in this directory.
			path := reference.(string)
			add(node, path)
		} else {
			// Collapse if visible, expand if collapsed.
			node.SetExpanded(!node.IsExpanded())
		}
	})

	// Automatically select the next node once a second.
	go func() {
		for {
			time.Sleep(time.Second)
			app.QueueUpdateDraw(func() {
				tree.Move(1)
			})
		}
	}()

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

I will close this issue soon, then.

<!-- gh-comment-id:1356273230 --> @rivo commented on GitHub (Dec 17, 2022): Coming back to this, there is now a function `TreeView.Move()` which lets you move the selection up/down. Also, secondary text can be implemented using a combination of prefixes and the "selectable" flag. I've modified. Here's an example, based on the file browser demo in the package: ```go // Demo code for the TreeView primitive. package main import ( "fmt" "io/ioutil" "path/filepath" "time" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) // Show a navigable tree view of the current directory. func main() { app := tview.NewApplication() // Set up the tree view. rootDir := "." root := tview.NewTreeNode(rootDir). SetColor(tcell.ColorRed) tree := tview.NewTreeView(). SetRoot(root). SetCurrentNode(root). SetGraphics(false). SetPrefixes([]string{""}) // A helper function which adds the files and directories of the given path // to the given target node. add := func(target *tview.TreeNode, path string) { files, err := ioutil.ReadDir(path) if err != nil { panic(err) } for _, file := range files { node := tview.NewTreeNode("- " + file.Name()). SetReference(filepath.Join(path, file.Name())). SetSelectable(file.IsDir()) if file.IsDir() { node.SetColor(tcell.ColorGreen) } target.AddChild(node) // For files, we add the file size, too. if !file.IsDir() { size := tview.NewTreeNode(fmt.Sprintf(" %d kB", file.Size()/1024)). SetSelectable(false). SetColor(tcell.ColorBlue) target.AddChild(size) } } } // Add the current directory to the root node. add(root, rootDir) // Deactivate all input handling. tree.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { return nil }) tree.SetMouseCapture(func(action tview.MouseAction, event *tcell.EventMouse) (tview.MouseAction, *tcell.EventMouse) { return 0, nil }) // If a directory was selected, open it. tree.SetChangedFunc(func(node *tview.TreeNode) { reference := node.GetReference() if reference == nil { return // Selecting the root node does nothing. } children := node.GetChildren() if len(children) == 0 { // Load and show files in this directory. path := reference.(string) add(node, path) } else { // Collapse if visible, expand if collapsed. node.SetExpanded(!node.IsExpanded()) } }) // Automatically select the next node once a second. go func() { for { time.Sleep(time.Second) app.QueueUpdateDraw(func() { tree.Move(1) }) } }() if err := app.SetRoot(tree, true).EnableMouse(true).Run(); err != nil { panic(err) } } ``` I will close this issue soon, then.
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#460
No description provided.