mirror of
https://github.com/bitsycore/Allofit.git
synced 2026-10-05 12:27:26 +00:00
- Replace the SwiftUI Table (capped at 2000 rows) with an NSTableView that only builds the visible rows, so every match is listed and scrolls smoothly. Column order / width / visibility autosave, header menu to hide columns, Finder-accurate drag (file URLs), Edit > Copy copies files, keys and context menu as before, "Authorize Access..." in the menu for unreadable files, bold highlights readable on selected rows. - Results arrive in two steps for broad queries: the first 2000 rows in milliseconds, then the complete sort (~0.2 s for 600k entries). Background refreshes publish once so the scroll position is kept. - Store the index in 16k-record chunks (RecordStore): snapshots are cheap and a change copies only the touched chunk. Results are positions into a snapshot (ResultList), 4 bytes per row instead of a copy of each record. The parallel filter walks the store chunk by chunk.
110 lines
3.2 KiB
Swift
110 lines
3.2 KiB
Swift
import Foundation
|
|
|
|
// RecordStore holds the index's records in fixed-size chunks instead of one
|
|
// flat array. Copies (snapshots for searches and result lists) only share
|
|
// chunk buffers, and a mutation copies just the chunk it touches: changing
|
|
// one record while a snapshot is alive costs ~1.6 MB instead of the whole
|
|
// 60+ MB index. That lets every window keep its results as plain positions
|
|
// into a snapshot, at 4 bytes per row.
|
|
struct RecordStore: RandomAccessCollection, MutableCollection, Sendable {
|
|
|
|
// records per chunk: 2^kShift
|
|
static let kShift = 14
|
|
// chunk size and index mask derived from kShift
|
|
static let kChunkSize = 1 << kShift
|
|
static let kMask = kChunkSize - 1
|
|
|
|
// the records, kChunkSize per chunk (the last one may be shorter)
|
|
private var chunks: [[FileRecord]] = []
|
|
// total number of records
|
|
private(set) var count: Int = 0
|
|
|
|
// empty store
|
|
init() {}
|
|
|
|
// store holding inRecords, in order
|
|
init<S: Sequence>(_ inRecords: S) where S.Element == FileRecord {
|
|
for vRecord in inRecords {
|
|
append(vRecord)
|
|
}
|
|
}
|
|
|
|
// first index (always 0)
|
|
var startIndex: Int { 0 }
|
|
// one past the last index
|
|
var endIndex: Int { count }
|
|
|
|
// record at inPosition
|
|
subscript(inPosition: Int) -> FileRecord {
|
|
get { chunks[inPosition >> Self.kShift][inPosition & Self.kMask] }
|
|
set { chunks[inPosition >> Self.kShift][inPosition & Self.kMask] = newValue }
|
|
}
|
|
|
|
// number of chunks
|
|
var chunkCount: Int { chunks.count }
|
|
|
|
// the records of chunk inIndex (store positions inIndex << kShift and
|
|
// up). Hot loops walk chunks to read records with plain array access.
|
|
func chunk(at inIndex: Int) -> [FileRecord] {
|
|
return chunks[inIndex]
|
|
}
|
|
|
|
// adds a record at the end
|
|
mutating func append(_ inRecord: FileRecord) {
|
|
if chunks.isEmpty || chunks[chunks.count - 1].count == Self.kChunkSize {
|
|
var vChunk: [FileRecord] = []
|
|
vChunk.reserveCapacity(Self.kChunkSize)
|
|
chunks.append(vChunk)
|
|
}
|
|
chunks[chunks.count - 1].append(inRecord)
|
|
count += 1
|
|
}
|
|
|
|
// removes the last record
|
|
mutating func removeLast() {
|
|
chunks[chunks.count - 1].removeLast()
|
|
count -= 1
|
|
if chunks[chunks.count - 1].isEmpty {
|
|
chunks.removeLast()
|
|
}
|
|
}
|
|
}
|
|
|
|
// ResultList is one window's search result: positions into a snapshot of
|
|
// the index, in display order. Cheap to keep around (4 bytes per row) and
|
|
// immune to later index changes, since the snapshot doesn't move.
|
|
struct ResultList: RandomAccessCollection, Sendable {
|
|
|
|
// the index as it was when the search ran
|
|
let store: RecordStore
|
|
// positions of the matching records in store, sorted for display
|
|
let positions: [Int32]
|
|
|
|
// empty result
|
|
init() {
|
|
store = RecordStore()
|
|
positions = []
|
|
}
|
|
|
|
// result for the given snapshot and positions
|
|
init(inStore: RecordStore, inPositions: [Int32]) {
|
|
store = inStore
|
|
positions = inPositions
|
|
}
|
|
|
|
// first index (always 0)
|
|
var startIndex: Int { 0 }
|
|
// number of results
|
|
var endIndex: Int { positions.count }
|
|
|
|
// record shown at inRow
|
|
subscript(inRow: Int) -> FileRecord {
|
|
return store[Int(positions[inRow])]
|
|
}
|
|
|
|
// the same list without the records whose ids are in inIds
|
|
func removing(inIds: Set<FileRecord.ID>) -> ResultList {
|
|
return ResultList(inStore: store, inPositions: positions.filter { !inIds.contains(store[Int($0)].id) })
|
|
}
|
|
}
|