Make a simple draft of filter

It reads the input and render it via /dev/tty and let the user
select the lines that are important and finaly write them back
to stdout.
This commit is contained in:
Sameer Rahmani 2022-11-23 21:09:40 +00:00
parent 690167e9fa
commit cbf2c76a29
2 changed files with 215 additions and 0 deletions

162
filter.go Normal file
View File

@ -0,0 +1,162 @@
package main
import (
"fmt"
"log"
"github.com/gdamore/tcell/v2"
)
var xmax int
var ymax int
var highlight tcell.Style
func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
row := y1
col := x1
for _, r := range []rune(text) {
s.SetContent(col, row, r, nil, style)
col++
if col >= x2 {
row++
col = x1
}
if row > y2 {
break
}
}
}
func Render(s tcell.Screen, inputs Items, lowerBound int, upperBound int, current int) {
y := 1
status := fmt.Sprintf("Current: %d | lower: %d | upper: %d | max: %d", current, lowerBound, upperBound, ymax)
drawText(s, 0, 0, xmax-1, 1, tcell.StyleDefault, status)
for i, item := range (*inputs)[lowerBound:upperBound] {
style := tcell.StyleDefault
var txt string
if current == lowerBound+i {
style = highlight
}
if item.Selected {
txt = fmt.Sprintf("XXXX | %s", item.Text)
} else {
txt = fmt.Sprintf("%04d | %s", lowerBound+i, item.Text)
}
drawText(s, 0, y, xmax-1, y+1, style, txt)
y += 1
}
}
func main() {
input, err := ReadFromStdin()
if err != nil {
log.Fatalf("%+v", err)
}
defStyle := tcell.StyleDefault.Background(tcell.ColorDefault).Foreground(tcell.ColorDefault)
highlight = tcell.StyleDefault.Background(tcell.ColorDarkGray).Foreground(tcell.ColorWhite)
currentLine := 0
// Initialize screen
tty, err := tcell.NewDevTty()
if err != nil {
log.Fatalf("%+v", err)
}
s, err := tcell.NewTerminfoScreenFromTty(tty)
if err != nil {
log.Fatalf("%+v", err)
}
if err := s.Init(); err != nil {
log.Fatalf("%+v", err)
}
s.SetStyle(defStyle)
s.Clear()
xmax, ymax = s.Size()
quit := func() {
// You have to catch panics in a defer, clean up, and
// re-raise them - otherwise your application can
// die without leaving any diagnostic trace.
maybePanic := recover()
s.Fini()
if maybePanic != nil {
panic(maybePanic)
}
}
defer quit()
// Here's an example of how to inject a keystroke where it will
// be picked up by the next PollEvent call. Note that the
// queue is LIFO, it has a limited length, and PostEvent() can
// return an error.
// s.PostEvent(tcell.NewEventKey(tcell.KeyRune, rune('a'), 0))
done := false
lowerBound := 0
upperBound := min(ymax-3, len(*input))
// Event loop
for {
if done {
break
}
s.Clear()
//window := (*input)[lowerBound:upperBound]
Render(s, input, lowerBound, upperBound, currentLine)
// Update screen
s.Show()
// Poll event
ev := s.PollEvent()
// Process event
switch ev := ev.(type) {
case *tcell.EventResize:
s.Sync()
case *tcell.EventKey:
if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC || ev.Rune() == 'q' {
return
} else if ev.Key() == tcell.KeyEnter {
done = true
break
} else if ev.Key() == tcell.KeyCtrlL {
s.Sync()
} else if ev.Rune() == 'C' || ev.Rune() == 'c' {
s.Clear()
} else if ev.Rune() == ' ' {
item := (*input)[currentLine]
item.Selected = !item.Selected
} else if ev.Key() == tcell.KeyUp || ev.Rune() == 'r' {
if currentLine > 0 {
currentLine -= 1
}
if currentLine <= lowerBound && currentLine != 0 {
lowerBound -= 1
upperBound -= 1
}
} else if ev.Key() == tcell.KeyDown || ev.Rune() == 'n' {
if currentLine < len(*input)-1 {
currentLine += 1
}
if currentLine >= upperBound && currentLine != len(*input)-1 {
lowerBound += 1
upperBound += 1
}
}
}
}
s.Fini()
for _, item := range *input {
if item.Selected {
fmt.Println(item.Text)
}
}
}

53
utils.go Normal file
View File

@ -0,0 +1,53 @@
package main
import (
"bufio"
"fmt"
"io"
"os"
)
type Item struct {
Text string
Selected bool
}
type Items = *[]*Item
func NewItem(text string) *Item {
return &Item{
Text: text,
Selected: false,
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func ReadFromStdin() (Items, error) {
info, err := os.Stdin.Stat()
if err != nil {
panic(err)
}
if info.Mode()&os.ModeCharDevice != 0 {
return nil, fmt.Errorf("The command is intended to work with pipes. For example `git log | filter | xarg | ...`")
}
reader := bufio.NewReader(os.Stdin)
var output []*Item
for {
input, _, err := reader.ReadLine()
if err != nil && err == io.EOF {
break
}
item := NewItem(string(input))
output = append(output, item)
}
return &output, nil
}