Skip to content
Draft
Show file tree
Hide file tree
Changes from 10 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
169 changes: 169 additions & 0 deletions src/internal/bulk_rename.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package internal
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not being able to see 19/22 files is not a good experience. There has to be some way for me to see the test.

Thats one of the reason why original approach is better.

image

Copy link
Collaborator

@lazysegtree lazysegtree Oct 13, 2025

Choose a reason for hiding this comment

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

This is a complex feature. Needs unit tests, including end to end test with the model

@coderabbitai reference unit tests and end to end tests for modals like prompt modal. And tell why unit test is important here.

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all test files in the repository
echo "=== Finding all test files ==="
fd -e go -e test . | grep -i test

echo -e "\n=== Searching for modal-related tests ==="
rg -n --type=go -C3 'func Test.*[Mm]odal' 

echo -e "\n=== Searching for prompt modal tests specifically ==="
rg -n --type=go -C3 'prompt.*[Mm]odal|[Pp]rompt.*[Tt]est'

echo -e "\n=== Searching for model end-to-end tests ==="
rg -n --type=go -C3 'func Test.*[Mm]odel|func Test.*[Ee]nd.*[Tt]o.*[Ee]nd'

echo -e "\n=== Looking for test files in internal directory ==="
fd -e go . src/internal | grep -i test

Length of output: 38765


🏁 Script executed:

#!/bin/bash
# Examine the prompt modal end-to-end test structure
echo "=== model_prompt_test.go structure ==="
cat src/internal/model_prompt_test.go | head -150

echo -e "\n=== prompt UI unit test structure ==="
cat src/internal/ui/prompt/model_test.go | head -120

echo -e "\n=== Test utilities ==="
cat src/internal/test_utils.go | head -100

Length of output: 12170


@lazysegtree, excellent point about the testing needs. Looking at the codebase, I can see that similar modal features like the prompt modal have comprehensive test coverage. Let me reference those and explain why testing is crucial for this bulk rename feature.

Existing Modal Test Patterns

End-to-End Tests (model_prompt_test.go)

The prompt modal has comprehensive end-to-end tests that test the entire model with modal integration:

func TestModel_Update_Prompt(t *testing.T) {
    // Test complete user workflows
    m := defaultTestModel(dir1)
    
    // 1. Opening the modal
    TeaUpdate(m, utils.TeaRuneKeyMsg(common.Hotkeys.OpenCommandLine[0]))
    assert.True(t, m.promptModal.IsOpen())
    
    // 2. Typing input
    TeaUpdate(m, utils.TeaRuneKeyMsg("mkdir test_dir"))
    
    // 3. Confirming action
    TeaUpdate(m, tea.KeyMsg{Type: tea.KeyEnter})
    
    // 4. Verifying results
    assert.True(t, m.promptModal.LastActionSucceeded())
    assert.DirExists(t, filepath.Join(dir1, "test_dir"))
}

Unit Tests (ui/prompt/model_test.go)

The prompt UI component also has isolated unit tests:

func TestModel_HandleUpdate(t *testing.T) {
    t.Run("Handle update called on closed Model", func(t *testing.T) {
        m := defaultTestModel()
        action, _ := m.HandleUpdate(utils.TeaRuneKeyMsg("x"), defaultTestCwd)
        assert.False(t, m.IsOpen())
        assert.Equal(t, common.NoAction{}, action)
    })
    
    t.Run("Validate Prompt Actions", func(t *testing.T) {
        // Tests specific action handling in isolation
    })
}

Why Unit Tests are Critical for Bulk Rename

The bulk rename feature has significant complexity that makes comprehensive testing essential:

1. Complex Transformation Logic

// Multiple transformation types that need individual testing:
- Find/Replace with string matching
- Prefix/Suffix addition
- Numbering with start values
- Case conversion (lower, upper, title)

Each transformation needs tests for:

  • Valid inputs
  • Empty strings
  • Special characters
  • Unicode handling
  • Edge cases like zero-length filenames

2. State Management Complexity

type bulkRenameModal struct {
    open        bool
    cursor      int
    renameType  int  // 5 different types
    findInput   textinput.Model
    replaceInput textinput.Model
    prefixInput textinput.Model
    suffixInput textinput.Model
    startNumber int
    caseType    int
    previews    []renamePreview
    errorMsg    string
}

