stop support menu filtering for simplicity

pull/392/head
Jesse Duffield 2 years ago
parent e6d4a97545
commit f7beb6e83f

@ -28,7 +28,7 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
sidePanelsDirection = boxlayout.ROW
}
showInfoSection := gui.Config.UserConfig.Gui.ShowBottomLine || gui.State.Searching.isSearching
showInfoSection := gui.Config.UserConfig.Gui.ShowBottomLine || gui.State.Filter.active
infoSectionSize := 0
if showInfoSection {
infoSectionSize = 1
@ -87,14 +87,14 @@ func (gui *Gui) getMidSectionWeights() (int, int) {
}
func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
if gui.State.Searching.isSearching {
if gui.State.Filter.active {
return []*boxlayout.Box{
{
Window: "searchPrefix",
Size: runewidth.StringWidth(SEARCH_PREFIX),
Window: "filterPrefix",
Size: runewidth.StringWidth(gui.filterPrompt()),
},
{
Window: "search",
Window: "filter",
Weight: 1,
},
}

@ -0,0 +1,80 @@
package gui
import (
"fmt"
"github.com/jesseduffield/gocui"
)
func (gui *Gui) handleOpenFilter() error {
panel, ok := gui.currentListPanel()
if !ok {
return nil
}
if panel.IsFilterDisabled() {
return nil
}
gui.State.Filter.active = true
gui.State.Filter.panel = panel
return gui.switchFocus(gui.Views.Filter)
}
func (gui *Gui) onNewFilterNeedle(value string) error {
gui.State.Filter.needle = value
gui.ResetOrigin(gui.State.Filter.panel.View())
return gui.State.Filter.panel.RerenderList()
}
func (gui *Gui) wrapEditor(f func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool) func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
return func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
matched := f(v, key, ch, mod)
if matched {
if err := gui.onNewFilterNeedle(v.TextArea.GetContent()); err != nil {
gui.Log.Error(err)
}
}
return matched
}
}
func (gui *Gui) escapeFilterPrompt() error {
if err := gui.clearFilter(); err != nil {
return err
}
return gui.returnFocus()
}
func (gui *Gui) clearFilter() error {
gui.State.Filter.needle = ""
gui.State.Filter.active = false
panel := gui.State.Filter.panel
gui.State.Filter.panel = nil
gui.Views.Filter.ClearTextArea()
if panel == nil {
return nil
}
gui.ResetOrigin(panel.View())
return panel.RerenderList()
}
// returns to the list view with the filter still applied
func (gui *Gui) commitFilter() error {
if gui.State.Filter.needle == "" {
if err := gui.clearFilter(); err != nil {
return err
}
}
return gui.returnFocus()
}
func (gui *Gui) filterPrompt() string {
return fmt.Sprintf("%s: ", gui.Tr.FilterPrompt)
}

@ -0,0 +1,146 @@
package gui
import (
"github.com/jesseduffield/gocui"
"github.com/samber/lo"
)
func (gui *Gui) newLineFocused(v *gocui.View) error {
if v == nil {
return nil
}
currentListPanel, ok := gui.currentListPanel()
if ok {
return currentListPanel.HandleSelect()
}
switch v.Name() {
case "confirmation":
return nil
case "main":
v.Highlight = false
return nil
case "filter":
return nil
default:
panic(gui.Tr.NoViewMachingNewLineFocusedSwitchStatement)
}
}
// TODO: move some of this logic into our onFocusLost and onFocus hooks
func (gui *Gui) switchFocus(newView *gocui.View) error {
gui.Mutexes.ViewStackMutex.Lock()
defer gui.Mutexes.ViewStackMutex.Unlock()
return gui.switchFocusAux(newView)
}
func (gui *Gui) switchFocusAux(newView *gocui.View) error {
gui.pushView(newView.Name())
gui.Log.Info("setting highlight to true for view " + newView.Name())
gui.Log.Info("new focused view is " + newView.Name())
if _, err := gui.g.SetCurrentView(newView.Name()); err != nil {
return err
}
gui.g.Cursor = newView.Editable
if err := gui.renderPanelOptions(); err != nil {
return err
}
newViewStack := gui.State.ViewStack
if gui.State.Filter.panel != nil && !lo.Contains(newViewStack, gui.State.Filter.panel.View().Name()) {
if err := gui.clearFilter(); err != nil {
return err
}
}
// TODO: add 'onFocusLost' hook
if !lo.Contains(newViewStack, "menu") {
gui.Views.Menu.Visible = false
}
return gui.newLineFocused(newView)
}
func (gui *Gui) returnFocus() error {
gui.Mutexes.ViewStackMutex.Lock()
defer gui.Mutexes.ViewStackMutex.Unlock()
if len(gui.State.ViewStack) <= 1 {
return nil
}
previousViewName := gui.State.ViewStack[len(gui.State.ViewStack)-2]
previousView, err := gui.g.View(previousViewName)
if err != nil {
return err
}
return gui.switchFocusAux(previousView)
}
func (gui *Gui) removeViewFromStack(view *gocui.View) {
gui.Mutexes.ViewStackMutex.Lock()
defer gui.Mutexes.ViewStackMutex.Unlock()
gui.State.ViewStack = lo.Filter(gui.State.ViewStack, func(viewName string, _ int) bool {
return viewName != view.Name()
})
}
// Not to be called directly. Use `switchFocus` instead
func (gui *Gui) pushView(name string) {
// No matter what view we're pushing, we first remove all popup panels from the stack
// (unless it's the search view because we may be searching the menu panel)
if name != "filter" {
gui.State.ViewStack = lo.Filter(gui.State.ViewStack, func(viewName string, _ int) bool {
return !gui.isPopupPanel(viewName)
})
}
// If we're pushing a side panel, we remove all other panels
if lo.Contains(gui.sideViewNames(), name) {
gui.State.ViewStack = []string{}
}
// If we're pushing a panel that's already in the stack, we remove it
gui.State.ViewStack = lo.Filter(gui.State.ViewStack, func(viewName string, _ int) bool {
return viewName != name
})
gui.State.ViewStack = append(gui.State.ViewStack, name)
}
// excludes popups
func (gui *Gui) currentStaticViewName() string {
gui.Mutexes.ViewStackMutex.Lock()
defer gui.Mutexes.ViewStackMutex.Unlock()
for i := len(gui.State.ViewStack) - 1; i >= 0; i-- {
if !lo.Contains(gui.popupViewNames(), gui.State.ViewStack[i]) {
return gui.State.ViewStack[i]
}
}
return gui.initiallyFocusedViewName()
}
func (gui *Gui) currentSideViewName() string {
gui.Mutexes.ViewStackMutex.Lock()
defer gui.Mutexes.ViewStackMutex.Unlock()
// we expect that there is a side window somewhere in the view stack, so we will search from top to bottom
for idx := range gui.State.ViewStack {
reversedIdx := len(gui.State.ViewStack) - 1 - idx
viewName := gui.State.ViewStack[reversedIdx]
if lo.Contains(gui.sideViewNames(), viewName) {
return viewName
}
}
return gui.initiallyFocusedViewName()
}

@ -83,13 +83,19 @@ type guiState struct {
ScreenMode WindowMaximisation
Searching searchingState
// Maintains the state of manual filtering i.e. typing in a substring
// to filter on in the current panel.
Filter filterState
}
type searchingState struct {
panel ISideListPanel
isSearching bool
searchString string
type filterState struct {
// If true then we're either currently inside the filter view
// or we've committed the filter and we're back in the list view
active bool
// The panel that we're filtering.
panel ISideListPanel
// The string that we're filtering on
needle string
}
// screen sizing determines how much space your selected window takes up (window
@ -368,6 +374,16 @@ func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
// this handler is executed when we press escape when there is only one view
// on the stack.
func (gui *Gui) escape() error {
if gui.State.Filter.active {
return gui.clearFilter()
}
return nil
}
func (gui *Gui) handleDonate(g *gocui.Gui, v *gocui.View) error {
if !gui.g.Mouse {
return nil

@ -110,11 +110,11 @@ func (gui *Gui) refreshStateImages() error {
}
func (gui *Gui) filterString(view *gocui.View) string {
if gui.State.Searching.panel != nil && gui.State.Searching.panel.View() != view {
if gui.State.Filter.panel != nil && gui.State.Filter.panel.View() != view {
return ""
}
return gui.State.Searching.searchString
return gui.State.Filter.needle
}
// TODO: merge into the above

@ -58,19 +58,19 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
bindings := []*Binding{
{
ViewName: "",
Key: 'q',
Key: gocui.KeyEsc,
Modifier: gocui.ModNone,
Handler: gui.quit,
Handler: wrappedHandler(gui.escape),
},
{
ViewName: "",
Key: gocui.KeyCtrlC,
Key: 'q',
Modifier: gocui.ModNone,
Handler: gui.quit,
},
{
ViewName: "",
Key: gocui.KeyEsc,
Key: gocui.KeyCtrlC,
Modifier: gocui.ModNone,
Handler: gui.quit,
},
@ -173,13 +173,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "menu",
Key: gocui.KeyEsc,
Modifier: gocui.ModNone,
Handler: gui.handleMenuClose,
Handler: wrappedHandler(gui.handleMenuClose),
},
{
ViewName: "menu",
Key: 'q',
Modifier: gocui.ModNone,
Handler: gui.handleMenuClose,
Handler: wrappedHandler(gui.handleMenuClose),
},
{
ViewName: "menu",
@ -517,16 +517,16 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.scrollRightMain,
},
{
ViewName: "search",
ViewName: "filter",
Key: gocui.KeyEnter,
Modifier: gocui.ModNone,
Handler: wrappedHandler(gui.commitSearch),
Handler: wrappedHandler(gui.commitFilter),
},
{
ViewName: "search",
ViewName: "filter",
Key: gocui.KeyEsc,
Modifier: gocui.ModNone,
Handler: wrappedHandler(gui.escapeSearchPrompt),
Handler: wrappedHandler(gui.escapeFilterPrompt),
},
{
ViewName: "",
@ -619,7 +619,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: panel.View().Name(),
Key: '/',
Modifier: gocui.ModNone,
Handler: wrappedHandler(gui.handleOpenSearch),
Handler: wrappedHandler(gui.handleOpenFilter),
Description: gui.Tr.LcFilter,
})
}

@ -73,6 +73,9 @@ type SideListPanel[T comparable] struct {
// function to be called after re-rendering list. Can be nil
onRerender func() error
// set this to true if you don't want to allow manual filtering via '/'
disableFilter bool
}
type ISideListPanel interface {
@ -81,6 +84,7 @@ type ISideListPanel interface {
View() *gocui.View
Refocus()
RerenderList() error
IsFilterDisabled() bool
}
var _ ISideListPanel = &SideListPanel[int]{}
@ -294,3 +298,7 @@ func (self *SideListPanel[T]) RerenderList() error {
func (self *SideListPanel[T]) SetContextIndex(index int) {
self.contextIdx = index
}
func (self *SideListPanel[T]) IsFilterDisabled() bool {
return self.disableFilter
}

@ -1,7 +1,6 @@
package gui
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/utils"
)
@ -39,6 +38,11 @@ func (gui *Gui) getMenuPanel() *SideListPanel[*MenuItem] {
onRerender: func() error {
return gui.resizePopupPanel(gui.Views.Menu)
},
// so that we can avoid some UI trickiness, the menu will not have filtering
// abillity yet. To support it, we would need to have filter state against
// each panel (e.g. for when you filter the images panel, then bring up
// the options menu, then try to filter that too.
disableFilter: true,
// the menu panel doesn't actually have any contexts to display on the main view
// so what follows are all dummy values
@ -54,17 +58,15 @@ func (gui *Gui) getMenuPanel() *SideListPanel[*MenuItem] {
}
func (gui *Gui) onMenuPress(menuItem *MenuItem) error {
gui.Views.Menu.Visible = false
err := gui.returnFocus()
if err != nil {
if err := gui.handleMenuClose(); err != nil {
return err
}
if menuItem.OnPress == nil {
return nil
if menuItem.OnPress != nil {
return menuItem.OnPress()
}
return menuItem.OnPress()
return nil
}
func (gui *Gui) handleMenuPress() error {
@ -133,7 +135,21 @@ func (gui *Gui) renderMenuOptions() error {
return gui.renderOptionsMap(optionsMap)
}
func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) handleMenuClose() error {
gui.Views.Menu.Visible = false
// this code is here for when we do add filter ability to the menu panel,
// though it's currently disabled
if gui.State.Filter.panel == gui.Panels.Menu {
if err := gui.clearFilter(); err != nil {
return err
}
// we need to remove the view from the view stack because we're about to
// return focus and don't want to land in the search view when it was searching
// the menu in the first place
gui.removeViewFromStack(gui.Views.Filter)
}
return gui.returnFocus()
}

@ -63,6 +63,8 @@ func (gui *Gui) getProjectPanel() *SideListPanel[*commands.Project] {
getDisplayStrings: func(project *commands.Project) []string {
return []string{project.Name}
},
// It doesn't make sense to filter a list of only one item.
disableFilter: true,
}
}

@ -1,60 +0,0 @@
package gui
import (
"github.com/jesseduffield/gocui"
)
func (gui *Gui) handleOpenSearch() error {
panel, ok := gui.currentListPanel()
if !ok {
return nil
}
gui.State.Searching.isSearching = true
gui.State.Searching.panel = panel
return gui.switchFocus(gui.Views.Search)
}
func (gui *Gui) onNewSearchString(value string) error {
// need to refresh the right list panel.
gui.State.Searching.searchString = value
gui.ResetOrigin(gui.State.Searching.panel.View())
return gui.State.Searching.panel.RerenderList()
}
func (gui *Gui) wrapEditor(f func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool) func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
return func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
matched := f(v, key, ch, mod)
if matched {
// TODO: handle error
_ = gui.onNewSearchString(v.TextArea.GetContent())
}
return matched
}
}
func (gui *Gui) escapeSearchPrompt() error {
if err := gui.clearSearch(); err != nil {
return err
}
return gui.returnFocus()
}
func (gui *Gui) clearSearch() error {
gui.State.Searching.searchString = ""
gui.State.Searching.isSearching = false
panel := gui.State.Searching.panel
gui.State.Searching.panel = nil
gui.Views.Search.ClearTextArea()
gui.ResetOrigin(panel.View())
return panel.RerenderList()
}
// returns to the list view with the filter still applied
func (gui *Gui) commitSearch() error {
return gui.returnFocus()
}

@ -66,123 +66,6 @@ func (gui *Gui) resetMainView() {
gui.getMainView().Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
}
func (gui *Gui) newLineFocused(v *gocui.View) error {
if v == nil {
return nil
}
currentListPanel, ok := gui.currentListPanel()
if ok {
return currentListPanel.HandleSelect()
}
switch v.Name() {
case "confirmation":
return nil
case "main":
v.Highlight = false
return nil
case "search":
return nil
default:
panic(gui.Tr.NoViewMachingNewLineFocusedSwitchStatement)
}
}
// TODO: move some of this logic into our onFocusLost and onFocus hooks
func (gui *Gui) switchFocus(newView *gocui.View) error {
gui.Mutexes.ViewStackMutex.Lock()
defer gui.Mutexes.ViewStackMutex.Unlock()
return gui.switchFocusAux(newView)
}
func (gui *Gui) switchFocusAux(newView *gocui.View) error {
gui.pushView(newView.Name())
gui.Log.Info("setting highlight to true for view " + newView.Name())
gui.Log.Info("new focused view is " + newView.Name())
if _, err := gui.g.SetCurrentView(newView.Name()); err != nil {
return err
}
gui.g.Cursor = newView.Editable
if err := gui.renderPanelOptions(); err != nil {
return err
}
return gui.newLineFocused(newView)
}
func (gui *Gui) returnFocus() error {
gui.Mutexes.ViewStackMutex.Lock()
defer gui.Mutexes.ViewStackMutex.Unlock()
if len(gui.State.ViewStack) <= 1 {
return nil
}
previousViewName := gui.State.ViewStack[len(gui.State.ViewStack)-2]
previousView, err := gui.g.View(previousViewName)
if err != nil {
return err
}
return gui.switchFocusAux(previousView)
}
// Not to be called directly. Use `switchFocus` instead
func (gui *Gui) pushView(name string) {
// No matter what view we're pushing, we first remove all popup panels from the stack
// (unless it's the search view because we may be searching the menu panel)
if name != "search" {
gui.State.ViewStack = lo.Filter(gui.State.ViewStack, func(viewName string, _ int) bool {
return !gui.isPopupPanel(viewName)
})
}
// If we're pushing a side panel, we remove all other panels
if lo.Contains(gui.sideViewNames(), name) {
gui.State.ViewStack = []string{}
}
// If we're pushing a panel that's already in the stack, we remove it
gui.State.ViewStack = lo.Filter(gui.State.ViewStack, func(viewName string, _ int) bool {
return viewName != name
})
gui.State.ViewStack = append(gui.State.ViewStack, name)
}
// excludes popups
func (gui *Gui) currentStaticViewName() string {
gui.Mutexes.ViewStackMutex.Lock()
defer gui.Mutexes.ViewStackMutex.Unlock()
for i := len(gui.State.ViewStack) - 1; i >= 0; i-- {
if !lo.Contains(gui.popupViewNames(), gui.State.ViewStack[i]) {
return gui.State.ViewStack[i]
}
}
return gui.initiallyFocusedViewName()
}
func (gui *Gui) currentSideViewName() string {
gui.Mutexes.ViewStackMutex.Lock()
defer gui.Mutexes.ViewStackMutex.Unlock()
// we expect that there is a side window somewhere in the view stack, so we will search from top to bottom
for idx := range gui.State.ViewStack {
reversedIdx := len(gui.State.ViewStack) - 1 - idx
viewName := gui.State.ViewStack[reversedIdx]
if lo.Contains(gui.sideViewNames(), viewName) {
return viewName
}
}
return gui.initiallyFocusedViewName()
}
// if the cursor down past the last item, move it to the last line
// nolint:unparam
func (gui *Gui) focusPoint(selectedX int, selectedY int, lineCount int, v *gocui.View) {
@ -396,10 +279,6 @@ func (gui *Gui) handleClick(v *gocui.View, itemCount int, selectedLine *int, han
return nil
}
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
return err
}
_, cy := v.Cursor()
_, oy := v.Origin()
@ -415,6 +294,12 @@ func (gui *Gui) handleClick(v *gocui.View, itemCount int, selectedLine *int, han
*selectedLine = newSelectedLine
if gui.currentViewName() != v.Name() {
if err := gui.switchFocus(v); err != nil {
return err
}
}
return handleSelect(gui.g, v)
}

@ -5,8 +5,6 @@ import (
"github.com/jesseduffield/gocui"
)
const SEARCH_PREFIX = "filter: "
type Views struct {
// side panels
Project *gocui.View
@ -23,9 +21,9 @@ type Views struct {
Information *gocui.View
AppStatus *gocui.View
// text that prompts you to enter text in the Search view
SearchPrefix *gocui.View
FilterPrefix *gocui.View
// appears next to the SearchPrefix view, it's where you type in the search string
Search *gocui.View
Filter *gocui.View
// popups
Confirmation *gocui.View
@ -56,8 +54,8 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
{viewPtr: &gui.Views.Options, name: "options"},
{viewPtr: &gui.Views.AppStatus, name: "appStatus"},
{viewPtr: &gui.Views.Information, name: "information"},
{viewPtr: &gui.Views.Search, name: "search"},
{viewPtr: &gui.Views.SearchPrefix, name: "searchPrefix"},
{viewPtr: &gui.Views.Filter, name: "filter"},
{viewPtr: &gui.Views.FilterPrefix, name: "filterPrefix"},
// popups.
{viewPtr: &gui.Views.Menu, name: "menu"},
@ -128,16 +126,16 @@ func (gui *Gui) createAllViews() error {
gui.Views.Limit.Title = gui.Tr.NotEnoughSpace
gui.Views.Limit.Wrap = true
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault
gui.Views.SearchPrefix.FgColor = gocui.ColorGreen
gui.Views.SearchPrefix.Frame = false
_ = gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX)
gui.Views.FilterPrefix.BgColor = gocui.ColorDefault
gui.Views.FilterPrefix.FgColor = gocui.ColorGreen
gui.Views.FilterPrefix.Frame = false
_ = gui.setViewContent(gui.Views.FilterPrefix, gui.filterPrompt())
gui.Views.Search.BgColor = gocui.ColorDefault
gui.Views.Search.FgColor = gocui.ColorGreen
gui.Views.Search.Editable = true
gui.Views.Search.Frame = false
gui.Views.Search.Editor = gocui.EditorFunc(gui.wrapEditor(gocui.SimpleEditor))
gui.Views.Filter.BgColor = gocui.ColorDefault
gui.Views.Filter.FgColor = gocui.ColorGreen
gui.Views.Filter.Editable = true
gui.Views.Filter.Frame = false
gui.Views.Filter.Editor = gocui.EditorFunc(gui.wrapEditor(gocui.SimpleEditor))
return nil
}
@ -169,7 +167,7 @@ func (gui *Gui) controlledBoundsViewNames() []string {
"appStatus",
"main",
"limit",
"searchPrefix",
"search",
"filterPrefix",
"filter",
}
}

@ -118,6 +118,8 @@ type TranslationSet struct {
LcNextScreenMode string
LcPrevScreenMode string
FilterPrompt string
PressEscToCancel string
}
func englishSet() TranslationSet {
@ -243,5 +245,7 @@ func englishSet() TranslationSet {
LcNextScreenMode: "next screen mode (normal/half/fullscreen)",
LcPrevScreenMode: "prev screen mode",
FilterPrompt: "filter",
PressEscToCancel: "press escape to cancel",
}
}

Loading…
Cancel
Save