-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
1,671 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Package ui provides declarative console based user interface output. | ||
// | ||
// Operation | ||
// | ||
// The target renderer is expected to render the entire desired UI every frame. | ||
// Every consecutive render diffs this string and only the necessary updates | ||
// are flushed to the output. | ||
// | ||
// Terminal size | ||
// | ||
// Every render is passed a current frame, which includes the terminal size. In | ||
// case the terminal is resized, a new render is triggered with the new size, | ||
// based on the output of the previous render. | ||
package ui |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package ui | ||
|
||
import "strings" | ||
|
||
// Rows joins all non-empty rows with new line. | ||
func Rows(rows ...string) string { | ||
rows = excludeEmpty(rows...) | ||
return strings.Join(rows, "\n") | ||
} | ||
|
||
// Cols joins all non-empty columns with a space. | ||
func Cols(cols ...string) string { | ||
cols = excludeEmpty(cols...) | ||
return strings.Join(cols, " ") | ||
} | ||
|
||
func excludeEmpty(values ...string) []string { | ||
out := make([]string, 0, len(values)) | ||
for _, v := range values { | ||
if len(v) > 0 { | ||
out = append(out, v) | ||
} | ||
} | ||
return out | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package ui_test | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/func/func/ui" | ||
) | ||
|
||
func ExampleRows() { | ||
rows := ui.Rows("foo", "bar", "baz") | ||
fmt.Println(rows) | ||
// Output: | ||
// foo | ||
// bar | ||
// baz | ||
} | ||
|
||
func ExampleRows_skipEmpty() { | ||
rows := ui.Rows("foo", "", "baz") | ||
fmt.Println(rows) | ||
// Output: | ||
// foo | ||
// baz | ||
} | ||
|
||
func ExampleCols() { | ||
cols := ui.Cols("foo", "bar", "baz") | ||
fmt.Println(cols) | ||
// Output: foo bar baz | ||
} | ||
|
||
func ExampleCols_skipEmpty() { | ||
cols := ui.Cols("foo", "", "baz") | ||
fmt.Println(cols) | ||
// Output: foo baz | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package ui | ||
|
||
import ( | ||
"bytes" | ||
"sync" | ||
) | ||
|
||
// A Stack maintains a list of child nodes. | ||
// | ||
// All methods are safe for concurrent access. | ||
type Stack struct { | ||
mu sync.Mutex | ||
nodes []Renderer | ||
buf bytes.Buffer | ||
} | ||
|
||
// Render renders all children in the stack separated by new lines. | ||
// | ||
// Calling Render() on a nil stack returns an empty string. | ||
func (s *Stack) Render(f Frame) string { | ||
if s == nil { | ||
return "" | ||
} | ||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
s.buf.Reset() | ||
for i, c := range s.nodes { | ||
line := c.Render(f) | ||
s.buf.WriteString(line) | ||
if i < len(s.nodes)-1 { | ||
s.buf.WriteByte('\n') | ||
} | ||
} | ||
return s.buf.String() | ||
} | ||
|
||
// Len returns the number of children in the stack. | ||
func (s *Stack) Len() int { | ||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
return len(s.nodes) | ||
} | ||
|
||
// Push adds a new node to the end of the stack. | ||
func (s *Stack) Push(node Renderer) { | ||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
s.nodes = append(s.nodes, node) | ||
} | ||
|
||
// Insert inserts a node at the given index. | ||
// Panics if the given index does not exist in the stack. | ||
func (s *Stack) Insert(node Renderer, index int) { | ||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
if index >= len(s.nodes) { | ||
panic("Out of bounds") | ||
} | ||
s.nodes = append(s.nodes, nil) | ||
copy(s.nodes[index+1:], s.nodes[index:]) | ||
s.nodes[index] = node | ||
} | ||
|
||
// Remove removes a node from the stack. | ||
// No-op if the child does not exist. | ||
func (s *Stack) Remove(node Renderer) { | ||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
for i, n := range s.nodes { | ||
if n == node { | ||
s.nodes = append(s.nodes[:i], s.nodes[i+1:]...) | ||
return | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package ui | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
"testing" | ||
) | ||
|
||
func TestStack_nil(t *testing.T) { | ||
var s *Stack | ||
got := s.Render(Frame{}) // Does not panic | ||
if got != "" { | ||
t.Errorf("(*Stack)(nil).Render() returned %q, want \"\"", got) | ||
} | ||
} | ||
|
||
func TestStack_Render(t *testing.T) { | ||
s := &Stack{} | ||
|
||
s.Push(renderFunc(func(f Frame) string { | ||
return fmt.Sprintf("<%d>", f.Number) | ||
})) | ||
|
||
got := s.Render(Frame{Number: 123}) | ||
want := "<123>" | ||
if got != want { | ||
t.Errorf("Rendered output does not match; got %q, want %q", got, want) | ||
} | ||
} | ||
|
||
func TestStack_io(t *testing.T) { | ||
s := &Stack{} | ||
|
||
head := testNode("HEAD") | ||
s.Push(head) | ||
tail := testNode("TAIL") | ||
s.Push(tail) | ||
for i := 0; i < 3; i++ { | ||
s.Insert(testNode('A'+i), 1) | ||
} | ||
s.Remove(head) | ||
|
||
got := s.Render(Frame{}) | ||
want := "C\nB\nA\nTAIL" | ||
if got != want { | ||
t.Errorf("Rendered output does not match; got %q, want %q", got, want) | ||
} | ||
} | ||
|
||
func TestStack_concurrent(t *testing.T) { | ||
s := &Stack{} | ||
|
||
var wg sync.WaitGroup | ||
for i := 0; i < 10000; i++ { | ||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
s.Push(testNode("")) | ||
}() | ||
} | ||
for i := 0; i < 10000; i++ { | ||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
s.Insert(testNode(""), 0) | ||
}() | ||
} | ||
wg.Wait() | ||
|
||
if s.Len() != 20000 { | ||
t.Errorf("Len() does not match; got %d, want %d", s.Len(), 20000) | ||
} | ||
} | ||
|
||
func TestStack_Insert_OutOfBounds(t *testing.T) { | ||
defer func() { | ||
p := recover() | ||
if p == nil { | ||
t.Error("Did not panic") | ||
} | ||
}() | ||
|
||
s := &Stack{} | ||
s.Insert(testNode(""), 123) | ||
} | ||
|
||
type testNode string | ||
|
||
func (n testNode) Render(f Frame) string { return string(n) } | ||
|
||
type renderFunc func(f Frame) string | ||
|
||
func (fn renderFunc) Render(f Frame) string { | ||
return fn(f) | ||
} |
Oops, something went wrong.