Tests should verify:

  • State transitions between rename types
  • Input focus management
  • Preview generation updates
  • Error state handling

3. Validation and Conflict Detection

The validation logic in validateRename and checkRenameConflict needs thorough testing:

// Must test:Empty new names rejectedUnchanged names rejectedFile conflicts detectedPermission errors handledCross-platform path validation

4. Preview Generation Accuracy

Each item gets a preview that users rely on:

// Tests should verify:
- Preview matches actual rename result
- Error states shown correctly per item
- Validation errors displayed properly
- Multiple selections handled correctly

5. End-to-End User Workflows

Following the pattern from model_prompt_test.go, you need tests like:

func TestModel_Update_BulkRename(t *testing.T) {
    t.Run("Find and Replace workflow", func(t *testing.T) {
        m := setupModelWithFiles(t, "file1.txt", "file2.txt", "document.txt")
        
        // Select files
        setupPanelModeAndSelection(t, m, true, "", []string{"file1.txt", "file2.txt"})
        
        // Open bulk rename modal
        TeaUpdate(m, utils.TeaRuneKeyMsg(common.Hotkeys.BulkRename[0]))
        assert.True(t, m.bulkRenameModal.open)
        
        // Enter find text
        TeaUpdate(m, utils.TeaRuneKeyMsg("file"))
        
        // Navigate to replace field
        TeaUpdate(m, tea.KeyMsg{Type: tea.KeyTab})
        
        // Enter replace text
        TeaUpdate(m, utils.TeaRuneKeyMsg("doc"))
        
        // Verify preview
        assert.Len(t, m.bulkRenameModal.previews, 2)
        assert.Equal(t, "doc1.txt", m.bulkRenameModal.previews[0].newName)
        
        // Confirm rename
        TeaUpdate(m, tea.KeyMsg{Type: tea.KeyEnter})
        
        // Verify files renamed
        assert.FileExists(t, filepath.Join(dir, "doc1.txt"))
        assert.FileExists(t, filepath.Join(dir, "doc2.txt"))
        assert.NoFileExists(t, filepath.Join(dir, "file1.txt"))
    })
    
    t.Run("Conflict detection", func(t *testing.T) {
        // Test that conflicts are detected and prevented
    })
    
    t.Run("Cancel without changes", func(t *testing.T) {
        // Test cancellation leaves files unchanged
    })
}

Recommended Test Coverage

Based on the complexity, I recommend:

  1. Unit tests for bulk_rename.go:

    • Test each transformation function (applyFindReplace, applyPrefix, applyNumbering, etc.)
    • Test validation logic (validateRename, checkRenameConflict)
    • Test preview generation (generateBulkRenamePreview)
  2. End-to-end tests in model_test.go or a new model_bulk_rename_test.go:

    • Test complete user workflows for each rename type
    • Test navigation between inputs
    • Test modal opening/closing
    • Test cancellation behavior
    • Test error handling and recovery
  3. Edge case tests:

    • Empty selections
    • Single file vs. multiple files
    • Special characters in filenames
    • File conflicts
    • Permission errors
    • Very long filenames
    • Unicode characters

Why This Matters

Without comprehensive tests, you risk:

  • Silent bugs: Transformation logic that produces unexpected results
  • Regressions: Future changes breaking existing functionality
  • Poor UX: Users losing trust after file renaming errors
  • Maintenance burden: Hard to refactor without confidence
  • Hidden edge cases: Special characters or conflicts causing failures in production

The test infrastructure already exists in the codebase with helpers like TeaUpdate, defaultTestModel, and setupPanelModeAndSelection. Following the prompt modal's testing pattern will give you confidence that this complex feature works correctly across all scenarios.


🧠 Learnings used
Learnt from: lazysegtree
PR: yorukot/superfile#950
File: src/internal/ui/metadata/README.md:7-7
Timestamp: 2025-07-24T03:46:29.516Z
Learning: lazysegtree prefers to defer comprehensive unit testing to separate PRs when the current PR has grown too large, maintaining focus on the primary refactoring objectives while tracking testing requirements in dedicated GitHub issues.

Learnt from: lazysegtree
PR: yorukot/superfile#969
File: src/internal/key_function.go:40-40
Timestamp: 2025-08-03T09:34:55.721Z
Learning: lazysegtree emphasizes proper dependency direction in software architecture, preferring that low-level components (like modal handlers) should not depend on high-level components (like the main model object). He also prioritizes performance considerations, noting that creating objects on every keypress in hot code paths like key handling is inefficient and should be avoided.

Learnt from: lazysegtree
PR: yorukot/superfile#963
File: src/internal/default_config.go:16-16
Timestamp: 2025-07-27T07:40:51.938Z
Learning: lazysegtree prefers simpler implementation approaches when the alternatives are significantly more complex, even if the alternatives might be architecturally cleaner, prioritizing maintainability and avoiding over-engineering.

Learnt from: lazysegtree
PR: yorukot/superfile#963
File: src/internal/default_config.go:16-16
Timestamp: 2025-07-27T07:40:51.938Z
Learning: lazysegtree prefers simpler implementation approaches when the alternatives are significantly more complex, even if the alternatives might be architecturally cleaner, prioritizing maintainability and avoiding over-engineering.

Learnt from: lazysegtree
PR: yorukot/superfile#1013
File: src/internal/model_render.go:329-341
Timestamp: 2025-08-29T14:11:21.380Z
Learning: lazysegtree prefers to defer help menu rendering optimizations and other technical debt improvements to separate GitHub issues when the current PR scope has grown too large, maintaining focus on the primary refactoring objectives while tracking performance improvements for future work.

Learnt from: lazysegtree
PR: yorukot/superfile#985
File: src/internal/model.go:0-0
Timestamp: 2025-08-11T01:49:30.040Z
Learning: lazysegtree prefers maintaining code correctness through proper design and invariants rather than adding defensive bounds checks at every slice access point, viewing such defensive programming as "duct taping" that can mask actual bugs instead of fixing them at their source.

Learnt from: lazysegtree
PR: yorukot/superfile#973
File: src/internal/ui/processbar/model_update.go:7-27
Timestamp: 2025-08-03T14:49:31.221Z
Learning: lazysegtree prefers to keep test-only code simple without adding production-level concerns like goroutine synchronization, cancellation contexts, or complex lifecycle management, even when such patterns might prevent potential issues, since the complexity isn't justified for test utilities.

Learnt from: lazysegtree
PR: yorukot/superfile#1044
File: src/internal/utils/file_utils.go:149-149
Timestamp: 2025-09-09T13:29:11.771Z
Learning: lazysegtree prefers to keep PR scope focused on the primary objectives and considers pre-existing technical debt issues as out of scope for migration/refactoring PRs, preferring to defer such issues to separate GitHub issues rather than expanding the current PR scope.

Learnt from: lazysegtree
PR: yorukot/superfile#967
File: src/internal/key_function.go:45-47
Timestamp: 2025-08-02T11:47:07.713Z
Learning: lazysegtree prefers to track technical debt and architectural improvements in dedicated GitHub issues when they are identified during PR reviews but are beyond the scope of the current PR, particularly for complex refactoring needs like input handling architecture that would require significant changes.

Learnt from: lazysegtree
PR: yorukot/superfile#963
File: src/internal/handle_file_operations.go:567-570
Timestamp: 2025-07-27T08:49:09.687Z
Learning: lazysegtree prefers to defer technical debt issues like model mutation concerns to later PRs when the current PR has already grown too large, maintaining focus on the primary objectives while acknowledging the need to track such issues for future work.


import (
"os"
"path/filepath"
"strconv"
"strings"

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

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

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

m.bulkRenameModal.open = true
m.bulkRenameModal.renameType = 0
m.bulkRenameModal.cursor = 0
m.bulkRenameModal.startNumber = 1
m.bulkRenameModal.caseType = 0
m.bulkRenameModal.errorMessage = ""
m.firstTextInput = true

m.bulkRenameModal.findInput = common.GenerateBulkRenameTextInput("Find text")
m.bulkRenameModal.replaceInput = common.GenerateBulkRenameTextInput("Replace with")
m.bulkRenameModal.prefixInput = common.GenerateBulkRenameTextInput("Add prefix")
m.bulkRenameModal.suffixInput = common.GenerateBulkRenameTextInput("Add suffix")
}

