star/gdu/tui/keys.go
2025-04-26 19:33:14 +08:00

351 lines
6.9 KiB
Go

package tui
import (
"fmt"
"b612.me/apps/b612/gdu/pkg/fs"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
func (ui *UI) keyPressed(key *tcell.EventKey) *tcell.EventKey {
if ui.handleCtrlZ(key) == nil {
return nil
}
if ui.pages.HasPage("file") || ui.pages.HasPage("export") {
return key // send event to primitive
}
if ui.filtering {
return key
}
key = ui.handleClosingModals(key)
if key == nil {
return nil
}
key = ui.handleInfoPageEvents(key)
if key == nil {
return nil
}
key = ui.handleQuit(key)
if key == nil {
return nil
}
if ui.pages.HasPage("confirm") {
return ui.handleConfirmation(key)
}
if ui.pages.HasPage("progress") ||
ui.pages.HasPage("deleting") ||
ui.pages.HasPage("emptying") {
return key
}
key = ui.handleHelp(key)
if key == nil {
return nil
}
if ui.pages.HasPage("help") {
return key
}
key = ui.handleShell(key)
if key == nil {
return nil
}
key = ui.handleLeftRight(key)
if key == nil {
return nil
}
key = ui.handleFiltering(key)
if key == nil {
return nil
}
return ui.handleMainActions(key)
}
func (ui *UI) handleClosingModals(key *tcell.EventKey) *tcell.EventKey {
if key.Key() == tcell.KeyEsc || key.Rune() == 'q' {
if ui.pages.HasPage("help") {
ui.pages.RemovePage("help")
ui.app.SetFocus(ui.table)
return nil
}
if ui.pages.HasPage("info") {
ui.pages.RemovePage("info")
ui.app.SetFocus(ui.table)
return nil
}
}
return key
}
func (ui *UI) handleConfirmation(key *tcell.EventKey) *tcell.EventKey {
if key.Rune() == 'h' {
return tcell.NewEventKey(tcell.KeyLeft, 0, 0)
}
if key.Rune() == 'l' {
return tcell.NewEventKey(tcell.KeyRight, 0, 0)
}
return key
}
func (ui *UI) handleInfoPageEvents(key *tcell.EventKey) *tcell.EventKey {
if ui.pages.HasPage("info") {
switch key.Rune() {
case 'i':
ui.pages.RemovePage("info")
ui.app.SetFocus(ui.table)
return nil
case '?':
return nil
}
if key.Key() == tcell.KeyUp ||
key.Key() == tcell.KeyDown ||
key.Rune() == 'j' ||
key.Rune() == 'k' {
row, column := ui.table.GetSelection()
if (key.Key() == tcell.KeyUp || key.Rune() == 'k') && row > 0 {
row--
} else if (key.Key() == tcell.KeyDown || key.Rune() == 'j') &&
row+1 < ui.table.GetRowCount() {
row++
}
ui.table.Select(row, column)
}
ui.showInfo() // refresh file info after any change
}
return key
}
// handle ctrl+z job control
func (ui *UI) handleCtrlZ(key *tcell.EventKey) *tcell.EventKey {
if key.Key() == tcell.KeyCtrlZ {
ui.app.Suspend(func() {
termApp := ui.app.(*tview.Application)
termApp.Lock()
defer termApp.Unlock()
err := stopProcess()
if err != nil {
ui.showErr("Error sending STOP signal", err)
}
})
return nil
}
return key
}
func (ui *UI) handleQuit(key *tcell.EventKey) *tcell.EventKey {
switch key.Rune() {
case 'Q':
ui.app.Stop()
fmt.Fprintf(ui.output, "%s\n", ui.currentDirPath)
return nil
case 'q':
ui.app.Stop()
return nil
}
return key
}
func (ui *UI) handleHelp(key *tcell.EventKey) *tcell.EventKey {
if key.Rune() == '?' {
if ui.pages.HasPage("help") {
ui.pages.RemovePage("help")
ui.app.SetFocus(ui.table)
return nil
}
ui.showHelp()
return nil
}
return key
}
func (ui *UI) handleShell(key *tcell.EventKey) *tcell.EventKey {
if key.Rune() == 'b' {
ui.spawnShell()
return nil
}
return key
}
func (ui *UI) handleLeftRight(key *tcell.EventKey) *tcell.EventKey {
if key.Rune() == 'h' || key.Key() == tcell.KeyLeft {
ui.handleLeft()
return nil
}
if key.Rune() == 'l' || key.Key() == tcell.KeyRight {
ui.handleRight()
return nil
}
return key
}
func (ui *UI) handleFiltering(key *tcell.EventKey) *tcell.EventKey {
if key.Key() == tcell.KeyTab && ui.filteringInput != nil {
ui.filtering = true
ui.app.SetFocus(ui.filteringInput)
return nil
}
return key
}
// nolint: funlen // Why: there's a lot of options to handle
func (ui *UI) handleMainActions(key *tcell.EventKey) *tcell.EventKey {
switch key.Rune() {
case 'd':
ui.handleDelete(false)
case 'e':
ui.handleDelete(true)
case 'v':
ui.showFile()
case 'o':
ui.openItem()
case 'i':
ui.showInfo()
case 'a':
ui.ShowApparentSize = !ui.ShowApparentSize
if ui.currentDir != nil {
row, column := ui.table.GetSelection()
ui.showDir()
ui.table.Select(row, column)
}
case 'B':
ui.ShowRelativeSize = !ui.ShowRelativeSize
if ui.currentDir != nil {
row, column := ui.table.GetSelection()
ui.showDir()
ui.table.Select(row, column)
}
case 'c':
ui.showItemCount = !ui.showItemCount
if ui.currentDir != nil {
row, column := ui.table.GetSelection()
ui.showDir()
ui.table.Select(row, column)
}
case 'm':
ui.showMtime = !ui.showMtime
if ui.currentDir != nil {
row, column := ui.table.GetSelection()
ui.showDir()
ui.table.Select(row, column)
}
case 'r':
if ui.currentDir != nil {
ui.rescanDir()
}
case 'E':
ui.confirmExport()
return nil
case 's':
ui.setSorting("size")
case 'C':
ui.setSorting("itemCount")
case 'n':
ui.setSorting("name")
case 'M':
ui.setSorting("mtime")
case '/':
ui.showFilterInput()
return nil
case ' ':
ui.handleMark()
case 'I':
ui.ignoreItem()
}
return key
}
func (ui *UI) handleLeft() {
if ui.currentDirPath == ui.topDirPath {
if ui.devices != nil {
ui.currentDir = nil
err := ui.ListDevices(ui.getter)
if err != nil {
ui.showErr("Error listing devices", err)
}
}
return
}
if ui.currentDir != nil {
ui.fileItemSelected(0, 0)
}
}
func (ui *UI) handleRight() {
row, column := ui.table.GetSelection()
if ui.currentDirPath != ui.topDirPath && row == 0 { // do not select /..
return
}
if ui.currentDir != nil {
ui.fileItemSelected(row, column)
} else {
ui.deviceItemSelected(row, column)
}
}
func (ui *UI) handleDelete(shouldEmpty bool) {
if ui.currentDir == nil {
return
}
// do not allow deleting parent dir
row, column := ui.table.GetSelection()
selectedFile, ok := ui.table.GetCell(row, column).GetReference().(fs.Item)
if !ok || selectedFile == ui.currentDir.GetParent() {
return
}
if ui.askBeforeDelete {
ui.confirmDeletion(shouldEmpty)
} else {
ui.delete(shouldEmpty)
}
}
func (ui *UI) handleMark() {
if ui.currentDir == nil {
return
}
// do not allow deleting parent dir
row, column := ui.table.GetSelection()
selectedFile, ok := ui.table.GetCell(row, column).GetReference().(fs.Item)
if !ok || selectedFile == ui.currentDir.GetParent() {
return
}
ui.fileItemMarked(row)
}
func (ui *UI) ignoreItem() {
if ui.currentDir == nil {
return
}
// do not allow ignoring parent dir
row, column := ui.table.GetSelection()
selectedFile, ok := ui.table.GetCell(row, column).GetReference().(fs.Item)
if !ok || selectedFile == ui.currentDir.GetParent() {
return
}
if _, ok := ui.ignoredRows[row]; ok {
delete(ui.ignoredRows, row)
} else {
ui.ignoredRows[row] = struct{}{}
}
ui.showDir()
// select next row if possible
ui.table.Select(min(row+1, ui.table.GetRowCount()-1), 0)
}