mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 00:21:04 +00:00
stop support menu filtering for simplicity
This commit is contained in:
parent
e6d4a97545
commit
f7beb6e83f
13 changed files with 326 additions and 231 deletions
|
|
@ -28,7 +28,7 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
|
||||||
sidePanelsDirection = boxlayout.ROW
|
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
|
infoSectionSize := 0
|
||||||
if showInfoSection {
|
if showInfoSection {
|
||||||
infoSectionSize = 1
|
infoSectionSize = 1
|
||||||
|
|
@ -87,14 +87,14 @@ func (gui *Gui) getMidSectionWeights() (int, int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
|
func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
|
||||||
if gui.State.Searching.isSearching {
|
if gui.State.Filter.active {
|
||||||
return []*boxlayout.Box{
|
return []*boxlayout.Box{
|
||||||
{
|
{
|
||||||
Window: "searchPrefix",
|
Window: "filterPrefix",
|
||||||
Size: runewidth.StringWidth(SEARCH_PREFIX),
|
Size: runewidth.StringWidth(gui.filterPrompt()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Window: "search",
|
Window: "filter",
|
||||||
Weight: 1,
|
Weight: 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
80
pkg/gui/filtering.go
Normal file
80
pkg/gui/filtering.go
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
146
pkg/gui/focus.go
Normal file
146
pkg/gui/focus.go
Normal file
|
|
@ -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
|
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 {
|
type filterState struct {
|
||||||
panel ISideListPanel
|
// If true then we're either currently inside the filter view
|
||||||
isSearching bool
|
// or we've committed the filter and we're back in the list view
|
||||||
searchString string
|
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
|
// 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
|
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 {
|
func (gui *Gui) handleDonate(g *gocui.Gui, v *gocui.View) error {
|
||||||
if !gui.g.Mouse {
|
if !gui.g.Mouse {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -110,11 +110,11 @@ func (gui *Gui) refreshStateImages() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) filterString(view *gocui.View) string {
|
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 ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.State.Searching.searchString
|
return gui.State.Filter.needle
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: merge into the above
|
// TODO: merge into the above
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,12 @@ func (b *Binding) GetKey() string {
|
||||||
// GetInitialKeybindings is a function.
|
// GetInitialKeybindings is a function.
|
||||||
func (gui *Gui) GetInitialKeybindings() []*Binding {
|
func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||||
bindings := []*Binding{
|
bindings := []*Binding{
|
||||||
|
{
|
||||||
|
ViewName: "",
|
||||||
|
Key: gocui.KeyEsc,
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: wrappedHandler(gui.escape),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ViewName: "",
|
ViewName: "",
|
||||||
Key: 'q',
|
Key: 'q',
|
||||||
|
|
@ -68,12 +74,6 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.quit,
|
Handler: gui.quit,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
ViewName: "",
|
|
||||||
Key: gocui.KeyEsc,
|
|
||||||
Modifier: gocui.ModNone,
|
|
||||||
Handler: gui.quit,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
ViewName: "",
|
ViewName: "",
|
||||||
Key: gocui.KeyPgup,
|
Key: gocui.KeyPgup,
|
||||||
|
|
@ -173,13 +173,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||||
ViewName: "menu",
|
ViewName: "menu",
|
||||||
Key: gocui.KeyEsc,
|
Key: gocui.KeyEsc,
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleMenuClose,
|
Handler: wrappedHandler(gui.handleMenuClose),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "menu",
|
ViewName: "menu",
|
||||||
Key: 'q',
|
Key: 'q',
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleMenuClose,
|
Handler: wrappedHandler(gui.handleMenuClose),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "menu",
|
ViewName: "menu",
|
||||||
|
|
@ -517,16 +517,16 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||||
Handler: gui.scrollRightMain,
|
Handler: gui.scrollRightMain,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "search",
|
ViewName: "filter",
|
||||||
Key: gocui.KeyEnter,
|
Key: gocui.KeyEnter,
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: wrappedHandler(gui.commitSearch),
|
Handler: wrappedHandler(gui.commitFilter),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "search",
|
ViewName: "filter",
|
||||||
Key: gocui.KeyEsc,
|
Key: gocui.KeyEsc,
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: wrappedHandler(gui.escapeSearchPrompt),
|
Handler: wrappedHandler(gui.escapeFilterPrompt),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "",
|
ViewName: "",
|
||||||
|
|
@ -619,7 +619,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||||
ViewName: panel.View().Name(),
|
ViewName: panel.View().Name(),
|
||||||
Key: '/',
|
Key: '/',
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: wrappedHandler(gui.handleOpenSearch),
|
Handler: wrappedHandler(gui.handleOpenFilter),
|
||||||
Description: gui.Tr.LcFilter,
|
Description: gui.Tr.LcFilter,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,9 @@ type SideListPanel[T comparable] struct {
|
||||||
|
|
||||||
// function to be called after re-rendering list. Can be nil
|
// function to be called after re-rendering list. Can be nil
|
||||||
onRerender func() error
|
onRerender func() error
|
||||||
|
|
||||||
|
// set this to true if you don't want to allow manual filtering via '/'
|
||||||
|
disableFilter bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ISideListPanel interface {
|
type ISideListPanel interface {
|
||||||
|
|
@ -81,6 +84,7 @@ type ISideListPanel interface {
|
||||||
View() *gocui.View
|
View() *gocui.View
|
||||||
Refocus()
|
Refocus()
|
||||||
RerenderList() error
|
RerenderList() error
|
||||||
|
IsFilterDisabled() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ ISideListPanel = &SideListPanel[int]{}
|
var _ ISideListPanel = &SideListPanel[int]{}
|
||||||
|
|
@ -294,3 +298,7 @@ func (self *SideListPanel[T]) RerenderList() error {
|
||||||
func (self *SideListPanel[T]) SetContextIndex(index int) {
|
func (self *SideListPanel[T]) SetContextIndex(index int) {
|
||||||
self.contextIdx = index
|
self.contextIdx = index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *SideListPanel[T]) IsFilterDisabled() bool {
|
||||||
|
return self.disableFilter
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package gui
|
package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/gocui"
|
|
||||||
"github.com/jesseduffield/lazydocker/pkg/utils"
|
"github.com/jesseduffield/lazydocker/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -39,6 +38,11 @@ func (gui *Gui) getMenuPanel() *SideListPanel[*MenuItem] {
|
||||||
onRerender: func() error {
|
onRerender: func() error {
|
||||||
return gui.resizePopupPanel(gui.Views.Menu)
|
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
|
// the menu panel doesn't actually have any contexts to display on the main view
|
||||||
// so what follows are all dummy values
|
// so what follows are all dummy values
|
||||||
|
|
@ -54,17 +58,15 @@ func (gui *Gui) getMenuPanel() *SideListPanel[*MenuItem] {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) onMenuPress(menuItem *MenuItem) error {
|
func (gui *Gui) onMenuPress(menuItem *MenuItem) error {
|
||||||
gui.Views.Menu.Visible = false
|
if err := gui.handleMenuClose(); err != nil {
|
||||||
err := gui.returnFocus()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if menuItem.OnPress == nil {
|
if menuItem.OnPress != nil {
|
||||||
return nil
|
return menuItem.OnPress()
|
||||||
}
|
}
|
||||||
|
|
||||||
return menuItem.OnPress()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleMenuPress() error {
|
func (gui *Gui) handleMenuPress() error {
|
||||||
|
|
@ -133,7 +135,21 @@ func (gui *Gui) renderMenuOptions() error {
|
||||||
return gui.renderOptionsMap(optionsMap)
|
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
|
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()
|
return gui.returnFocus()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ func (gui *Gui) getProjectPanel() *SideListPanel[*commands.Project] {
|
||||||
getDisplayStrings: func(project *commands.Project) []string {
|
getDisplayStrings: func(project *commands.Project) []string {
|
||||||
return []string{project.Name}
|
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
|
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
|
// if the cursor down past the last item, move it to the last line
|
||||||
// nolint:unparam
|
// nolint:unparam
|
||||||
func (gui *Gui) focusPoint(selectedX int, selectedY int, lineCount int, v *gocui.View) {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, cy := v.Cursor()
|
_, cy := v.Cursor()
|
||||||
_, oy := v.Origin()
|
_, oy := v.Origin()
|
||||||
|
|
||||||
|
|
@ -415,6 +294,12 @@ func (gui *Gui) handleClick(v *gocui.View, itemCount int, selectedLine *int, han
|
||||||
|
|
||||||
*selectedLine = newSelectedLine
|
*selectedLine = newSelectedLine
|
||||||
|
|
||||||
|
if gui.currentViewName() != v.Name() {
|
||||||
|
if err := gui.switchFocus(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return handleSelect(gui.g, v)
|
return handleSelect(gui.g, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ import (
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SEARCH_PREFIX = "filter: "
|
|
||||||
|
|
||||||
type Views struct {
|
type Views struct {
|
||||||
// side panels
|
// side panels
|
||||||
Project *gocui.View
|
Project *gocui.View
|
||||||
|
|
@ -23,9 +21,9 @@ type Views struct {
|
||||||
Information *gocui.View
|
Information *gocui.View
|
||||||
AppStatus *gocui.View
|
AppStatus *gocui.View
|
||||||
// text that prompts you to enter text in the Search 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
|
// appears next to the SearchPrefix view, it's where you type in the search string
|
||||||
Search *gocui.View
|
Filter *gocui.View
|
||||||
|
|
||||||
// popups
|
// popups
|
||||||
Confirmation *gocui.View
|
Confirmation *gocui.View
|
||||||
|
|
@ -56,8 +54,8 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
|
||||||
{viewPtr: &gui.Views.Options, name: "options"},
|
{viewPtr: &gui.Views.Options, name: "options"},
|
||||||
{viewPtr: &gui.Views.AppStatus, name: "appStatus"},
|
{viewPtr: &gui.Views.AppStatus, name: "appStatus"},
|
||||||
{viewPtr: &gui.Views.Information, name: "information"},
|
{viewPtr: &gui.Views.Information, name: "information"},
|
||||||
{viewPtr: &gui.Views.Search, name: "search"},
|
{viewPtr: &gui.Views.Filter, name: "filter"},
|
||||||
{viewPtr: &gui.Views.SearchPrefix, name: "searchPrefix"},
|
{viewPtr: &gui.Views.FilterPrefix, name: "filterPrefix"},
|
||||||
|
|
||||||
// popups.
|
// popups.
|
||||||
{viewPtr: &gui.Views.Menu, name: "menu"},
|
{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.Title = gui.Tr.NotEnoughSpace
|
||||||
gui.Views.Limit.Wrap = true
|
gui.Views.Limit.Wrap = true
|
||||||
|
|
||||||
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault
|
gui.Views.FilterPrefix.BgColor = gocui.ColorDefault
|
||||||
gui.Views.SearchPrefix.FgColor = gocui.ColorGreen
|
gui.Views.FilterPrefix.FgColor = gocui.ColorGreen
|
||||||
gui.Views.SearchPrefix.Frame = false
|
gui.Views.FilterPrefix.Frame = false
|
||||||
_ = gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX)
|
_ = gui.setViewContent(gui.Views.FilterPrefix, gui.filterPrompt())
|
||||||
|
|
||||||
gui.Views.Search.BgColor = gocui.ColorDefault
|
gui.Views.Filter.BgColor = gocui.ColorDefault
|
||||||
gui.Views.Search.FgColor = gocui.ColorGreen
|
gui.Views.Filter.FgColor = gocui.ColorGreen
|
||||||
gui.Views.Search.Editable = true
|
gui.Views.Filter.Editable = true
|
||||||
gui.Views.Search.Frame = false
|
gui.Views.Filter.Frame = false
|
||||||
gui.Views.Search.Editor = gocui.EditorFunc(gui.wrapEditor(gocui.SimpleEditor))
|
gui.Views.Filter.Editor = gocui.EditorFunc(gui.wrapEditor(gocui.SimpleEditor))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +167,7 @@ func (gui *Gui) controlledBoundsViewNames() []string {
|
||||||
"appStatus",
|
"appStatus",
|
||||||
"main",
|
"main",
|
||||||
"limit",
|
"limit",
|
||||||
"searchPrefix",
|
"filterPrefix",
|
||||||
"search",
|
"filter",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,8 @@ type TranslationSet struct {
|
||||||
|
|
||||||
LcNextScreenMode string
|
LcNextScreenMode string
|
||||||
LcPrevScreenMode string
|
LcPrevScreenMode string
|
||||||
|
FilterPrompt string
|
||||||
|
PressEscToCancel string
|
||||||
}
|
}
|
||||||
|
|
||||||
func englishSet() TranslationSet {
|
func englishSet() TranslationSet {
|
||||||
|
|
@ -243,5 +245,7 @@ func englishSet() TranslationSet {
|
||||||
|
|
||||||
LcNextScreenMode: "next screen mode (normal/half/fullscreen)",
|
LcNextScreenMode: "next screen mode (normal/half/fullscreen)",
|
||||||
LcPrevScreenMode: "prev screen mode",
|
LcPrevScreenMode: "prev screen mode",
|
||||||
|
FilterPrompt: "filter",
|
||||||
|
PressEscToCancel: "press escape to cancel",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue