From c0b97850ae2ca3db1b6e59f01c301e0d6ceb2fc7 Mon Sep 17 00:00:00 2001 From: Sameer Rahmani Date: Fri, 25 Nov 2022 16:40:02 +0000 Subject: [PATCH] Fix the scrolling issue --- filter.go | 173 +++++++++++++++++++++++++++++++++++++----------------- utils.go | 5 +- 2 files changed, 122 insertions(+), 56 deletions(-) diff --git a/filter.go b/filter.go index 3a3e816..85fb98b 100644 --- a/filter.go +++ b/filter.go @@ -39,11 +39,12 @@ var highlight, helpStyle, selectionStyle, selectionArrow, + cursorStyle, normalArrow tcell.Style type Scene struct { - ID int - RenderFn func(*State, Items) + RenderFn func(*State, Items) + KeyHandlerFn func(*State, tcell.EventKey, Items) } type State struct { @@ -53,6 +54,9 @@ type State struct { 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 @@ -75,9 +79,19 @@ 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 *State, inputs Items) { + scene := s.Scenes[s.CurrentScene] + scene.RenderFn(s, inputs) +} + +func RenderHelp(s *State, inputs Items) { + drawText(*s.Screen, 0, 4, xmax-1, 4+1, normalArrow, "hnthnt") +} + +func RenderMain(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", s.Current+1) + status := fmt.Sprintf("[Line: %d][? For help]: %s", 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 @@ -85,7 +99,7 @@ func Render(s *State, inputs Items) { var txt string if item.Selected { - txt = fmt.Sprintf(" ✔ %s", item.Text) + txt = fmt.Sprintf(" X %s", item.Text) style = selectedStyle } else { txt = fmt.Sprintf(" %s", item.Text) @@ -96,19 +110,25 @@ func Render(s *State, inputs Items) { } if s.InSelectionMode { - low := min(s.SelectionStartLine, s.Current) - high := max(s.SelectionStartLine, s.Current) - - if i >= low && i <= high { + if (s.SelectionStartLine < s.LowerBound && i <= s.Current) || (s.SelectionStartLine > s.UpperBound && i >= s.Current) { style = selectionStyle arrowStyle = selectionArrow + } else { + low := min(s.SelectionStartLine, s.Current) + high := max(s.SelectionStartLine, s.Current) + + if i >= low && i <= high { + style = selectionStyle + arrowStyle = selectionArrow + } } } - drawText(*s.Screen, 0, y, xmax-1, y+1, style, txt) + 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-1-len(txt))) + 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, ">") } @@ -118,6 +138,70 @@ func Render(s *State, inputs Items) { } +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 + 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 { + start := min(state.SelectionStartLine, state.Current) + end := max(state.SelectionStartLine, state.Current) + toggleItems(input, start, end+1) + state.SelectionStartLine = -1 + } else { + state.SelectionStartLine = state.Current + } + + state.InSelectionMode = !state.InSelectionMode + + } else if ev.Key() == tcell.KeyCtrlH { + state.CurrentScene = 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() { input, err := ReadFromStdin() if err != nil { @@ -132,6 +216,7 @@ func main() { 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) done := false @@ -164,6 +249,15 @@ func main() { } defer quit() + mainScene := Scene{ + RenderMain, + HandleMain, + } + helpScene := Scene{ + RenderHelp, + HandleHelp, + } + state := State{ Current: 0, SelectionStartLine: 0, @@ -171,11 +265,21 @@ func main() { LowerBound: 0, UpperBound: min(ymax-3, len(*input)), InSelectionMode: false, + CurrentScene: 0, + Scenes: []*Scene{&mainScene, &helpScene}, } + previousScene := 0 + // Event loop for !done { - Render(state, input) + // If we need to render a new scene let's clear the screen first + if state.CurrentScene != previousScene { + s := *state.Screen + s.Clear() + } + + Render(&state, input) // Update screen s.Show() @@ -188,55 +292,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)[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 { - start := min(state.SelectionStartLine, state.Current) - end := max(state.SelectionStartLine, state.Current) - toggleItems(input, start, end) - } else { - state.SelectionStartLine = state.Current - } - - state.InSelectionMode = !state.InSelectionMode - } else if ev.Rune() == '?' { + } else { + // In case of no global binding hand the even to the scene + HandleKeyEvents(&state, *ev, input) } } } diff --git a/utils.go b/utils.go index 6349047..397ea29 100644 --- a/utils.go +++ b/utils.go @@ -23,8 +23,9 @@ import ( ) type Item struct { - Text string - Selected bool + Text string + Selected bool + Highlight bool } type Items = *[]*Item