Compare commits
11 Commits
Author | SHA1 | Date |
---|---|---|
Sameer Rahmani | 2b1164e0fa | |
Sameer Rahmani | 3f94d0fee8 | |
Sameer Rahmani | fe2ec158be | |
Sameer Rahmani | c0b97850ae | |
Sameer Rahmani | 275a42440a | |
Sameer Rahmani | a9d729e894 | |
Sameer Rahmani | 3981cdfd45 | |
Sameer Rahmani | 459ab8d739 | |
Sameer Rahmani | ce8f6e0e82 | |
Sameer Rahmani | 49465bb6f1 | |
Sameer Rahmani | 944c43b396 |
246
filter.go
246
filter.go
|
@ -18,6 +18,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
)
|
||||
|
@ -33,16 +34,49 @@ var ymax int
|
|||
|
||||
// TODO: Make the highlight style configuratble.
|
||||
// The style to be used with the current line
|
||||
var highlight tcell.Style
|
||||
var selectedStyle tcell.Style
|
||||
var helpStyle tcell.Style
|
||||
var highlight,
|
||||
selectedStyle,
|
||||
helpStyle,
|
||||
selectionStyle,
|
||||
selectionArrow,
|
||||
cursorStyle,
|
||||
normalArrow tcell.Style
|
||||
|
||||
var help [][]string = [][]string{
|
||||
{"?", "View help"},
|
||||
{"TAB", "Select an entry"},
|
||||
{"C-p, Up", "Move up"},
|
||||
{"C-n, Down", "Move down"},
|
||||
{"C-space", "Start/Stop muliple-selection"},
|
||||
{"C-e, End", "Jump to the end of input"},
|
||||
{"C-e, End", "Jump to the end of input"},
|
||||
{"q", "Back"},
|
||||
{"ESC", "Exit"},
|
||||
}
|
||||
|
||||
type Scene struct {
|
||||
RenderFn func(*State, Items)
|
||||
KeyHandlerFn func(*State, tcell.EventKey, Items)
|
||||
}
|
||||
|
||||
type State struct {
|
||||
Current int
|
||||
InSelectionMode bool
|
||||
SelectionStartLine int
|
||||
UpperBound int
|
||||
LowerBound int
|
||||
Screen *tcell.Screen
|
||||
Scenes []*Scene
|
||||
CurrentScene int
|
||||
UserInput string
|
||||
}
|
||||
|
||||
// Draws the given `text` to the screen `s` at the given coordinates. Remember that
|
||||
// the text will be wrapped if it overflow.
|
||||
func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
|
||||
row := y1
|
||||
col := x1
|
||||
for _, r := range []rune(text) {
|
||||
for _, r := range text {
|
||||
s.SetContent(col, row, r, nil, style)
|
||||
col++
|
||||
if col >= x2 {
|
||||
|
@ -56,28 +90,148 @@ func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string
|
|||
}
|
||||
|
||||
// Main render function that gets called during the event loop.
|
||||
func Render(s tcell.Screen, inputs Items, lowerBound int, upperBound int, current int) {
|
||||
func Render(s *State, inputs Items) {
|
||||
scene := s.Scenes[s.CurrentScene]
|
||||
scene.RenderFn(s, inputs)
|
||||
}
|
||||
|
||||
func RenderHelp(s *State, inputs Items) {
|
||||
y := 2
|
||||
status := fmt.Sprintf("Navigation: [UP or C-p] [Down or C-n] | Select: SPACE | Cancel: [q ESC] | Done: [Enter] | Line: %d", current)
|
||||
drawText(s, 0, 0, xmax-1, 1, helpStyle, status)
|
||||
for i, item := range (*inputs)[lowerBound:upperBound] {
|
||||
for _, key := range help {
|
||||
drawText(*s.Screen, 4, y, 25, y+1, tcell.StyleDefault.Bold(true), key[0])
|
||||
drawText(*s.Screen, 25, y, xmax-1, y+1, tcell.StyleDefault.Bold(true), key[1])
|
||||
y += 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func RenderMain(s *State, inputs Items) {
|
||||
y := 2
|
||||
status := fmt.Sprintf("[Line: %d][? For help]", s.Current+1)
|
||||
//status := fmt.Sprintf("[Line: %d][? For help]: %s", s.Current+1, s.UserInput)
|
||||
//status := fmt.Sprintf("L: %d, U: %d, S: %d [Line: %d][? For help]: %s", s.LowerBound, s.UpperBound, s.SelectionStartLine, s.Current+1, s.UserInput)
|
||||
drawText(*s.Screen, 0, 0, xmax-1, 1, helpStyle, status)
|
||||
//drawText(*s.Screen, len(status), 0, xmax-1, 1, cursorStyle, "⎥")
|
||||
|
||||
for i, item := range (*inputs)[s.LowerBound:s.UpperBound] {
|
||||
style := tcell.StyleDefault
|
||||
arrowStyle := normalArrow
|
||||
index := s.LowerBound + i
|
||||
|
||||
var txt string
|
||||
|
||||
if item.Selected {
|
||||
txt = fmt.Sprintf(" X %s", item.Text)
|
||||
txt = fmt.Sprintf(" X %s", item.Text)
|
||||
style = selectedStyle
|
||||
} else {
|
||||
txt = fmt.Sprintf(" %s", item.Text)
|
||||
txt = fmt.Sprintf(" %s", item.Text)
|
||||
}
|
||||
|
||||
if current == lowerBound+i {
|
||||
if s.Current == s.LowerBound+i {
|
||||
style = highlight
|
||||
}
|
||||
|
||||
drawText(s, 0, y, xmax-1, y+1, style, txt)
|
||||
if s.InSelectionMode {
|
||||
if (s.SelectionStartLine < s.LowerBound && index <= s.Current) || (s.SelectionStartLine > s.UpperBound && index >= s.Current) {
|
||||
style = selectionStyle
|
||||
arrowStyle = selectionArrow
|
||||
} else {
|
||||
low := min(s.SelectionStartLine, s.Current)
|
||||
high := max(s.SelectionStartLine, s.Current)
|
||||
|
||||
if index >= low && index <= high {
|
||||
style = selectionStyle
|
||||
arrowStyle = selectionArrow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawText(*s.Screen, 0, y, len(txt), y+1, style, txt)
|
||||
// Clean up the rest of the line that might've been filled with a previous render.
|
||||
// This is a hack to NOT use `Clear` on each loop
|
||||
drawText(*s.Screen, len(txt), y, xmax-1, y+1, style, strings.Repeat(" ", xmax-len(txt)))
|
||||
|
||||
if s.Current == s.LowerBound+i {
|
||||
drawText(*s.Screen, 0, y, 1, y+1, arrowStyle, ">")
|
||||
}
|
||||
|
||||
y += 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func HandleKeyEvents(s *State, ev tcell.EventKey, inputs Items) {
|
||||
scene := s.Scenes[s.CurrentScene]
|
||||
scene.KeyHandlerFn(s, ev, inputs)
|
||||
}
|
||||
|
||||
func HandleMain(state *State, ev tcell.EventKey, input Items) {
|
||||
if ev.Key() == tcell.KeyTAB {
|
||||
// Mark the current line as selected
|
||||
if state.InSelectionMode {
|
||||
start := min(state.SelectionStartLine, state.Current)
|
||||
end := max(state.SelectionStartLine, state.Current)
|
||||
toggleItems(input, start, end+1)
|
||||
state.SelectionStartLine = -1
|
||||
state.InSelectionMode = false
|
||||
} else {
|
||||
item := (*input)[state.Current]
|
||||
item.Selected = !item.Selected
|
||||
}
|
||||
|
||||
} else if ev.Key() == tcell.KeyUp || ev.Key() == tcell.KeyCtrlP {
|
||||
if state.Current > 0 {
|
||||
state.Current -= 1
|
||||
}
|
||||
if state.Current <= state.LowerBound && state.Current != 0 {
|
||||
state.LowerBound -= 1
|
||||
state.UpperBound -= 1
|
||||
}
|
||||
|
||||
} else if ev.Key() == tcell.KeyDown || ev.Key() == tcell.KeyCtrlN {
|
||||
if state.Current < len(*input)-1 {
|
||||
state.Current += 1
|
||||
}
|
||||
|
||||
if state.Current >= state.UpperBound && state.Current != len(*input)-1 {
|
||||
state.LowerBound += 1
|
||||
state.UpperBound += 1
|
||||
}
|
||||
} else if ev.Key() == tcell.KeyCtrlE || ev.Key() == tcell.KeyEnd {
|
||||
state.UpperBound = len(*input)
|
||||
state.LowerBound = state.UpperBound - ymax + 2
|
||||
state.Current = len(*input) - 1
|
||||
|
||||
} else if ev.Key() == tcell.KeyCtrlA || ev.Key() == tcell.KeyHome {
|
||||
state.UpperBound = ymax - 2
|
||||
state.LowerBound = 0
|
||||
state.Current = 0
|
||||
|
||||
} else if ev.Key() == tcell.KeyCtrlSpace {
|
||||
if state.InSelectionMode {
|
||||
state.SelectionStartLine = -1
|
||||
} else {
|
||||
state.SelectionStartLine = state.Current
|
||||
}
|
||||
|
||||
state.InSelectionMode = !state.InSelectionMode
|
||||
|
||||
} else if ev.Rune() == '?' {
|
||||
state.CurrentScene = 1
|
||||
} else if ev.Key() == tcell.KeyBackspace || ev.Key() == tcell.KeyBackspace2 {
|
||||
if len(state.UserInput) != 0 {
|
||||
state.UserInput = string(state.UserInput[:len(state.UserInput)-1])
|
||||
}
|
||||
|
||||
} else {
|
||||
state.UserInput = state.UserInput + string(ev.Rune())
|
||||
}
|
||||
}
|
||||
|
||||
func HandleHelp(state *State, ev tcell.EventKey, input Items) {
|
||||
if ev.Rune() == 'q' {
|
||||
state.CurrentScene = 0
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -88,11 +242,14 @@ func main() {
|
|||
|
||||
// TODO: Make the default style configurable
|
||||
defStyle := tcell.StyleDefault.Background(tcell.ColorDefault).Foreground(tcell.ColorDefault)
|
||||
highlight = tcell.StyleDefault.Background(tcell.ColorDarkGray).Foreground(tcell.ColorWhite)
|
||||
selectedStyle = tcell.StyleDefault.Foreground(tcell.ColorYellow)
|
||||
highlight = tcell.StyleDefault.Background(tcell.ColorDefault).Foreground(tcell.ColorDefault).Bold(true)
|
||||
selectedStyle = tcell.StyleDefault.Foreground(tcell.ColorPlum)
|
||||
helpStyle = tcell.StyleDefault.Foreground(tcell.ColorDefault)
|
||||
selectionStyle = tcell.StyleDefault.Background(tcell.ColorRebeccaPurple).Foreground(tcell.ColorDefault)
|
||||
selectionArrow = tcell.StyleDefault.Background(tcell.ColorRebeccaPurple).Foreground(tcell.ColorGreen).Bold(true)
|
||||
normalArrow = tcell.StyleDefault.Foreground(tcell.ColorGreen).Bold(true)
|
||||
cursorStyle = normalArrow.Blink(true)
|
||||
|
||||
currentLine := 0
|
||||
done := false
|
||||
|
||||
// Initialize screen
|
||||
|
@ -124,14 +281,37 @@ func main() {
|
|||
}
|
||||
defer quit()
|
||||
|
||||
lowerBound := 0
|
||||
upperBound := min(ymax-3, len(*input))
|
||||
mainScene := Scene{
|
||||
RenderMain,
|
||||
HandleMain,
|
||||
}
|
||||
helpScene := Scene{
|
||||
RenderHelp,
|
||||
HandleHelp,
|
||||
}
|
||||
|
||||
state := State{
|
||||
Current: 0,
|
||||
SelectionStartLine: 0,
|
||||
Screen: &s,
|
||||
LowerBound: 0,
|
||||
UpperBound: min(ymax-3, len(*input)),
|
||||
InSelectionMode: false,
|
||||
CurrentScene: 0,
|
||||
Scenes: []*Scene{&mainScene, &helpScene},
|
||||
}
|
||||
|
||||
previousScene := 0
|
||||
|
||||
// Event loop
|
||||
for !done {
|
||||
//s.Clear()
|
||||
// If we need to render a new scene let's clear the screen first
|
||||
if state.CurrentScene != previousScene {
|
||||
s := *state.Screen
|
||||
s.Clear()
|
||||
}
|
||||
|
||||
Render(s, input, lowerBound, upperBound, currentLine)
|
||||
Render(&state, input)
|
||||
|
||||
// Update screen
|
||||
s.Show()
|
||||
|
@ -144,36 +324,16 @@ func main() {
|
|||
case *tcell.EventResize:
|
||||
s.Sync()
|
||||
case *tcell.EventKey:
|
||||
if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC || ev.Rune() == 'q' {
|
||||
if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC {
|
||||
return
|
||||
} else if ev.Key() == tcell.KeyEnter {
|
||||
done = true
|
||||
break
|
||||
} else if ev.Key() == tcell.KeyCtrlL {
|
||||
s.Sync()
|
||||
} else if ev.Rune() == ' ' {
|
||||
// Mark the current line as selected
|
||||
item := (*input)[currentLine]
|
||||
item.Selected = !item.Selected
|
||||
|
||||
} else if ev.Key() == tcell.KeyUp || ev.Key() == tcell.KeyCtrlP {
|
||||
if currentLine > 0 {
|
||||
currentLine -= 1
|
||||
}
|
||||
if currentLine <= lowerBound && currentLine != 0 {
|
||||
lowerBound -= 1
|
||||
upperBound -= 1
|
||||
}
|
||||
|
||||
} else if ev.Key() == tcell.KeyDown || ev.Key() == tcell.KeyCtrlN {
|
||||
if currentLine < len(*input)-1 {
|
||||
currentLine += 1
|
||||
}
|
||||
|
||||
if currentLine >= upperBound && currentLine != len(*input)-1 {
|
||||
lowerBound += 1
|
||||
upperBound += 1
|
||||
}
|
||||
} else {
|
||||
// In case of no global binding hand the even to the scene
|
||||
HandleKeyEvents(&state, *ev, input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
13
utils.go
13
utils.go
|
@ -36,6 +36,12 @@ func NewItem(text string) *Item {
|
|||
}
|
||||
}
|
||||
|
||||
func toggleItems(items Items, start int, end int) {
|
||||
for _, item := range (*items)[start:end] {
|
||||
item.Selected = !item.Selected
|
||||
}
|
||||
}
|
||||
|
||||
// Pffff Golang, right? :D
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
|
@ -44,6 +50,13 @@ func min(a, b int) int {
|
|||
return b
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Reads the input from stdin and makes sure that the current
|
||||
// process is being run in a pipeline and converts the data
|
||||
// to an array of items.
|
||||
|
|
Loading…
Reference in New Issue