tui/keys.go

Summary

Maintainability
A
2 hrs
Test Coverage
package tui

import (
    "fmt"

    "github.com/dundee/gdu/v5/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)
}