import Environment import Foundation import SuggestionModel public protocol FilespacePropertyKey { associatedtype Value static func createDefaultValue() -> Value } public final class FilespacePropertyValues { private var storage: [ObjectIdentifier: Any] = [:] @WorkspaceActor public subscript(_ key: K.Type) -> K.Value { get { if let value = storage[ObjectIdentifier(key)] as? K.Value { return value } let value = key.createDefaultValue() storage[ObjectIdentifier(key)] = value return value } set { storage[ObjectIdentifier(key)] = newValue } } } public struct FilespaceCodeMetadata: Equatable { public var uti: String? public var tabSize: Int? public var indentSize: Int? public var usesTabsForIndentation: Bool? init( uti: String? = nil, tabSize: Int? = nil, indentSize: Int? = nil, usesTabsForIndentation: Bool? = nil ) { self.uti = uti self.tabSize = tabSize self.indentSize = indentSize self.usesTabsForIndentation = usesTabsForIndentation } } @dynamicMemberLookup public final class Filespace { public let fileURL: URL public private(set) lazy var language: CodeLanguage = languageIdentifierFromFileURL(fileURL) public var codeMetadata: FilespaceCodeMetadata = .init() public internal(set) var suggestions: [CodeSuggestion] = [] { didSet { refreshUpdateTime() } } public private(set) var suggestionIndex: Int = 0 public var presentingSuggestion: CodeSuggestion? { guard suggestions.endIndex > suggestionIndex, suggestionIndex >= 0 else { return nil } return suggestions[suggestionIndex] } public var isExpired: Bool { Environment.now().timeIntervalSince(lastSuggestionUpdateTime) > 60 * 3 } private(set) var lastSuggestionUpdateTime: Date = Environment.now() private var additionalProperties = FilespacePropertyValues() let fileSaveWatcher: FileSaveWatcher let onClose: (URL) -> Void deinit { onClose(fileURL) } init( fileURL: URL, onSave: @escaping (Filespace) -> Void, onClose: @escaping (URL) -> Void ) { self.fileURL = fileURL self.onClose = onClose fileSaveWatcher = .init(fileURL: fileURL) fileSaveWatcher.changeHandler = { [weak self] in guard let self else { return } onSave(self) } } @WorkspaceActor public subscript( dynamicMember dynamicMember: WritableKeyPath ) -> K { get { additionalProperties[keyPath: dynamicMember] } set { additionalProperties[keyPath: dynamicMember] = newValue } } @WorkspaceActor public func reset() { suggestions = [] suggestionIndex = 0 } public func refreshUpdateTime() { lastSuggestionUpdateTime = Environment.now() } @WorkspaceActor public func setSuggestions(_ suggestions: [CodeSuggestion]) { self.suggestions = suggestions suggestionIndex = 0 } @WorkspaceActor public func nextSuggestion() { suggestionIndex += 1 if suggestionIndex >= suggestions.endIndex { suggestionIndex = 0 } } @WorkspaceActor public func previousSuggestion() { suggestionIndex -= 1 if suggestionIndex < 0 { suggestionIndex = suggestions.endIndex - 1 } } }