Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ linters:
- makezero # finds slice declarations with non-zero initial length
- mirror # reports wrong mirror patterns of bytes/strings usage
# TODO enable
# - mnd # detects magic numbers
- mnd # detects magic numbers
- musttag # enforces field tags in (un)marshaled structs
- nakedret # finds naked returns in functions greater than a specified function length
# TODO enable : Many reports. A bit hard to understand the nesting value.
Expand Down
31 changes: 20 additions & 11 deletions src/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,25 @@ func Run(content embed.FS) {
fmt.Println(variable.LastDirFile)
return nil
}
fmt.Printf("%-*s %s\n", 55, lipgloss.NewStyle().Foreground(lipgloss.Color("#66b2ff")).
Render("[Configuration file path]"), variable.ConfigFile)
fmt.Printf("%-*s %s\n", 55, lipgloss.NewStyle().Foreground(lipgloss.Color("#ffcc66")).
Render("[Hotkeys file path]"), variable.HotkeysFile)
fmt.Printf("%-*s %s\n", 55, lipgloss.NewStyle().Foreground(lipgloss.Color("#66ff66")).
Render("[Log file path]"), variable.LogFile)
fmt.Printf("%-*s %s\n", 55, lipgloss.NewStyle().Foreground(lipgloss.Color("#ff9999")).
Render("[Configuration directory path]"), variable.SuperFileMainDir)
fmt.Printf("%-*s %s\n", 55, lipgloss.NewStyle().Foreground(lipgloss.Color("#ff66ff")).
Render("[Data directory path]"), variable.SuperFileDataDir)
fmt.Printf("%-*s %s\n",
common.HelpKeyColumnWidth,
lipgloss.NewStyle().Foreground(lipgloss.Color("#66b2ff")).Render("[Configuration file path]"),
variable.ConfigFile,
)
fmt.Printf("%-*s %s\n",
common.HelpKeyColumnWidth,
lipgloss.NewStyle().Foreground(lipgloss.Color("#ffcc66")).Render("[Hotkeys file path]"),
variable.HotkeysFile,
)
logStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#66ff66"))
configStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#ff9999"))
dataStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#ff66ff"))
fmt.Printf("%-*s %s\n", common.HelpKeyColumnWidth,
logStyle.Render("[Log file path]"), variable.LogFile)
fmt.Printf("%-*s %s\n", common.HelpKeyColumnWidth,
configStyle.Render("[Configuration directory path]"), variable.SuperFileMainDir)
fmt.Printf("%-*s %s\n", common.HelpKeyColumnWidth,
dataStyle.Render("[Data directory path]"), variable.SuperFileDataDir)
return nil
},
Flags: []cli.Flag{
Expand Down Expand Up @@ -267,7 +276,7 @@ func shouldCheckForUpdate(now, last time.Time) bool {
}

func checkAndNotifyUpdate() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), common.DefaultCLIContextTimeout)
defer cancel()

resp, err := fetchLatestRelease(ctx)
Expand Down
30 changes: 21 additions & 9 deletions src/internal/common/string_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ import (
"github.com/charmbracelet/x/ansi"
)

// Size calculation constants
const (
KilobyteSize = 1000 // SI decimal unit
KibibyteSize = 1024 // Binary unit
TabWidth = 4 // Standard tab expansion width
DefaultBufferSize = 1024 // Default buffer size for string operations
NonBreakingSpace = 0xa0 // Unicode non-breaking space
EscapeChar = 0x1b // ANSI escape character
ASCIIMax = 0x7f // Maximum ASCII character value
)

