Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7186827
feat: Added basic bulk rename feature
harryfrzz Oct 6, 2025
069b328
Merge branch 'feature_branch' of https://github.com/harryfrzz/superfi…
harryfrzz Oct 6, 2025
a2757f8
fix: layout & text-align issues
harryfrzz Oct 6, 2025
ce1c2b0
fix: UI whitespace issue
harryfrzz Oct 6, 2025
10e0461
fix: layout issue
harryfrzz Oct 6, 2025
0309dab
chore: refactor code
harryfrzz Oct 6, 2025
22e881f
refactor: clean up comments and improve code readability in bulk rena…
harryfrzz Oct 6, 2025
d73bea5
chore: refactor codebase
harryfrzz Oct 6, 2025
59ded81
codescene fix
harryfrzz Oct 6, 2025
426406e
code quality fix
harryfrzz Oct 6, 2025
03554c6
complexity fix
harryfrzz Oct 6, 2025
ff604c5
bug-fix
harryfrzz Oct 6, 2025
8ed4eea
bug-fix
harryfrzz Oct 6, 2025
6c9431d
refactor: introduce modalStateChecker for improved modal management
harryfrzz Oct 6, 2025
ae0969b
refactor: enhance modal state management with primary and secondary c…
harryfrzz Oct 6, 2025
dd804b1
code health fixes
harryfrzz Oct 6, 2025
267c9bb
Merge branch 'yorukot:main' into feature_branch
harryfrzz Oct 10, 2025
3d8d060
fix: update text-based cursor to common Icon based cursor
harryfrzz Oct 10, 2025
8e4726a
fix: Changed preview file alignment, hotkey configuration
harryfrzz Oct 10, 2025
4fad81d
Merge branch 'yorukot:main' into feature_branch
harryfrzz Oct 14, 2025
5f20c36
chore: refactor codebase, stable UI design & other fixes
harryfrzz Oct 15, 2025
7716a94
refactor: code complexity & hotkey fix
harryfrzz Oct 15, 2025
ac3a147
chore: refactor codebase to remove `slop` & code duplication, added v…
harryfrzz Oct 21, 2025
8df3fd5
chore: refactor codebase to remove slop & code duplication, added vim…
harryfrzz Oct 21, 2025
cd17f2e
Fix formatting of rename hotkey entry
harryfrzz Oct 21, 2025
82428e9
chore: refractor unit testing file
harryfrzz Oct 22, 2025
a9c35ec
Merge branch 'feature_branch' of https://github.com/harryfrzz/superfi…
harryfrzz Oct 22, 2025
0cb37cf
chore: fixed tests issues, multiple bools in structs, UI width issues…
harryfrzz Nov 1, 2025
78901fd
chore: fixed performance issues when renaming 1000-10k files, fixed m…
harryfrzz Nov 1, 2025
711f4e3
chore: fixed modal width & height issue
harryfrzz Nov 1, 2025
aa69539
chore: migrated navigation & types to new file
harryfrzz Nov 15, 2025
acd9890
chore: refractor code to fix `gofmt` issue, hardcoded values changed …
harryfrzz Nov 15, 2025
cb96f94
fix: Move common implementation to common.ResolveEditor
lazysegtree Nov 16, 2025
5047d30
add hotkey
lazysegtree Dec 7, 2025
f021c51
fix: reuse bulk rename text inputs
lazysegtree Dec 7, 2025
abba9ef
comment fix 1
lazysegtree Dec 7, 2025
9f70b38
remove temp files
lazysegtree Dec 7, 2025
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
5 changes: 5 additions & 0 deletions src/internal/common/config_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ type HotkeysType struct {
PageUp []string `toml:"page_up"`
PageDown []string `toml:"page_down"`

// bulk rename
BulkRename []string `toml:"bulk_rename"`
NavBulkRename []string `toml:"nav_bulk_rename"`
RevNavBulkRename []string `toml:"rev_nav_bulk_rename"`

CloseFilePanel []string `toml:"close_file_panel" comment:"file panel control"`
CreateNewFilePanel []string `toml:"create_new_file_panel"`
NextFilePanel []string `toml:"next_file_panel"`
Expand Down
23 changes: 23 additions & 0 deletions src/internal/common/config_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package common

import (
"os"
"runtime"

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

// ResolveEditor returns the command used to open files in an editor.
// Priority: Config.Editor → $EDITOR → OS fallback (non-empty result guaranteed).
func ResolveEditor() string {
if Config.Editor != "" {
return Config.Editor
}
if envEditor := os.Getenv("EDITOR"); envEditor != "" {
return envEditor
}
if runtime.GOOS == utils.OsWindows {
return "notepad"
}
return "nano"
}
15 changes: 15 additions & 0 deletions src/internal/common/style_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,18 @@ func GenerateFooterBorder(countString string, width int) string {
return strings.Repeat(Config.BorderBottom, repeatCount) + Config.BorderMiddleRight +
countString + Config.BorderMiddleLeft
}

func GenerateBulkRenameTextInput(placeholder string) textinput.Model {
ti := textinput.New()
ti.Cursor.Style = ModalStyle
ti.Cursor.TextStyle = ModalStyle
ti.PromptStyle = ModalStyle
ti.Prompt = ""
ti.TextStyle = ModalStyle
ti.Cursor.Blink = true
ti.Placeholder = placeholder
ti.PlaceholderStyle = ModalStyle
ti.CharLimit = 156
ti.Width = ModalWidth - 10
return ti
}
25 changes: 16 additions & 9 deletions src/internal/default_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

zoxidelib "github.com/lazysegtree/go-zoxide"

bulkrename "github.com/yorukot/superfile/src/internal/ui/bulk_rename"
"github.com/yorukot/superfile/src/internal/ui/metadata"
"github.com/yorukot/superfile/src/internal/ui/processbar"
"github.com/yorukot/superfile/src/internal/ui/sidebar"
Expand Down Expand Up @@ -32,15 +33,16 @@ func defaultModelConfig(toggleDotFile, toggleFooter, firstUse bool,
filePreview: preview.New(),
width: 10,
},
helpMenu: newHelpMenuModal(),
promptModal: prompt.DefaultModel(prompt.PromptMinHeight, prompt.PromptMinWidth),
zoxideModal: zoxideui.DefaultModel(zoxideui.ZoxideMinHeight, zoxideui.ZoxideMinWidth, zClient),
zClient: zClient,
modelQuitState: notQuitting,
toggleDotFile: toggleDotFile,
toggleFooter: toggleFooter,
firstUse: firstUse,
hasTrash: common.InitTrash(),
helpMenu: newHelpMenuModal(),
promptModal: prompt.DefaultModel(prompt.PromptMinHeight, prompt.PromptMinWidth),
zoxideModal: zoxideui.DefaultModel(zoxideui.ZoxideMinHeight, zoxideui.ZoxideMinWidth, zClient),
bulkRenameModel: bulkrename.DefaultModel(bulkrename.DefaultHeight, bulkrename.DefaultWidth),
zClient: zClient,
modelQuitState: notQuitting,
toggleDotFile: toggleDotFile,
toggleFooter: toggleFooter,
firstUse: firstUse,
hasTrash: common.InitTrash(),
}
}

