Skip to content

Commit df2a8ba

Browse files
author
Iakov Senatov
committed
fix compile errors in applyPatch - remove Task.detached
fix escape chars in closures fix compile errors in applyPatch - remove Task.detached fix: star.badge.plus→star.fill, cache shortKind O(1) perf: cache UTType lookups in KindCell Kind col: short ext (MP3_PARTIAL→MP3) + UTType tooltip permissions tooltip zeigt octal (755) bei hover add cellPadding 6pt inside columns - header+rows match fix doppel-scan bei navigation - remove redundant refreshFiles calls perf: smart skip poll wenn FSEvents aktiv, interval 60→300s remove unnecessary cache - tooltip is on-demand perf: incremental FSEvents patch O(log n), childCount updates for subdirs perf: skip redundant cache rebuild when files unchanged column minWidth based on header text+icon width
1 parent 9e35ef1 commit df2a8ba

File tree

13 files changed

+256
-93
lines changed

13 files changed

+256
-93
lines changed

GUI/Resources/Localizable.xcstrings

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,10 @@
11821182
}
11831183
}
11841184
},
1185+
"Folder" : {
1186+
"comment" : "A description of a folder.",
1187+
"isCommentAutoGenerated" : true
1188+
},
11851189
"Folder Access" : {
11861190

11871191
},
@@ -2133,6 +2137,10 @@
21332137
}
21342138
}
21352139
},
2140+
"Symbolic Link to Folder" : {
2141+
"comment" : "A tooltip for a folder that is a symbolic link.",
2142+
"isCommentAutoGenerated" : true
2143+
},
21362144
"Target file already exists:" : {
21372145
"comment" : "Target exists message",
21382146
"localizations" : {

GUI/Sources/Config/AppConstants.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ enum AppConstants {
1919
static let maxDepth = 2
2020

2121
/// Refresh interval for directory monitoring (seconds)
22-
static let refreshInterval: TimeInterval = 60
22+
static let refreshInterval: TimeInterval = 300
2323
}
2424

2525
// MARK: - History limits

GUI/Sources/ContextMenu/ActionsEnums/DirectoryAction.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ enum DirectoryAction: String, CaseIterable, Identifiable {
9090
case .delete: return "trash"
9191
case .getInfo: return "info.circle"
9292
case .openOnOtherPanel: return "arrow.left.arrow.right.square"
93-
case .addToFavorites: return "star.badge.plus"
93+
case .addToFavorites: return "star.fill"
9494
}
9595
}
9696

GUI/Sources/ContextMenu/ActionsEnums/FileAction.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ enum FileAction: String, CaseIterable, Identifiable {
9090
case .rename: return "pencil"
9191
case .delete: return "trash"
9292
case .getInfo: return "info.circle"
93-
case .addToFavorites: return "star.badge.plus"
93+
case .addToFavorites: return "star.fill"
9494
}
9595
}
9696