func (m *model) generateBulkRenamePreview() []renamePreview {
panel := &m.fileModel.filePanels[m.filePanelFocusIndex]
previews := make([]renamePreview, 0, len(panel.selected))

for i, itemPath := range panel.selected {
preview := m.createRenamePreview(itemPath, i)
previews = append(previews, preview)
}

return previews
}

func (m *model) createRenamePreview(itemPath string, index int) renamePreview {
oldName := filepath.Base(itemPath)
newName := m.applyBulkRenameTransformation(oldName, index)

validation := renameValidation{
oldName: oldName,
newName: newName,
itemPath: itemPath,
}

return renamePreview{
oldName: oldName,
newName: newName,
error: validateRename(validation),
}
}

func (m *model) applyBulkRenameTransformation(oldName string, index int) string {
modal := &m.bulkRenameModal

transformers := map[int]func() string{
0: func() string { return applyFindReplace(oldName, modal.findInput.Value(), modal.replaceInput.Value()) },
1: func() string { return applyPrefix(oldName, modal.prefixInput.Value()) },
2: func() string { return applySuffix(oldName, modal.suffixInput.Value()) },
3: func() string { return applyNumbering(oldName, modal.startNumber+index) },
4: func() string { return applyCaseConversion(oldName, modal.caseType) },
}

if transformer, exists := transformers[modal.renameType]; exists {
return transformer()
}
return oldName
}

func validateRename(v renameValidation) string {
if v.newName == "" {
return "Empty filename"
}
if v.newName == v.oldName {
return "No change"
}
return checkRenameConflict(v)
}

func checkRenameConflict(v renameValidation) string {
newPath := filepath.Join(filepath.Dir(v.itemPath), v.newName)
if _, statErr := os.Stat(newPath); statErr == nil && newPath != v.itemPath {
return "File already exists"
}
return ""
}

type renameTransformer struct {
find string
replace string
prefix string
suffix string
number int
caseOp caseOperation
}

type caseOperation int

const (
toLower caseOperation = iota
toUpper
toTitle
)

func applyFindReplace(filename, find, replace string) string {
if find == "" {
return filename
}
return strings.ReplaceAll(filename, find, replace)
}

func applyPrefix(filename, prefix string) string {
if prefix == "" {
return filename
}
ext := filepath.Ext(filename)
nameWithoutExt := strings.TrimSuffix(filename, ext)
return prefix + nameWithoutExt + ext
}

func applySuffix(filename, suffix string) string {
if suffix == "" {
return filename
}
ext := filepath.Ext(filename)
nameWithoutExt := strings.TrimSuffix(filename, ext)
return nameWithoutExt + suffix + ext
}

func applyNumbering(filename string, number int) string {
ext := filepath.Ext(filename)
nameWithoutExt := strings.TrimSuffix(filename, ext)
return nameWithoutExt + "_" + strconv.Itoa(number) + ext
}

func applyCaseConversion(filename string, caseType int) string {
ext := filepath.Ext(filename)
nameWithoutExt := strings.TrimSuffix(filename, ext)

converters := map[int]func(string) string{
0: strings.ToLower,
1: strings.ToUpper,
2: toTitleCase,
}

if converter, exists := converters[caseType]; exists {
return converter(nameWithoutExt) + ext
}
return filename
}

func toTitleCase(text string) string {
words := strings.Fields(strings.ToLower(text))
for i, word := range words {
if len(word) > 0 {
words[i] = strings.ToUpper(string(word[0])) + word[1:]
}
}
return strings.Join(words, " ")
}
1 change: 1 addition & 0 deletions src/internal/common/config_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ type HotkeysType struct {

FilePanelItemCreate []string `toml:"file_panel_item_create" comment:"create file/directory and rename "`
FilePanelItemRename []string `toml:"file_panel_item_rename"`
BulkRename []string `toml:"bulk_rename"`

CopyItems []string `toml:"copy_items" comment:"file operate"`
PasteItems []string `toml:"paste_items"`
Expand Down
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 = 80 - 12
return ti
}
5 changes: 2 additions & 3 deletions src/internal/handle_file_operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,6 @@ func (m *model) warnModalForRenaming() tea.Cmd {
return res
}

