333 lines
9.0 KiB
Go
333 lines
9.0 KiB
Go
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gdamore/tcell/v2"
|
|
"github.com/rivo/tview"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"b612.me/apps/b612/gdu/build"
|
|
)
|
|
|
|
const helpText = ` [::b]上/下, k/j [white:black:-]光标上/下移动
|
|
[::b]pgup/pgdn, g/G [white:black:-]光标跳转至顶部/底部
|
|
[::b]enter, 右, l [white:black:-]进入目录/设备
|
|
[::b]左, h [white:black:-]返回上级目录
|
|
|
|
[::b]r [white:black:-]重新扫描当前目录
|
|
[::b]E [white:black:-]导出分析数据为JSON文件
|
|
[::b]/ [white:black:-]按名称搜索项目
|
|
[::b]a [white:black:-]切换磁盘用量与表观大小显示
|
|
[::b]B [white:black:-]切换进度条对齐方式(最大文件/目录)
|
|
[::b]c [white:black:-]显示/隐藏文件数量统计
|
|
[::b]m [white:black:-]显示/隐藏最新修改时间
|
|
[::b]b [white:black:-]在当前目录打开Shell
|
|
[::b]q [white:black:-]退出程序
|
|
[::b]Q [white:black:-]退出并输出当前目录路径
|
|
|
|
光标所在项目操作:
|
|
[::b]d [white:black:-]删除文件或目录
|
|
[::b]e [white:black:-]清空文件或目录
|
|
[::b]space [white:black:-]标记待删除文件/目录
|
|
[::b]I [white:black:-]忽略当前文件或目录
|
|
[::b]v [white:black:-]查看文件内容
|
|
[::b]o [white:black:-]使用外部程序打开
|
|
[::b]i [white:black:-]显示项目详细信息
|
|
|
|
排序方式(再次按键切换升序/降序):
|
|
[::b]n [white:black:-]按名称排序
|
|
[::b]s [white:black:-]按大小排序
|
|
[::b]C [white:black:-]按文件数量排序
|
|
[::b]M [white:black:-]按修改时间排序`
|
|
|
|
// nolint: funlen // Why: complex function
|
|
func (ui *UI) showDir() {
|
|
var (
|
|
totalUsage int64
|
|
totalSize int64
|
|
maxUsage int64
|
|
maxSize int64
|
|
itemCount int
|
|
)
|
|
|
|
ui.currentDirPath = ui.currentDir.GetPath()
|
|
|
|
if ui.changeCwdFn != nil {
|
|
err := ui.changeCwdFn(ui.currentDirPath)
|
|
if err != nil {
|
|
log.Printf("error setting cwd: %s", err.Error())
|
|
}
|
|
log.Printf("changing cwd to %s", ui.currentDirPath)
|
|
}
|
|
|
|
ui.currentDirLabel.SetText("[::b] --- " +
|
|
tview.Escape(
|
|
strings.TrimPrefix(ui.currentDirPath, build.RootPathPrefix),
|
|
) +
|
|
" ---").SetDynamicColors(true)
|
|
|
|
ui.table.Clear()
|
|
|
|
rowIndex := 0
|
|
if ui.currentDirPath != ui.topDirPath {
|
|
prefix := " "
|
|
if len(ui.markedRows) > 0 {
|
|
prefix += " "
|
|
}
|
|
|
|
cell := tview.NewTableCell(prefix + "[::b]/..")
|
|
cell.SetReference(ui.currentDir.GetParent())
|
|
cell.SetStyle(tcell.Style{}.Foreground(tcell.ColorDefault))
|
|
ui.table.SetCell(0, 0, cell)
|
|
rowIndex++
|
|
}
|
|
|
|
ui.sortItems()
|
|
|
|
unlock := ui.currentDir.RLock()
|
|
defer unlock()
|
|
|
|
i := rowIndex
|
|
maxUsage = 0
|
|
maxSize = 0
|
|
for _, item := range ui.currentDir.GetFiles() {
|
|
if _, ignored := ui.ignoredRows[i]; ignored {
|
|
i++
|
|
continue
|
|
}
|
|
|
|
if ui.ShowRelativeSize {
|
|
if item.GetUsage() > maxUsage {
|
|
maxUsage = item.GetUsage()
|
|
}
|
|
if item.GetSize() > maxSize {
|
|
maxSize = item.GetSize()
|
|
}
|
|
} else {
|
|
maxSize += item.GetSize()
|
|
maxUsage += item.GetUsage()
|
|
}
|
|
i++
|
|
}
|
|
|
|
for i, item := range ui.currentDir.GetFiles() {
|
|
if ui.filterValue != "" && !strings.Contains(
|
|
strings.ToLower(item.GetName()),
|
|
strings.ToLower(ui.filterValue),
|
|
) {
|
|
continue
|
|
}
|
|
|
|
_, ignored := ui.ignoredRows[rowIndex]
|
|
|
|
if !ignored {
|
|
totalUsage += item.GetUsage()
|
|
totalSize += item.GetSize()
|
|
itemCount += item.GetItemCount()
|
|
}
|
|
|
|
_, marked := ui.markedRows[rowIndex]
|
|
cell := tview.NewTableCell(ui.formatFileRow(item, maxUsage, maxSize, marked, ignored))
|
|
cell.SetReference(ui.currentDir.GetFiles()[i])
|
|
|
|
switch {
|
|
case ignored:
|
|
cell.SetStyle(tcell.Style{}.Foreground(tview.Styles.SecondaryTextColor))
|
|
case marked:
|
|
cell.SetStyle(tcell.Style{}.Foreground(tview.Styles.PrimaryTextColor))
|
|
cell.SetBackgroundColor(tview.Styles.ContrastBackgroundColor)
|
|
default:
|
|
cell.SetStyle(tcell.Style{}.Foreground(tcell.ColorDefault))
|
|
}
|
|
|
|
ui.table.SetCell(rowIndex, 0, cell)
|
|
rowIndex++
|
|
}
|
|
|
|
var footerNumberColor, footerTextColor string
|
|
if ui.UseColors {
|
|
footerNumberColor = fmt.Sprintf(
|
|
"[%s:%s:b]",
|
|
ui.footerNumberColor,
|
|
ui.footerBackgroundColor,
|
|
)
|
|
footerTextColor = fmt.Sprintf(
|
|
"[%s:%s:-]",
|
|
ui.footerTextColor,
|
|
ui.footerBackgroundColor,
|
|
)
|
|
} else {
|
|
footerNumberColor = "[black:white:b]"
|
|
footerTextColor = blackOnWhite
|
|
}
|
|
|
|
selected := ""
|
|
if len(ui.markedRows) > 0 {
|
|
selected = " Selected items: " + footerNumberColor +
|
|
strconv.Itoa(len(ui.markedRows)) + footerTextColor
|
|
}
|
|
|
|
ui.footerLabel.SetText(
|
|
selected + footerTextColor +
|
|
" Total disk usage: " +
|
|
footerNumberColor +
|
|
ui.formatSize(totalUsage, true, false) +
|
|
" Apparent size: " +
|
|
footerNumberColor +
|
|
ui.formatSize(totalSize, true, false) +
|
|
" Items: " + footerNumberColor + strconv.Itoa(itemCount) +
|
|
footerTextColor +
|
|
" Sorting by: " + ui.sortBy + " " + ui.sortOrder)
|
|
|
|
ui.table.Select(0, 0)
|
|
ui.table.ScrollToBeginning()
|
|
|
|
if !ui.filtering {
|
|
ui.app.SetFocus(ui.table)
|
|
}
|
|
}
|
|
|
|
func (ui *UI) showDevices() {
|
|
var totalUsage int64
|
|
|
|
ui.table.Clear()
|
|
ui.table.SetCell(0, 0, tview.NewTableCell("Device name").SetSelectable(false))
|
|
ui.table.SetCell(0, 1, tview.NewTableCell("Size").SetSelectable(false))
|
|
ui.table.SetCell(0, 2, tview.NewTableCell("Used").SetSelectable(false))
|
|
ui.table.SetCell(0, 3, tview.NewTableCell("Used part").SetSelectable(false))
|
|
ui.table.SetCell(0, 4, tview.NewTableCell("Free").SetSelectable(false))
|
|
ui.table.SetCell(0, 5, tview.NewTableCell("Mount point").SetSelectable(false))
|
|
|
|
var textColor, sizeColor string
|
|
if ui.UseColors {
|
|
textColor = "[#3498db:-:b]"
|
|
sizeColor = "[#edb20a:-:b]"
|
|
} else {
|
|
textColor = "[white:-:b]"
|
|
sizeColor = "[white:-:b]"
|
|
}
|
|
|
|
ui.sortDevices()
|
|
|
|
for i, device := range ui.devices {
|
|
totalUsage += device.GetUsage()
|
|
ui.table.SetCell(i+1, 0, tview.NewTableCell(textColor+device.Name).SetReference(ui.devices[i]))
|
|
ui.table.SetCell(i+1, 1, tview.NewTableCell(ui.formatSize(device.Size, false, true)))
|
|
ui.table.SetCell(i+1, 2, tview.NewTableCell(sizeColor+ui.formatSize(device.Size-device.Free, false, true)))
|
|
ui.table.SetCell(i+1, 3, tview.NewTableCell(getDeviceUsagePart(device, ui.useOldSizeBar)))
|
|
ui.table.SetCell(i+1, 4, tview.NewTableCell(ui.formatSize(device.Free, false, true)))
|
|
ui.table.SetCell(i+1, 5, tview.NewTableCell(textColor+device.MountPoint).SetReference(ui.devices[i]))
|
|
}
|
|
|
|
var footerNumberColor, footerTextColor string
|
|
if ui.UseColors {
|
|
footerNumberColor = fmt.Sprintf(
|
|
"[%s:%s:b]",
|
|
ui.footerNumberColor,
|
|
ui.footerBackgroundColor,
|
|
)
|
|
footerTextColor = fmt.Sprintf(
|
|
"[%s:%s:-]",
|
|
ui.footerTextColor,
|
|
ui.footerBackgroundColor,
|
|
)
|
|
} else {
|
|
footerNumberColor = "[black:white:b]"
|
|
footerTextColor = blackOnWhite
|
|
}
|
|
|
|
ui.footerLabel.SetText(
|
|
" Total usage: " +
|
|
footerNumberColor +
|
|
ui.formatSize(totalUsage, true, false) +
|
|
footerTextColor +
|
|
" Sorting by: " + ui.sortBy + " " + ui.sortOrder)
|
|
|
|
ui.table.Select(1, 0)
|
|
ui.table.SetSelectedFunc(ui.deviceItemSelected)
|
|
|
|
if ui.topDirPath != "" {
|
|
for i, device := range ui.devices {
|
|
if device.MountPoint == ui.topDirPath {
|
|
ui.table.Select(i+1, 0)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ui *UI) showErr(msg string, err error) {
|
|
modal := tview.NewModal().
|
|
SetText(msg + ": " + err.Error()).
|
|
AddButtons([]string{"ok"}).
|
|
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
|
ui.pages.RemovePage("error")
|
|
})
|
|
|
|
if !ui.UseColors {
|
|
modal.SetBackgroundColor(tcell.ColorGray)
|
|
}
|
|
|
|
ui.pages.AddPage("error", modal, true, true)
|
|
ui.app.SetFocus(modal)
|
|
}
|
|
|
|
func (ui *UI) showErrFromGo(msg string, err error) {
|
|
ui.app.QueueUpdateDraw(func() {
|
|
ui.showErr(msg, err)
|
|
})
|
|
}
|
|
|
|
func (ui *UI) showHelp() {
|
|
text := tview.NewTextView().SetDynamicColors(true)
|
|
text.SetBorder(true).SetBorderPadding(2, 2, 2, 2)
|
|
text.SetBorderColor(tcell.ColorDefault)
|
|
text.SetTitle(" gdu help ")
|
|
text.SetScrollable(true)
|
|
|
|
formattedHelpText := ui.formatHelpTextFor()
|
|
text.SetText(formattedHelpText)
|
|
|
|
maxHeight := strings.Count(formattedHelpText, "\n") + 7
|
|
_, height := ui.screen.Size()
|
|
if height > maxHeight {
|
|
height = maxHeight
|
|
}
|
|
|
|
flex := tview.NewFlex().
|
|
AddItem(nil, 0, 1, false).
|
|
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
|
|
AddItem(nil, 0, 1, false).
|
|
AddItem(text, height, 1, false).
|
|
AddItem(nil, 0, 1, false), 80, 1, false).
|
|
AddItem(nil, 0, 1, false)
|
|
|
|
ui.help = flex
|
|
ui.pages.AddPage("help", flex, true, true)
|
|
ui.app.SetFocus(text)
|
|
}
|
|
|
|
func (ui *UI) formatHelpTextFor() string {
|
|
lines := strings.Split(helpText, "\n")
|
|
|
|
for i, line := range lines {
|
|
if ui.UseColors {
|
|
lines[i] = strings.ReplaceAll(
|
|
strings.ReplaceAll(line, defaultColorBold, "[red]"),
|
|
whiteOnBlack,
|
|
"[white]",
|
|
)
|
|
}
|
|
|
|
if ui.noDelete && (strings.Contains(line, "Empty file or directory") ||
|
|
strings.Contains(line, "Delete file or directory")) {
|
|
lines[i] += " (disabled)"
|
|
}
|
|
}
|
|
|
|
return strings.Join(lines, "\n")
|
|
}
|