GUI/Sources/Features/Panels/FileRow.swift

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ struct FileRow: View {
3030
@State private var isDropTargeted: Bool = false
3131

3232
// MARK: - Selection colors — live from ColorThemeStore
33-
private var selectionActiveFill: Color { colorStore.activeTheme.selectionActive }
33+
private var selectionActiveFill: Color { colorStore.activeTheme.selectionActive }
3434
private var selectionInactiveFill: Color { colorStore.activeTheme.selectionInactive }
3535

36-
private static let dropTargetFill = Color.accentColor.opacity(0.2)
36+
private static let dropTargetFill = Color.accentColor.opacity(0.2)
3737
private static let dropTargetBorder = Color.accentColor
3838

3939
private var isActivePanel: Bool {
@@ -315,6 +315,7 @@ struct FileRow: View {
315315
.foregroundStyle(cellColor(for: spec.id))
316316
.lineLimit(1)
317317
.truncationMode(.tail)
318+
.padding(.horizontal, TableColumnDefaults.cellPadding)
318319
.frame(width: spec.width, alignment: spec.id.alignment)
319320
}
320321
}
@@ -329,7 +330,7 @@ struct FileRow: View {
329330
case .dateModified: Text(file.modifiedDateFormatted)
330331
case .size: Text(file.fileSizeFormatted)
331332
case .kind: KindCell(file: file)
332-
case .permissions: Text(file.permissionsFormatted)
333+
case .permissions: PermissionsCell(permissions: file.permissionsFormatted)
333334
case .owner: Text(file.ownerFormatted)
334335
case .childCount: Text(file.childCountFormatted)
335336
case .dateCreated: Text(file.creationDateFormatted)
@@ -341,20 +342,83 @@ struct FileRow: View {
341342
}
342343

343344
// MARK: - Kind column cell
344-
/// Shows a multicolor folder SF Symbol for directories, plain text for files.
345+
// MARK: - Kind column cell
346+
/// Shows short kind (extension) with full UTType description in tooltip
345347
private struct KindCell: View {
346348
let file: CustomFile
349+
347350
var body: some View {
348351
if file.isDirectory || file.isSymbolicDirectory {
349352
Image(systemName: file.isSymbolicDirectory ? "folder.badge.questionmark" : "folder.fill")
350353
.symbolRenderingMode(.multicolor)
351354
.font(.system(size: 12, weight: .regular))
355+
.help(file.isSymbolicDirectory ? "Symbolic Link to Folder" : "Folder")
352356
} else if file.isSymbolicLink {
353357
Image(systemName: "arrow.up.right.square")
354358
.symbolRenderingMode(.multicolor)
355359
.font(.system(size: 12, weight: .regular))
360+
.help("Symbolic Link")
356361
} else {
357-
Text(file.kindFormatted)
362+
Text(shortKind)
363+
.help(fullKindDescription)
364+
}
365+
}
366+
367+
/// Short kind: extension truncated at _ or -
368+
private var shortKind: String {
369+
let ext = file.fileExtension.uppercased()
370+
if ext.isEmpty { return "Doc" }
371+
if let idx = ext.firstIndex(where: { $0 == "_" || $0 == "-" }) {
372+
return String(ext[..<idx])
358373
}
374+
if ext.count > 5 { return String(ext.prefix(4)) + "" }
375+
return ext
376+
}
377+
378+
/// Full description from UTType (on-demand, no cache needed)
379+
private var fullKindDescription: String {
380+
let ext = file.fileExtension.lowercased()
381+
guard !ext.isEmpty else { return "Document" }
382+
if let uttype = UTType(filenameExtension: ext),
383+
let desc = uttype.localizedDescription
384+
{
385+
return desc
386+
}
387+
return ext.uppercased()
388+
}
389+
}
390+
391+
private struct PermissionsCell: View {
392+
let permissions: String
393+
394+
var body: some View {
395+
Text(permissions)
396+
.help(octalValue)
397+
}
398+
399+
/// Convert symbolic permissions (rwxr-xr-x) to octal (755)
400+
private var octalValue: String {
401+
let chars = Array(permissions)
402+
guard chars.count >= 9 else { return permissions }
403+
404+
// Take last 9 characters (skip type indicator like 'd' or '-')
405+
let permChars = chars.suffix(9)
406+
guard permChars.count == 9 else { return permissions }
407+
408+
let arr = Array(permChars)
409+
let owner = tripletToOctal(arr[0], arr[1], arr[2])
410+
let group = tripletToOctal(arr[3], arr[4], arr[5])
411+
let other = tripletToOctal(arr[6], arr[7], arr[8])
412+
413+
return "\(owner)\(group)\(other)"
414+
}
415+
416+
/// Convert rwx triplet to octal digit (0-7)
417+
private func tripletToOctal(_ r: Character, _ w: Character, _ x: Character) -> Int {
418+
var value = 0
419+
if r == "r" { value += 4 }
420+
if w == "w" { value += 2 }
421+
if x == "x" || x == "s" || x == "t" { value += 1 }
422+
return value
359423
}
360424
}

GUI/Sources/Features/Panels/FileTable/ColumnLayoutModel.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ enum ColumnID: String, CaseIterable, Codable, Identifiable, Transferable {
7575

7676
var isRequired: Bool { self == .name }
7777

78+
// Minimum width based on header text + icon + sort arrow + padding
79+
var minHeaderWidth: CGFloat {
80+
let font = NSFont.systemFont(ofSize: 12, weight: .medium)
81+
let textWidth = (title as NSString).size(withAttributes: [.font: font]).width
82+
let iconWidth: CGFloat = icon != nil ? 18 : 0
83+
let sortArrowWidth: CGFloat = 16
84+
let padding: CGFloat = 20
85+
return ceil(textWidth + iconWidth + sortArrowWidth + padding)
86+
}
87+
7888
var alignment: Alignment {
7989
switch self {
8090
case .size, .childCount: .trailing
@@ -177,7 +187,7 @@ final class ColumnLayoutModel {
177187

178188
func setWidth(_ width: CGFloat, for id: ColumnID) {
179189
if let idx = columns.firstIndex(where: { $0.id == id }) {
180-
columns[idx].width = max(TableColumnDefaults.minWidth, min(width, TableColumnDefaults.maxWidth))
190+
columns[idx].width = max(id.minHeaderWidth, min(width, TableColumnDefaults.maxWidth))
181191
layoutVersion += 1
182192
}
183193
}

GUI/Sources/Features/Panels/FileTable/TableHeaderView.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ struct TableHeaderView: View {
5151
get: { spec.width },
5252
set: { layout.setWidth($0, for: spec.id) }
5353
),
54-
min: TableColumnDefaults.minWidth,
54+
min: spec.id.minHeaderWidth,
5555
max: TableColumnDefaults.maxWidth,
5656
onEnd: { layout.saveWidths() }
5757
)
@@ -102,8 +102,8 @@ struct TableHeaderView: View {
102102
autoFitColumn(spec.id)
103103
}
104104
)
105+
.padding(.horizontal, TableColumnDefaults.cellPadding)
105106
.frame(width: spec.width, alignment: spec.id.alignment)
106-
// No padding - width must match NSTableView column exactly
107107
.background(
108108
dragOverTargetID == spec.id
109109
? Color.accentColor.opacity(0.15)
@@ -216,7 +216,7 @@ struct TableHeaderView: View {
216216
if w > maxW { maxW = w }
217217
}
218218
let optimal = ceil(maxW + 16)
219-
let clamped = Swift.min(Swift.max(optimal, TableColumnDefaults.minWidth), TableColumnDefaults.maxWidth)
219+
let clamped = Swift.min(Swift.max(optimal, col.minHeaderWidth), TableColumnDefaults.maxWidth)
220220
layout.setWidth(clamped, for: col)
221221
layout.saveWidths()
222222
log.debug("[AutoFit] col=\(col) optimal=\(Int(clamped))pt")

GUI/Sources/Features/Panels/FileTable/TableView/FileTableView+State.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,18 @@ import SwiftUI
1111
// MARK: - State Management
1212
extension FileTableView {
1313

14+
/// Track last files reference to skip redundant rebuilds
15+
private static var lastFilesHash: [PanelSide: Int] = [:]
16+
1417
func recomputeSortedCache() {
15-
// Files arrive pre-sorted from DualDirectoryScanner.Task.detached.
16-
// Local re-sort is only needed when user changes sort column/direction
17-
// (onChange of sortKey/bSortAscending). On plain files change the order
18-
// is already correct — just assign without re-sorting to avoid blocking
19-
// MainActor for ~100ms on 26k-item directories.
18+
// Quick check: skip if files array is identical (same reference or content)
19+
let newHash = files.count ^ (files.first?.id.hashValue ?? 0) ^ (files.last?.id.hashValue ?? 0)
20+
if Self.lastFilesHash[panelSide] == newHash && cachedSortedFiles.count == files.count {
21+
log.debug("[Cache] panel=\(panelSide) skip rebuild — no changes detected")
22+
return
23+
}
24+
Self.lastFilesHash[panelSide] = newHash
25+
2026
let t0 = Date()
2127
cachedSortedFiles = files
2228
rebuildIndexByID()

0 commit comments

Comments
 (0)