feat: add leaves filter to show explicitly installed packages

Add new filter [L] to display only "leaf" packages - those installed
explicitly by the user and not as dependencies of other packages.
This commit is contained in:
Vito Castellano 2025-10-11 01:05:16 +02:00
commit b0ad014236
No known key found for this signature in database
GPG key ID: E13085DB38BC5819
2 changed files with 37 additions and 2 deletions

View file

@ -36,6 +36,7 @@ type AppService struct {
filteredPackages *[]models.Formula
showOnlyInstalled bool
showOnlyOutdated bool
showOnlyLeaves bool
brewVersion string
brewService BrewServiceInterface
@ -58,6 +59,7 @@ var NewAppService = func() AppServiceInterface {
filteredPackages: new([]models.Formula),
showOnlyInstalled: false,
showOnlyOutdated: false,
showOnlyLeaves: false,
brewVersion: "-",
}
@ -127,6 +129,15 @@ func (s *AppService) search(searchText string, scrollToTop bool) {
}
}
if s.showOnlyLeaves {
sourceList = &[]models.Formula{}
for _, info := range *s.packages {
if info.LocallyInstalled && len(info.Installed) > 0 && info.Installed[0].InstalledOnRequest {
*sourceList = append(*sourceList, info)
}
}
}
if searchText == "" {
// Reset to the appropriate list when the search string is empty
filteredList = *sourceList

View file

@ -3,6 +3,7 @@ package services
import (
"bbrew/internal/ui"
"fmt"
"github.com/gdamore/tcell/v2"
)
@ -11,6 +12,7 @@ type FilterType int
const (
FilterInstalled FilterType = iota
FilterOutdated
FilterLeaves
)
// IOAction represents an input/output action that can be triggered by a key event.
@ -43,6 +45,7 @@ type IOService struct {
ActionSearch *IOAction
ActionFilterInstalled *IOAction
ActionFilterOutdated *IOAction
ActionFilterLeaves *IOAction
ActionInstall *IOAction
ActionUpdate *IOAction
ActionRemove *IOAction
@ -62,6 +65,7 @@ var NewIOService = func(appService *AppService) IOServiceInterface {
s.ActionSearch = &IOAction{Key: tcell.KeyRune, Rune: '/', KeySlug: "/", Name: "Search"}
s.ActionFilterInstalled = &IOAction{Key: tcell.KeyRune, Rune: 'f', KeySlug: "f", Name: "Filter Installed"}
s.ActionFilterOutdated = &IOAction{Key: tcell.KeyRune, Rune: 'o', KeySlug: "o", Name: "Filter Outdated"}
s.ActionFilterLeaves = &IOAction{Key: tcell.KeyRune, Rune: 'l', KeySlug: "l", Name: "Filter Leaves"}
s.ActionInstall = &IOAction{Key: tcell.KeyRune, Rune: 'i', KeySlug: "i", Name: "Install"}
s.ActionUpdate = &IOAction{Key: tcell.KeyRune, Rune: 'u', KeySlug: "u", Name: "Update"}
s.ActionRemove = &IOAction{Key: tcell.KeyRune, Rune: 'r', KeySlug: "r", Name: "Remove"}
@ -73,6 +77,7 @@ var NewIOService = func(appService *AppService) IOServiceInterface {
s.ActionSearch.SetAction(s.handleSearchFieldEvent)
s.ActionFilterInstalled.SetAction(s.handleFilterPackagesEvent)
s.ActionFilterOutdated.SetAction(s.handleFilterOutdatedPackagesEvent)
s.ActionFilterLeaves.SetAction(s.handleFilterLeavesEvent)
s.ActionInstall.SetAction(s.handleInstallPackageEvent)
s.ActionUpdate.SetAction(s.handleUpdatePackageEvent)
s.ActionRemove.SetAction(s.handleRemovePackageEvent)
@ -85,6 +90,7 @@ var NewIOService = func(appService *AppService) IOServiceInterface {
s.ActionSearch,
s.ActionFilterInstalled,
s.ActionFilterOutdated,
s.ActionFilterLeaves,
s.ActionInstall,
s.ActionUpdate,
s.ActionRemove,
@ -149,19 +155,29 @@ func (s *IOService) handleFilterEvent(filterType FilterType) {
switch filterType {
case FilterInstalled:
if s.appService.showOnlyOutdated {
if s.appService.showOnlyOutdated || s.appService.showOnlyLeaves {
s.appService.showOnlyOutdated = false
s.appService.showOnlyLeaves = false
s.appService.showOnlyInstalled = true
} else {
s.appService.showOnlyInstalled = !s.appService.showOnlyInstalled
}
case FilterOutdated:
if s.appService.showOnlyInstalled {
if s.appService.showOnlyInstalled || s.appService.showOnlyLeaves {
s.appService.showOnlyInstalled = false
s.appService.showOnlyLeaves = false
s.appService.showOnlyOutdated = true
} else {
s.appService.showOnlyOutdated = !s.appService.showOnlyOutdated
}
case FilterLeaves:
if s.appService.showOnlyInstalled || s.appService.showOnlyOutdated {
s.appService.showOnlyInstalled = false
s.appService.showOnlyOutdated = false
s.appService.showOnlyLeaves = true
} else {
s.appService.showOnlyLeaves = !s.appService.showOnlyLeaves
}
}
// Update the search field label and legend based on the current filter state
@ -171,6 +187,9 @@ func (s *IOService) handleFilterEvent(filterType FilterType) {
} else if s.appService.showOnlyInstalled {
s.layout.GetSearch().Field().SetLabel("Search (Installed): ")
s.layout.GetLegend().SetLegend(s.legendEntries, s.ActionFilterInstalled.KeySlug)
} else if s.appService.showOnlyLeaves {
s.layout.GetSearch().Field().SetLabel("Search (Leaves): ")
s.layout.GetLegend().SetLegend(s.legendEntries, s.ActionFilterLeaves.KeySlug)
} else {
s.layout.GetSearch().Field().SetLabel("Search (All): ")
}
@ -188,6 +207,11 @@ func (s *IOService) handleFilterOutdatedPackagesEvent() {
s.handleFilterEvent(FilterOutdated)
}
// handleFilterLeavesEvent toggles the filter for leaf packages (installed on request)
func (s *IOService) handleFilterLeavesEvent() {
s.handleFilterEvent(FilterLeaves)
}
// showModal displays a modal dialog with the specified text and confirmation/cancellation actions.
// This is used for actions like installing, removing, or updating packages, invoking user confirmation.
func (s *IOService) showModal(text string, confirmFunc func(), cancelFunc func()) {