Skip to content

Commit 575155b

Browse files
authored
fix: Remove repeated os.ReadDir calls (#1147)
1 parent 8001c08 commit 575155b

File tree

4 files changed

+68
-74
lines changed

4 files changed

+68
-74
lines changed

src/internal/function.go

Lines changed: 49 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -125,97 +125,106 @@ func returnDirElementBySearchString(location string, displayDotFile bool, search
125125
}
126126

127127
func sortFileElement(sortOptions sortOptionsModelData, dirEntries []os.DirEntry, location string) []element {
128-
// Sort files
129-
sort.Slice(dirEntries, getOrderingFunc(location, dirEntries,
130-
sortOptions.reversed, sortOptions.options[sortOptions.selected]))
131-
// Preallocate for efficiency
132-
directoryElement := make([]element, 0, len(dirEntries))
128+
elements := make([]element, 0, len(dirEntries))
133129
for _, item := range dirEntries {
134-
directoryElement = append(directoryElement, element{
130+
info, err := item.Info()
131+
if err != nil {
132+
slog.Error("Error while retrieving file info during sort",
133+
"error", err, "path", filepath.Join(location, item.Name()))
134+
continue
135+
}
136+
137+
elements = append(elements, element{
135138
name: item.Name(),
136-
directory: item.IsDir(),
139+
directory: item.IsDir() || isSymlinkToDir(location, info, item.Name()),
137140
location: filepath.Join(location, item.Name()),
141+
info: info,
138142
})
139143
}
140-
return directoryElement
144+
145+
sort.Slice(elements, getOrderingFunc(elements,
146+
sortOptions.reversed, sortOptions.options[sortOptions.selected]))
147+
148+
return elements
149+
}
150+
151+
// Symlinks to directories are to be identified as directories
152+
func isSymlinkToDir(location string, info os.FileInfo, name string) bool {
153+
if info.Mode()&os.ModeSymlink != 0 {
154+
targetInfo, errStat := os.Stat(filepath.Join(location, name))
155+
return errStat == nil && targetInfo.IsDir()
156+
}
157+
return false
141158
}
142159

143-
func getOrderingFunc(location string, dirEntries []os.DirEntry, reversed bool, sortOption string) sliceOrderFunc {
160+
func getOrderingFunc(elements []element, reversed bool, sortOption string) sliceOrderFunc {
144161
var order func(i, j int) bool
145162
switch sortOption {
146163
case string(sortingName):
147164
order = func(i, j int) bool {
148165
// One of them is a directory, and other is not
149-
if dirEntries[i].IsDir() != dirEntries[j].IsDir() {
150-
return dirEntries[i].IsDir()
166+
if elements[i].directory != elements[j].directory {
167+
return elements[i].directory
151168
}
152169
if common.Config.CaseSensitiveSort {
153-
return dirEntries[i].Name() < dirEntries[j].Name() != reversed
170+
return elements[i].name < elements[j].name != reversed
154171
}
155-
return strings.ToLower(dirEntries[i].Name()) < strings.ToLower(dirEntries[j].Name()) != reversed
172+
return strings.ToLower(elements[i].name) < strings.ToLower(elements[j].name) != reversed
156173
}
157174
case string(sortingSize):
158-
order = getSizeOrderingFunc(dirEntries, reversed, location)
175+
order = getSizeOrderingFunc(elements, reversed)
159176
case string(sortingDateModified):
160177
order = func(i, j int) bool {
161-
// No need for err check, we already filtered out dirEntries with err != nil in Info() call
162-
fileInfoI, _ := dirEntries[i].Info()
163-
fileInfoJ, _ := dirEntries[j].Info()
164-
// Note : If ModTime matches, the comparator returns false both ways; order becomes non-deterministic
165-
// TODO: Fix this
166-
return fileInfoI.ModTime().After(fileInfoJ.ModTime()) != reversed
178+
return elements[i].info.ModTime().After(elements[j].info.ModTime()) != reversed
167179
}
168180
case string(sortingFileType):
169-
order = getTypeOrderingFunc(dirEntries, reversed)
181+
order = getTypeOrderingFunc(elements, reversed)
170182
}
171183
return order
172184
}
173185

174-
func getSizeOrderingFunc(dirEntries []os.DirEntry, reversed bool, location string) sliceOrderFunc {
186+
func getSizeOrderingFunc(elements []element, reversed bool) sliceOrderFunc {
175187
return func(i, j int) bool {
176188
// Directories at the top sorted by direct child count (not recursive)
177189
// Files sorted by size
178190

179191
// One of them is a directory, and other is not
180-
if dirEntries[i].IsDir() != dirEntries[j].IsDir() {
181-
return dirEntries[i].IsDir()
192+
if elements[i].directory != elements[j].directory {
193+
return elements[i].directory
182194
}
183195

184196
// This needs to be improved, and we should sort by actual size only
185197
// Repeated recursive read would be slow, so we could cache
186-
if dirEntries[i].IsDir() && dirEntries[j].IsDir() {
187-
filesI, err := os.ReadDir(filepath.Join(location, dirEntries[i].Name()))
198+
if elements[i].directory && elements[j].directory {
199+
filesI, err := os.ReadDir(elements[i].location)
188200
// No need of early return, we only call len() on filesI, so nil would
189201
// just result in 0
190202
if err != nil {
191203
slog.Error("Error when reading directory during sort", "error", err)
192204
}
193-
filesJ, err := os.ReadDir(filepath.Join(location, dirEntries[j].Name()))
205+
filesJ, err := os.ReadDir(elements[j].location)
194206
if err != nil {
195207
slog.Error("Error when reading directory during sort", "error", err)
196208
}
197209
return len(filesI) < len(filesJ) != reversed
198210
}
199-
// No need for err check, we already filtered out dirEntries with err != nil in Info() call
200-
fileInfoI, _ := dirEntries[i].Info()
201-
fileInfoJ, _ := dirEntries[j].Info()
202-
return fileInfoI.Size() < fileInfoJ.Size() != reversed
211+
return elements[i].info.Size() < elements[j].info.Size() != reversed
203212
}
204213
}
205214

206-
func getTypeOrderingFunc(dirEntries []os.DirEntry, reversed bool) sliceOrderFunc {
215+
func getTypeOrderingFunc(elements []element, reversed bool) sliceOrderFunc {
207216
return func(i, j int) bool {
208217
// One of them is a directory, and the other is not
209-
if dirEntries[i].IsDir() != dirEntries[j].IsDir() {
210-
return dirEntries[i].IsDir()
218+
if elements[i].directory != elements[j].directory {
219+
return elements[i].directory
211220
}
212221

213222
var extI, extJ string
214-
if !dirEntries[i].IsDir() {
215-
extI = strings.ToLower(filepath.Ext(dirEntries[i].Name()))
223+
if !elements[i].directory {
224+
extI = strings.ToLower(filepath.Ext(elements[i].name))
216225
}
217-
if !dirEntries[j].IsDir() {
218-
extJ = strings.ToLower(filepath.Ext(dirEntries[j].Name()))
226+
if !elements[j].directory {
227+
extJ = strings.ToLower(filepath.Ext(elements[j].name))
219228
}
220229

221230
// Compare by extension/type
@@ -225,10 +234,10 @@ func getTypeOrderingFunc(dirEntries []os.DirEntry, reversed bool) sliceOrderFunc
225234

226235
// If same type, fall back to name
227236
if common.Config.CaseSensitiveSort {
228-
return (dirEntries[i].Name() < dirEntries[j].Name()) != reversed
237+
return (elements[i].name < elements[j].name) != reversed
229238
}
230239

231-
return (strings.ToLower(dirEntries[i].Name()) < strings.ToLower(dirEntries[j].Name())) != reversed
240+
return (strings.ToLower(elements[i].name) < strings.ToLower(elements[j].name)) != reversed
232241
}
233242
}
234243

src/internal/handle_panel_movement.go

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,38 +32,26 @@ func (m *model) enterPanel() {
3232
}
3333
selectedItem := panel.getSelectedItem()
3434
if selectedItem.directory {
35-
// TODO : Propagate error out from this this function. Return here, instead of logging
36-
err := m.updateCurrentFilePanelDir(selectedItem.location)
37-
if err != nil {
38-
slog.Error("Error while changing to directory", "error", err, "target", selectedItem.location)
39-
}
40-
return
41-
}
42-
fileInfo, err := os.Lstat(selectedItem.location)
43-
if err != nil {
44-
slog.Error("Error while getting file info", "error", err)
45-
return
46-
}
47-
48-
if fileInfo.Mode()&os.ModeSymlink != 0 {
49-
targetPath, symlinkErr := filepath.EvalSymlinks(selectedItem.location)
50-
if symlinkErr != nil {
51-
return
52-
}
53-
54-
targetInfo, lstatErr := os.Lstat(targetPath)
35+
targetPath := selectedItem.location
5536

56-
if lstatErr != nil {
57-
return
58-
}
37+
if selectedItem.info.Mode()&os.ModeSymlink != 0 {
38+
var symlinkErr error
39+
targetPath, symlinkErr = filepath.EvalSymlinks(targetPath)
40+
if symlinkErr != nil {
41+
return
42+
}
5943

60-
if targetInfo.IsDir() {
61-
err = m.updateCurrentFilePanelDir(targetPath)
62-
if err != nil {
63-
slog.Error("Error while changing to directory", "error", err, "target", targetPath)
44+
// targetPath shouldn't be a link now, so Stat and Lstat should be same
45+
if targetInfo, lstatErr := os.Lstat(targetPath); lstatErr != nil || !targetInfo.IsDir() {
46+
return
6447
}
65-
return
6648
}
49+
// TODO : Propagate error out from this this function. Return here, instead of logging
50+
err := m.updateCurrentFilePanelDir(targetPath)
51+
if err != nil {
52+
slog.Error("Error while changing to directory", "error", err, "target", targetPath)
53+
}
54+
return
6755
}
6856

6957
if variable.ChooserFile != "" {

src/internal/model_render.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,6 @@ func (panel *filePanel) renderFileEntries(r *rendering.Renderer, mainPanelHeight
131131
cursor = icon.Cursor
132132
}
133133

134-
// Performance TODO: Remove or cache this if not needed at render time
135-
// This will unnecessarily slow down rendering. There should be a way to avoid this at render
136-
_, err := os.ReadDir(panel.element[i].location)
137-
dirExists := err == nil || panel.element[i].directory
138-
139134
selectBox := panel.renderSelectBox(isSelected)
140135

141136
// Calculate the actual prefix width for proper alignment
@@ -144,7 +139,7 @@ func (panel *filePanel) renderFileEntries(r *rendering.Renderer, mainPanelHeight
144139
renderedName := common.PrettierName(
145140
panel.element[i].name,
146141
filePanelWidth-prefixWidth,
147-
dirExists,
142+
panel.element[i].directory,
148143
isSelected,
149144
common.FilePanelBGColor,
150145
)

src/internal/type.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package internal
22

33
import (
4+
"os"
45
"time"
56

67
zoxidelib "github.com/lazysegtree/go-zoxide"
@@ -195,6 +196,7 @@ type element struct {
195196
location string
196197
directory bool
197198
metaData [][2]string
199+
info os.FileInfo
198200
}
199201

200202
/* FILE WINDOWS TYPE END*/

0 commit comments

Comments
 (0)