Expand Down Expand Up @@ -222,6 +224,11 @@ func getHelpMenuData() []helpMenuModalData { //nolint: funlen // This should be
description: "Rename file or folder",
hotkeyWorkType: globalType,
},
{
hotkey: common.Hotkeys.BulkRename,
description: "Open bulk rename modal",
hotkeyWorkType: globalType,
},
{
hotkey: common.Hotkeys.CopyItems,
description: "Copy selected items to the clipboard",
Expand Down
25 changes: 12 additions & 13 deletions src/internal/handle_file_operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ func (m *model) panelItemRename() {
panel.rename = common.GenerateRenameTextInput(m.fileModel.width-4, cursorPos, panel.element[panel.cursor].name)
}

func (m *model) panelBulkRename() {
panel := &m.fileModel.filePanels[m.filePanelFocusIndex]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

M2 - use m.getFocusedFilePanel() Dont access slice directly


if panel.panelMode != selectMode || len(panel.selected) == 0 {
return
}

m.bulkRenameModel.Open(panel.selected, panel.location)
m.firstTextInput = true
}

func (m *model) getDeleteCmd(permDelete bool) tea.Cmd {
panel := m.getFocusedFilePanel()
if len(panel.element) == 0 {
Expand Down Expand Up @@ -444,19 +455,7 @@ func (m *model) openFileWithEditor() tea.Cmd {
slog.Error("Error while writing to chooser file, continuing with open via file editor", "error", err)
}

editor := common.Config.Editor
if editor == "" {
editor = os.Getenv("EDITOR")
}

// Make sure there is an editor
if editor == "" {
if runtime.GOOS == utils.OsWindows {
editor = "notepad"
} else {
editor = "nano"
}
}
editor := common.ResolveEditor()

// Split the editor command into command and arguments
parts := strings.Fields(editor)
Expand Down
97 changes: 67 additions & 30 deletions src/internal/key_function.go
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

M1 - Need to check if the refactoring broke anything

Original file line number Diff line number Diff line change
Expand Up @@ -130,42 +130,71 @@ func (m *model) mainKey(msg string) tea.Cmd { //nolint: gocyclo,cyclop,funlen //
}

func (m *model) normalAndBrowserModeKey(msg string) tea.Cmd {
// if not focus on the filepanel return

This comment was marked as spam.

if !m.getFocusedFilePanel().isFocused {
if m.focusPanel == sidebarFocus && slices.Contains(common.Hotkeys.Confirm, msg) {
m.sidebarSelectDirectory()
}
if m.focusPanel == sidebarFocus && slices.Contains(common.Hotkeys.FilePanelItemRename, msg) {
m.sidebarModel.PinnedItemRename()
}
if m.focusPanel == sidebarFocus && slices.Contains(common.Hotkeys.SearchBar, msg) {
m.sidebarSearchBarFocus()
}
return nil
return m.handleSidebarFocusKeys(msg)
}
// Check if in the select mode and focusOn filepanel

if m.getFocusedFilePanel().panelMode == selectMode {
switch {
case slices.Contains(common.Hotkeys.Confirm, msg):
m.fileModel.filePanels[m.filePanelFocusIndex].singleItemSelect()
case slices.Contains(common.Hotkeys.FilePanelSelectModeItemsSelectUp, msg):
m.fileModel.filePanels[m.filePanelFocusIndex].itemSelectUp(m.mainPanelHeight)
case slices.Contains(common.Hotkeys.FilePanelSelectModeItemsSelectDown, msg):
m.fileModel.filePanels[m.filePanelFocusIndex].itemSelectDown(m.mainPanelHeight)
case slices.Contains(common.Hotkeys.DeleteItems, msg):
return m.getDeleteTriggerCmd(false)
case slices.Contains(common.Hotkeys.PermanentlyDeleteItems, msg):
return m.getDeleteTriggerCmd(true)
case slices.Contains(common.Hotkeys.CopyItems, msg):
m.copyMultipleItem(false)
case slices.Contains(common.Hotkeys.CutItems, msg):
m.copyMultipleItem(true)
case slices.Contains(common.Hotkeys.FilePanelSelectAllItem, msg):
m.selectAllItem()
}
return m.handleSelectModeKeys(msg)
}

return m.handleBrowserModeKeys(msg)
}

func (m *model) handleSidebarFocusKeys(msg string) tea.Cmd {
if m.focusPanel != sidebarFocus {
return nil
}

switch {
case slices.Contains(common.Hotkeys.Confirm, msg):
m.sidebarSelectDirectory()
case slices.Contains(common.Hotkeys.FilePanelItemRename, msg):
m.sidebarModel.PinnedItemRename()
case slices.Contains(common.Hotkeys.SearchBar, msg):
m.sidebarSearchBarFocus()
}
return nil
}

func (m *model) handleSelectModeKeys(msg string) tea.Cmd {
if slices.Contains(common.Hotkeys.Confirm, msg) {
m.fileModel.filePanels[m.filePanelFocusIndex].singleItemSelect()
return nil
}
if slices.Contains(common.Hotkeys.FilePanelSelectModeItemsSelectUp, msg) {
m.fileModel.filePanels[m.filePanelFocusIndex].itemSelectUp(m.mainPanelHeight)
return nil
}
if slices.Contains(common.Hotkeys.FilePanelSelectModeItemsSelectDown, msg) {
m.fileModel.filePanels[m.filePanelFocusIndex].itemSelectDown(m.mainPanelHeight)
return nil
}
if slices.Contains(common.Hotkeys.DeleteItems, msg) {
return m.getDeleteTriggerCmd(false)
}
if slices.Contains(common.Hotkeys.PermanentlyDeleteItems, msg) {
return m.getDeleteTriggerCmd(true)
}

return m.handleSelectModeFileOperations(msg)
}

func (m *model) handleSelectModeFileOperations(msg string) tea.Cmd {
switch {
case slices.Contains(common.Hotkeys.CopyItems, msg):
m.copyMultipleItem(false)
case slices.Contains(common.Hotkeys.CutItems, msg):
m.copyMultipleItem(true)
case slices.Contains(common.Hotkeys.BulkRename, msg):
m.panelBulkRename()
case slices.Contains(common.Hotkeys.FilePanelSelectAllItem, msg):
m.selectAllItem()
}
return nil
}

func (m *model) handleBrowserModeKeys(msg string) tea.Cmd {
switch {
case slices.Contains(common.Hotkeys.Confirm, msg):
m.enterPanel()
Expand All @@ -175,6 +204,14 @@ func (m *model) normalAndBrowserModeKey(msg string) tea.Cmd {
return m.getDeleteTriggerCmd(false)
case slices.Contains(common.Hotkeys.PermanentlyDeleteItems, msg):
return m.getDeleteTriggerCmd(true)
default:
return m.handleBrowserModeFileOperations(msg)
}
return nil
}

func (m *model) handleBrowserModeFileOperations(msg string) tea.Cmd {
switch {
case slices.Contains(common.Hotkeys.CopyItems, msg):
m.copySingleItem(false)
case slices.Contains(common.Hotkeys.CutItems, msg):
Expand Down
Loading