@@ -30,11 +30,12 @@ type fbTracksResolvedMsg struct {
3030
3131// openFileBrowser initialises and shows the file browser overlay.
3232func (m * Model ) openFileBrowser () {
33- home , err := os .UserHomeDir ()
34- if err != nil {
35- home = "/"
33+ if m .fileBrowser .dir == "" {
34+ m .fileBrowser .dir , _ = os .UserHomeDir ()
35+ if m .fileBrowser .dir == "" {
36+ m .fileBrowser .dir = "/"
37+ }
3638 }
37- m .fileBrowser .dir = home
3839 m .fileBrowser .cursor = 0
3940 m .fileBrowser .selected = make (map [string ]bool )
4041 m .fileBrowser .err = ""
@@ -45,7 +46,14 @@ func (m *Model) openFileBrowser() {
4546// loadFBDir reads the current directory and populates fbEntries.
4647func (m * Model ) loadFBDir () {
4748 m .fileBrowser .err = ""
48- m .fileBrowser .entries = nil
49+ m .fileBrowser .cursor = 0
50+
51+ // Reuse internal memory buffer of m.fileBrowser.entries.
52+ m .fileBrowser .entries = m .fileBrowser .entries [:0 ]
53+ if cap (m .fileBrowser .entries ) > 512 {
54+ // Previous directory list was too large, do not retain memory, re-allocate buffer.
55+ m .fileBrowser .entries = nil
56+ }
4957
5058 // Always provide a parent entry for navigating up.
5159 m .fileBrowser .entries = append (m .fileBrowser .entries , fbEntry {
@@ -55,40 +63,55 @@ func (m *Model) loadFBDir() {
5563 isParent : true ,
5664 })
5765
66+ // Get entries sorted by name, dirs and files mixed
5867 entries , err := os .ReadDir (m .fileBrowser .dir )
5968 if err != nil {
6069 m .fileBrowser .err = err .Error ()
61- m .fileBrowser .cursor = 0
6270 return
6371 }
6472
65- // Separate dirs and files, skip dotfiles.
66- var dirs , files []fbEntry
73+ // Add directories to m.fileBrowser.entries (reuse internal memory),
74+ // add files to files, then append all files to m.fileBrowser.entries, skip dotfiles.
75+ var files []fbEntry
6776 for _ , e := range entries {
6877 name := e .Name ()
6978 if strings .HasPrefix (name , "." ) {
7079 continue
7180 }
72- full := filepath .Join (m .fileBrowser .dir , name )
81+ // Detect directories and directory-like entries.
82+ dirType := "" // Name suffix for directories and some non-regular file types.
7383 if e .IsDir () {
74- dirs = append (dirs , fbEntry {
75- name : name + "/" ,
76- path : full ,
84+ dirType = "/"
85+ } else if ! e .Type ().IsRegular () {
86+ if e .Type ()& os .ModeSymlink != 0 && ! player .SupportedExts [strings .ToLower (filepath .Ext (name ))] {
87+ // Treat symlink as a directory unless it points to media file.
88+ // os.DirEntry has no option to test the type of object symlink points to.
89+ dirType = "@"
90+ } else if os .PathSeparator == '\\' && e .Type ()& os .ModeIrregular != 0 {
91+ // Try to support directory junctions on Windows (mklink /J).
92+ // Go do not support such files, it treats them as os.ModeIrregular (?---------).
93+ dirType = "?"
94+ }
95+ }
96+ // Add entry to m.fileBrowser.entries or to files slice
97+ if dirType != "" {
98+ m .fileBrowser .entries = append (m .fileBrowser .entries , fbEntry {
99+ name : name + dirType ,
100+ path : filepath .Join (m .fileBrowser .dir , name ),
77101 isDir : true ,
78102 })
79103 } else {
80- ext := strings .ToLower (filepath .Ext (name ))
104+ if files == nil {
105+ files = make ([]fbEntry , 0 , 16 ) // Avoid reallocations
106+ }
81107 files = append (files , fbEntry {
82108 name : name ,
83- path : full ,
84- isAudio : player .SupportedExts [ext ],
109+ path : filepath . Join ( m . fileBrowser . dir , name ) ,
110+ isAudio : player .SupportedExts [strings . ToLower ( filepath . Ext ( name )) ],
85111 })
86112 }
87113 }
88-
89- m .fileBrowser .entries = append (m .fileBrowser .entries , dirs ... )
90114 m .fileBrowser .entries = append (m .fileBrowser .entries , files ... )
91- m .fileBrowser .cursor = 0
92115}
93116
94117// handleFileBrowserKey processes key presses while the file browser is open.
@@ -98,9 +121,8 @@ func (m *Model) handleFileBrowserKey(msg tea.KeyMsg) tea.Cmd {
98121 m .fileBrowser .visible = false
99122 return m .quit ()
100123
101- case "esc" , "o" :
124+ case "esc" , "o" , "q" :
102125 m .fileBrowser .visible = false
103- return nil
104126
105127 case "up" , "k" :
106128 if m .fileBrowser .cursor > 0 {
@@ -145,6 +167,20 @@ func (m *Model) handleFileBrowserKey(msg tea.KeyMsg) tea.Cmd {
145167 m .fileBrowser .dir = filepath .Dir (m .fileBrowser .dir )
146168 m .loadFBDir ()
147169
170+ case "~" :
171+ cd , _ := os .UserHomeDir ()
172+ if cd != "" && m .fileBrowser .dir != cd {
173+ m .fileBrowser .dir = cd
174+ m .loadFBDir ()
175+ }
176+
177+ case "." :
178+ cd , _ := os .Getwd ()
179+ if cd != "" && m .fileBrowser .dir != cd {
180+ m .fileBrowser .dir = cd
181+ m .loadFBDir ()
182+ }
183+
148184 case " " :
149185 if m .fileBrowser .cursor < len (m .fileBrowser .entries ) {
150186 e := m .fileBrowser .entries [m .fileBrowser .cursor ]
@@ -155,6 +191,9 @@ func (m *Model) handleFileBrowserKey(msg tea.KeyMsg) tea.Cmd {
155191 m .fileBrowser .selected [e .path ] = true
156192 }
157193 }
194+ if m .fileBrowser .cursor < len (m .fileBrowser .entries )- 1 {
195+ m .fileBrowser .cursor ++
196+ }
158197 }
159198
160199 case "a" :
@@ -284,7 +323,9 @@ func (m Model) renderFileBrowser() string {
284323 lines = append (lines , "" )
285324 }
286325
287- help := helpKey ("↑↓" , "Navigate " ) + helpKey ("Enter" , "Open " ) + helpKey ("Spc" , "Select " ) + helpKey ("a" , "All " ) + helpKey ("←" , "Back " )
326+ help := helpKey ("↑↓" , "Scroll " ) + helpKey ("Enter" , "Open " ) +
327+ helpKey ("Spc" , "Select " ) + helpKey ("a" , "All " ) +
328+ helpKey ("←" , "Back " ) + helpKey ("~." , "Home/Cwd " )
288329 if len (m .fileBrowser .selected ) > 0 {
289330 help += helpKey ("R" , "Replace " )
290331 }
0 commit comments