func TruncateText(text string, maxChars int, tails string) string {
truncatedText := ansi.Truncate(text, maxChars-len(tails), "")
if text != truncatedText {
Expand Down Expand Up @@ -51,6 +62,7 @@ func TruncateMiddleText(text string, maxChars int, tails string) string {
return text
}

//nolint:mnd // standard halving for center truncation
halfEllipsisLength := (maxChars - 3) / 2
// TODO : Use ansi.Substring to correctly handle ANSI escape codes
truncatedText := text[:halfEllipsisLength] + tails + text[utf8.RuneCountInString(text)-halfEllipsisLength:]
Expand Down Expand Up @@ -116,12 +128,12 @@ func FormatFileSize(size int64) string {

// TODO : Remove duplication here
if Config.FileSizeUseSI {
unitIndex := int(math.Floor(math.Log(float64(size)) / math.Log(1000)))
adjustedSize := float64(size) / math.Pow(1000, float64(unitIndex))
unitIndex := int(math.Floor(math.Log(float64(size)) / math.Log(KilobyteSize)))
adjustedSize := float64(size) / math.Pow(KilobyteSize, float64(unitIndex))
return fmt.Sprintf("%.2f %s", adjustedSize, unitsDec[unitIndex])
}
unitIndex := int(math.Floor(math.Log(float64(size)) / math.Log(1024)))
adjustedSize := float64(size) / math.Pow(1024, float64(unitIndex))
unitIndex := int(math.Floor(math.Log(float64(size)) / math.Log(KibibyteSize)))
adjustedSize := float64(size) / math.Pow(KibibyteSize, float64(unitIndex))
return fmt.Sprintf("%.2f %s", adjustedSize, unitsBin[unitIndex])
}

Expand All @@ -132,7 +144,7 @@ func CheckAndTruncateLineLengths(text string, maxLength int) string {

for _, line := range lines {
// Replace tabs with spaces
expandedLine := strings.ReplaceAll(line, "\t", strings.Repeat(" ", 4))
expandedLine := strings.ReplaceAll(line, "\t", strings.Repeat(" ", TabWidth))
truncatedLine := ansi.Truncate(expandedLine, maxLength, "")
result.WriteString(truncatedLine + "\n")
}
Expand Down Expand Up @@ -180,7 +192,7 @@ func IsTextFile(filename string) (bool, error) {
defer file.Close()

reader := bufio.NewReader(file)
buffer := make([]byte, 1024)
buffer := make([]byte, DefaultBufferSize)
cnt, err := reader.Read(buffer)
if err != nil && !errors.Is(err, io.EOF) {
return false, err
Expand All @@ -204,7 +216,7 @@ func MakePrintableWithEscCheck(line string, allowEsc bool) string { //nolint: go
}
// It needs to be handled separately since considered a space,
// It is multi-byte in UTF-8, But it has zero display width
if r == 0xa0 {
if r == NonBreakingSpace {
sb.WriteRune(r)
continue
}
Expand All @@ -215,13 +227,13 @@ func MakePrintableWithEscCheck(line string, allowEsc bool) string { //nolint: go
sb.WriteString(" ")
continue
}
if r == 0x1b {
if r == EscapeChar {
if allowEsc {
sb.WriteRune(r)
}
continue
}
if r > 0x7f {
if r > ASCIIMax {
if unicode.IsSpace(r) && utf8.RuneLen(r) > 1 {
// See https://github.com/charmbracelet/x/issues/466
// Space chacters spanning more than one bytes are not handled well by
Expand Down
3 changes: 2 additions & 1 deletion src/internal/common/style_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ func GenerateNewFileTextInput() textinput.Model {
t.PlaceholderStyle = ModalStyle
t.Focus()
t.CharLimit = 156
//nolint:mnd // modal width minus padding
t.Width = ModalWidth - 10
return t
}
Expand Down Expand Up @@ -304,7 +305,7 @@ func GeneratePinnedRenameTextInput(cursorPos int, defaultValue string) textinput
ti.SetCursor(cursorPos)
ti.Focus()
ti.CharLimit = 156
ti.Width = Config.SidebarWidth - 4
ti.Width = Config.SidebarWidth - PanelPadding
return ti
}

Expand Down
36 changes: 36 additions & 0 deletions src/internal/common/ui_consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package common

import "time"

// Shared UI/layout constants to replace magic numbers flagged by mnd.
const (
HelpKeyColumnWidth = 55 // width of help key column in CLI help
DefaultCLIContextTimeout = 5 * time.Second // default CLI context timeout for CLI ops

PanelPadding = 3 // rows reserved around file list (borders/header/footer)
BorderPadding = 2 // rows/cols for outer border frame
InnerPadding = 4 // cols for inner content padding (truncate widths)
FooterGroupCols = 3 // columns per group in footer layout math

DefaultFilePanelWidth = 10 // default width for file panels
FilePanelMax = 10 // max number of file panels supported
MinWidthForRename = 18 // minimal width for rename input to render
ResponsiveWidthThreshold = 95 // width breakpoint for layout behavior

HeightBreakA = 30 // responsive height tiers
HeightBreakB = 35
HeightBreakC = 40
HeightBreakD = 45

ReRenderChunkDivisor = 100 // divisor for re-render throttling

FilePanelWidthUnit = 20 // width unit used to calculate max file panels
DefaultPreviewTimeout = 500 * time.Millisecond // preview operation timeout

// File permissions
ExtractedFileMode = 0644 // default permissions for extracted files
ExtractedDirMode = 0755 // default permissions for extracted directories

// UI positioning
CenterDivisor = 2 // divisor for centering UI elements
)
2 changes: 1 addition & 1 deletion src/internal/default_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func defaultModelConfig(toggleDotFile, toggleFooter, firstUse bool,
fileModel: fileModel{
filePanels: filePanelSlice(firstPanelPaths),
filePreview: preview.New(),
width: 10,
width: common.DefaultFilePanelWidth,
},
helpMenu: newHelpMenuModal(),
promptModal: prompt.DefaultModel(prompt.PromptMinHeight, prompt.PromptMinWidth),
Expand Down
5 changes: 3 additions & 2 deletions src/internal/file_operations_extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"golift.io/xtractr"

"github.com/yorukot/superfile/src/config/icon"
"github.com/yorukot/superfile/src/internal/common"
"github.com/yorukot/superfile/src/internal/ui/processbar"
)

Expand All @@ -20,8 +21,8 @@ func extractCompressFile(src, dest string, processBar *processbar.Model) error {
x := &xtractr.XFile{
FilePath: src,
OutputDir: dest,
FileMode: 0644,
DirMode: 0755,
FileMode: common.ExtractedFileMode,
DirMode: common.ExtractedDirMode,
}

_, _, _, err = xtractr.ExtractFile(x)
Expand Down
3 changes: 2 additions & 1 deletion src/internal/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ func getTypeOrderingFunc(elements []element, reversed bool) sliceOrderFunc {
}

func panelElementHeight(mainPanelHeight int) int {
return mainPanelHeight - 3
return mainPanelHeight - common.PanelPadding
}

// TODO : replace usage of this with slices.contains
Expand Down Expand Up @@ -291,6 +291,7 @@ func renameIfDuplicate(destination string) (string, error) {

// Extract base name without existing suffix
counter := 1
//nolint:mnd // 3 = full match + 2 capture groups
if match := suffixRegexp.FindStringSubmatch(name); len(match) == 3 {
name = match[1] // base name without (N)
if num, err := strconv.Atoi(match[2]); err == nil {
Expand Down
5 changes: 4 additions & 1 deletion src/internal/handle_file_operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ func (m *model) panelItemRename() {
m.fileModel.renaming = true
panel.renaming = true
m.firstTextInput = true
panel.rename = common.GenerateRenameTextInput(m.fileModel.width-4, cursorPos, panel.element[panel.cursor].name)
panel.rename = common.GenerateRenameTextInput(
m.fileModel.width-common.InnerPadding,
cursorPos,
panel.element[panel.cursor].name)
}

func (m *model) getDeleteCmd(permDelete bool) tea.Cmd {
Expand Down
5 changes: 3 additions & 2 deletions src/internal/handle_modal.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path/filepath"
"strings"

"github.com/yorukot/superfile/src/internal/common"
"github.com/yorukot/superfile/src/internal/utils"
)

Expand Down Expand Up @@ -160,7 +161,7 @@ func (m *model) helpMenuListUp() {
// Similarly, we use max(..., 0) to ensure the renderIndex doesn't become negative,
// which can happen if the number of items is less than the view height.
// This prevents a potential out-of-bounds panic during rendering.
m.helpMenu.renderIndex = max(len(m.helpMenu.filteredData)-(m.helpMenu.height-4), 0)
m.helpMenu.renderIndex = max(len(m.helpMenu.filteredData)-(m.helpMenu.height-common.InnerPadding), 0)
}
}

Expand Down Expand Up @@ -189,7 +190,7 @@ func (m *model) helpMenuListDown() {
m.helpMenu.renderIndex++
}
// Clamp renderIndex to bottom.
bottom := len(m.helpMenu.filteredData) - (m.helpMenu.height - 4)
bottom := len(m.helpMenu.filteredData) - (m.helpMenu.height - common.InnerPadding)
if bottom < 0 {
bottom = 0
}
Expand Down
3 changes: 2 additions & 1 deletion src/internal/handle_panel_movement.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

tea "github.com/charmbracelet/bubbletea"

"github.com/yorukot/superfile/src/internal/common"
"github.com/yorukot/superfile/src/internal/utils"

variable "github.com/yorukot/superfile/src/config"
Expand Down Expand Up @@ -175,7 +176,7 @@ func (m *model) searchBarFocus() {
}

// config search bar width
panel.searchBar.Width = m.fileModel.width - 4
panel.searchBar.Width = m.fileModel.width - common.InnerPadding
}

func (m *model) sidebarSearchBarFocus() {
Expand Down
26 changes: 13 additions & 13 deletions src/internal/handle_panel_navigation.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (m *model) createNewFilePanel(location string) error {
// File preview panel width same as file panel
if common.Config.FilePreviewWidth == 0 {
m.fileModel.filePreview.SetWidth((m.fullWidth - common.Config.SidebarWidth -
(4 + (len(m.fileModel.filePanels))*2)) / (len(m.fileModel.filePanels) + 1))
(common.InnerPadding + (len(m.fileModel.filePanels))*common.BorderPadding)) / (len(m.fileModel.filePanels) + 1))
} else {
m.fileModel.filePreview.SetWidth((m.fullWidth - common.Config.SidebarWidth) / common.Config.FilePreviewWidth)
}
Expand All @@ -60,13 +60,13 @@ func (m *model) createNewFilePanel(location string) error {
m.fileModel.filePanels[m.filePanelFocusIndex].isFocused = false
m.fileModel.filePanels[m.filePanelFocusIndex+1].isFocused = returnFocusType(m.focusPanel)
m.fileModel.width = (m.fullWidth - common.Config.SidebarWidth - m.fileModel.filePreview.GetWidth() -
(4 + (len(m.fileModel.filePanels)-1)*2)) / len(m.fileModel.filePanels)
(common.InnerPadding + (len(m.fileModel.filePanels)-1)*common.BorderPadding)) / len(m.fileModel.filePanels)
m.filePanelFocusIndex++

m.fileModel.maxFilePanel = (m.fullWidth - common.Config.SidebarWidth - m.fileModel.filePreview.GetWidth()) / 20
m.fileModel.maxFilePanel = (m.fullWidth - common.Config.SidebarWidth - m.fileModel.filePreview.GetWidth()) / common.FilePanelWidthUnit

for i := range m.fileModel.filePanels {
m.fileModel.filePanels[i].searchBar.Width = m.fileModel.width - 4
m.fileModel.filePanels[i].searchBar.Width = m.fileModel.width - common.InnerPadding
}
return nil
}
Expand All @@ -84,7 +84,7 @@ func (m *model) closeFilePanel() {
// File preview panel width same as file panel
if common.Config.FilePreviewWidth == 0 {
m.fileModel.filePreview.SetWidth((m.fullWidth - common.Config.SidebarWidth -
(4 + (len(m.fileModel.filePanels))*2)) / (len(m.fileModel.filePanels) + 1))
(common.InnerPadding + (len(m.fileModel.filePanels))*common.BorderPadding)) / (len(m.fileModel.filePanels) + 1))
} else {
m.fileModel.filePreview.SetWidth((m.fullWidth - common.Config.SidebarWidth) / common.Config.FilePreviewWidth)
}
Expand All @@ -95,37 +95,37 @@ func (m *model) closeFilePanel() {
}

m.fileModel.width = (m.fullWidth - common.Config.SidebarWidth - m.fileModel.filePreview.GetWidth() -
(4 + (len(m.fileModel.filePanels)-1)*2)) / len(m.fileModel.filePanels)
(common.InnerPadding + (len(m.fileModel.filePanels)-1)*common.BorderPadding)) / len(m.fileModel.filePanels)
m.fileModel.filePanels[m.filePanelFocusIndex].isFocused = returnFocusType(m.focusPanel)

m.fileModel.maxFilePanel = (m.fullWidth - common.Config.SidebarWidth - m.fileModel.filePreview.GetWidth()) / 20
m.fileModel.maxFilePanel = (m.fullWidth - common.Config.SidebarWidth - m.fileModel.filePreview.GetWidth()) / common.FilePanelWidthUnit

for i := range m.fileModel.filePanels {
m.fileModel.filePanels[i].searchBar.Width = m.fileModel.width - 4
m.fileModel.filePanels[i].searchBar.Width = m.fileModel.width - common.InnerPadding
}
}

func (m *model) toggleFilePreviewPanel() {
m.fileModel.filePreview.ToggleOpen()
m.fileModel.filePreview.SetWidth(0)
m.fileModel.filePreview.SetHeight(m.mainPanelHeight + 2)
m.fileModel.filePreview.SetHeight(m.mainPanelHeight + common.BorderPadding)
if m.fileModel.filePreview.IsOpen() {
// File preview panel width same as file panel
if common.Config.FilePreviewWidth == 0 {
m.fileModel.filePreview.SetWidth((m.fullWidth - common.Config.SidebarWidth -
(4 + (len(m.fileModel.filePanels))*2)) / (len(m.fileModel.filePanels) + 1))
(common.InnerPadding + (len(m.fileModel.filePanels))*common.BorderPadding)) / (len(m.fileModel.filePanels) + 1))
} else {
m.fileModel.filePreview.SetWidth((m.fullWidth - common.Config.SidebarWidth) / common.Config.FilePreviewWidth)
}
}

m.fileModel.width = (m.fullWidth - common.Config.SidebarWidth - m.fileModel.filePreview.GetWidth() -
(4 + (len(m.fileModel.filePanels)-1)*2)) / len(m.fileModel.filePanels)
(common.InnerPadding + (len(m.fileModel.filePanels)-1)*common.BorderPadding)) / len(m.fileModel.filePanels)

m.fileModel.maxFilePanel = (m.fullWidth - common.Config.SidebarWidth - m.fileModel.filePreview.GetWidth()) / 20
m.fileModel.maxFilePanel = (m.fullWidth - common.Config.SidebarWidth - m.fileModel.filePreview.GetWidth()) / common.FilePanelWidthUnit

for i := range m.fileModel.filePanels {
m.fileModel.filePanels[i].searchBar.Width = m.fileModel.width - 4
m.fileModel.filePanels[i].searchBar.Width = m.fileModel.width - common.InnerPadding
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/internal/handle_panel_up_down.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (panel *filePanel) pgUp(mainPanelHeight int) {
}

panHeight := panelElementHeight(mainPanelHeight)
panCenter := panHeight / 2 // For making sure the cursor is at the center of the panel
panCenter := panHeight / 2 //nolint:mnd // For making sure the cursor is at the center of the panel

if panHeight >= panlen {
panel.cursor = 0
Expand All @@ -86,7 +86,7 @@ func (panel *filePanel) pgDown(mainPanelHeight int) {
}

panHeight := panelElementHeight(mainPanelHeight)
panCenter := panHeight / 2 // For making sure the cursor is at the center of the panel
panCenter := panHeight / 2 //nolint:mnd // For making sure the cursor is at the center of the panel

if panHeight >= panlen {
panel.cursor = panlen - 1
Expand Down
Loading
Loading