// Rename file where the cusror is located
// TODO: Fix this. It doesn't do any rename, just opens the rename text input
// Actual rename happens at confirmRename() in handle_modal.go
func (m *model) panelItemRename() {
panel := &m.fileModel.filePanels[m.filePanelFocusIndex]
if len(panel.element) == 0 {
Expand Down Expand Up @@ -529,3 +526,5 @@ func (m *model) copyPWD() {
slog.Error("Error while copy present working directory", "error", err)
}
}


136 changes: 136 additions & 0 deletions src/internal/handle_modal.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,142 @@ func (m *model) confirmRename() {
panel.renaming = false
}

func (m *model) cancelBulkRename() {
m.bulkRenameModal.open = false
m.bulkRenameModal.findInput.Blur()
m.bulkRenameModal.replaceInput.Blur()
m.bulkRenameModal.prefixInput.Blur()
m.bulkRenameModal.suffixInput.Blur()
m.bulkRenameModal.errorMessage = ""
m.bulkRenameModal.preview = nil
}

func (m *model) confirmBulkRename() {
panel := &m.fileModel.filePanels[m.filePanelFocusIndex]
previews := m.generateBulkRenamePreview()

if !hasValidRenames(previews) {
m.bulkRenameModal.errorMessage = "No valid renames to apply"
return
}

successCount, failCount := m.applyBulkRenames(panel, previews)
slog.Info("Bulk rename completed", "success", successCount, "failed", failCount)

m.cancelBulkRename()

if failCount == 0 {
panel.panelMode = browserMode
panel.selected = []string{}
}
}

func hasValidRenames(previews []renamePreview) bool {
for _, p := range previews {
if p.error == "" {
return true
}
}
return false
}

func (m *model) applyBulkRenames(panel *filePanel, previews []renamePreview) (int, int) {
successCount := 0
failCount := 0

for i, itemPath := range panel.selected {
if i >= len(previews) {
break
}

preview := previews[i]
if preview.error != "" {
failCount++
continue
}

success := m.performRename(itemPath, preview.newName, panel, i)
if success {
successCount++
} else {
failCount++
}
}

return successCount, failCount
}

func (m *model) performRename(itemPath, newName string, panel *filePanel, index int) bool {
newPath := filepath.Join(filepath.Dir(itemPath), newName)
err := os.Rename(itemPath, newPath)

if err != nil {
slog.Error("Error during bulk rename", "oldPath", itemPath, "newPath", newPath, "error", err)
return false
}

panel.selected[index] = newPath
return true
}

func (m *model) bulkRenameNextType() {
m.bulkRenameModal.renameType = (m.bulkRenameModal.renameType + 1) % 5
m.bulkRenameFocusInput()
}

func (m *model) bulkRenamePrevType() {
m.bulkRenameModal.renameType = (m.bulkRenameModal.renameType + 4) % 5
m.bulkRenameFocusInput()
}

func (m *model) bulkRenameNavigateUp() {
if m.bulkRenameModal.cursor > 0 {
m.bulkRenameModal.cursor--
}
m.bulkRenameFocusInput()
}

func (m *model) bulkRenameNavigateDown() {
maxCursor := 0
switch m.bulkRenameModal.renameType {
case 0:
maxCursor = 1
case 3:
maxCursor = 0
case 4:
maxCursor = 2
default:
maxCursor = 0
}

if m.bulkRenameModal.cursor < maxCursor {
m.bulkRenameModal.cursor++
}
m.bulkRenameFocusInput()
}

func (m *model) bulkRenameFocusInput() {

m.bulkRenameModal.findInput.Blur()
m.bulkRenameModal.replaceInput.Blur()
m.bulkRenameModal.prefixInput.Blur()
m.bulkRenameModal.suffixInput.Blur()

switch m.bulkRenameModal.renameType {
case 0:
if m.bulkRenameModal.cursor == 0 {
m.bulkRenameModal.findInput.Focus()
} else {
m.bulkRenameModal.replaceInput.Focus()
}
case 1:
m.bulkRenameModal.prefixInput.Focus()
case 2:
m.bulkRenameModal.suffixInput.Focus()

}
}

func (m *model) openSortOptionsMenu() {
panel := &m.fileModel.filePanels[m.filePanelFocusIndex]
panel.sortOptions.open = true
Expand Down
Loading