Skip to content

Commit 19d0608

Browse files
committed
Adjust Tool/Workspace
1 parent d9e230c commit 19d0608

File tree

4 files changed

+80
-106
lines changed

4 files changed

+80
-106
lines changed

Tool/Sources/Workspace/Filespace.swift

Lines changed: 24 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,50 @@ import SuggestionModel
44

55
public protocol FilespacePropertyKey {
66
associatedtype Value
7+
static func createDefaultValue() -> Value
78
}
89

9-
public struct FilespacePropertyValues {
10+
@WorkspaceActor
11+
public final class FilespacePropertyValues {
1012
var storage: [ObjectIdentifier: Any] = [:]
1113

12-
public subscript<K: WorkspacePropertyKey>(key: K.Type) -> K.Value? {
14+
public subscript<K: FilespacePropertyKey>(_ key: K.Type) -> K.Value {
1315
get {
14-
storage[ObjectIdentifier(key)] as? K.Value
16+
if let value = storage[ObjectIdentifier(key)] as? K.Value {
17+
return value
18+
}
19+
let value = key.createDefaultValue()
20+
storage[ObjectIdentifier(key)] = value
21+
return value
1522
}
1623
set {
1724
storage[ObjectIdentifier(key)] = newValue
1825
}
1926
}
2027
}
2128

29+
@WorkspaceActor
30+
@dynamicMemberLookup
2231
public final class Filespace {
23-
public struct Snapshot: Equatable {
24-
public var linesHash: Int
25-
public var cursorPosition: CursorPosition
26-
}
27-
28-
public struct CodeMetadata: Equatable {
29-
public var uti: String?
30-
public var tabSize: Int?
31-
public var indentSize: Int?
32-
public var usesTabsForIndentation: Bool?
33-
}
34-
3532
public let fileURL: URL
3633
public private(set) lazy var language: String = languageIdentifierFromFileURL(fileURL).rawValue
3734
public var suggestions: [CodeSuggestion] = [] {
3835
didSet { refreshUpdateTime() }
3936
}
4037

41-
/// stored for pseudo command handler
42-
public var codeMetadata: CodeMetadata = .init()
4338
public var suggestionIndex: Int = 0
44-
public var suggestionSourceSnapshot: Snapshot = .init(linesHash: -1, cursorPosition: .outOfScope)
39+
4540
public var presentingSuggestion: CodeSuggestion? {
4641
guard suggestions.endIndex > suggestionIndex, suggestionIndex >= 0 else { return nil }
4742
return suggestions[suggestionIndex]
4843
}
4944

50-
private(set) var lastSuggestionUpdateTime: Date = Environment.now()
5145
public var isExpired: Bool {
5246
Environment.now().timeIntervalSince(lastSuggestionUpdateTime) > 60 * 3
5347
}
54-
48+
49+
private(set) var lastSuggestionUpdateTime: Date = Environment.now()
50+
var additionalProperties = FilespacePropertyValues()
5551
let fileSaveWatcher: FileSaveWatcher
5652
let onClose: (URL) -> Void
5753

@@ -72,68 +68,21 @@ public final class Filespace {
7268
onSave(self)
7369
}
7470
}
71+
72+
public subscript<K>(
73+
dynamicMember dynamicMember: WritableKeyPath<FilespacePropertyValues, K>
74+
) -> K {
75+
get { additionalProperties[keyPath: dynamicMember] }
76+
set { additionalProperties[keyPath: dynamicMember] = newValue }
77+
}
7578

76-
public func reset(resetSnapshot: Bool = true) {
79+
public func reset() {
7780
suggestions = []
7881
suggestionIndex = 0
79-
if resetSnapshot {
80-
suggestionSourceSnapshot = .init(linesHash: -1, cursorPosition: .outOfScope)
81-
}
8282
}
8383

8484
public func refreshUpdateTime() {
8585
lastSuggestionUpdateTime = Environment.now()
8686
}
87-
88-
/// Validate the suggestion is still valid.
89-
/// - Parameters:
90-
/// - lines: lines of the file
91-
/// - cursorPosition: cursor position
92-
/// - Returns: `true` if the suggestion is still valid
93-
public func validateSuggestions(lines: [String], cursorPosition: CursorPosition) -> Bool {
94-
guard let presentingSuggestion else { return false }
95-
96-
// cursor has moved to another line
97-
if cursorPosition.line != presentingSuggestion.position.line {
98-
reset()
99-
return false
100-
}
101-
102-
// the cursor position is valid
103-
guard cursorPosition.line >= 0, cursorPosition.line < lines.count else {
104-
reset()
105-
return false
106-
}
107-
108-
let editingLine = lines[cursorPosition.line].dropLast(1) // dropping \n
109-
let suggestionLines = presentingSuggestion.text.split(separator: "\n")
110-
let suggestionFirstLine = suggestionLines.first ?? ""
111-
112-
// the line content doesn't match the suggestion
113-
if cursorPosition.character > 0,
114-
!suggestionFirstLine.hasPrefix(editingLine[..<(editingLine.index(
115-
editingLine.startIndex,
116-
offsetBy: cursorPosition.character,
117-
limitedBy: editingLine.endIndex
118-
) ?? editingLine.endIndex)])
119-
{
120-
reset()
121-
return false
122-
}
123-
124-
// finished typing the whole suggestion when the suggestion has only one line
125-
if editingLine.hasPrefix(suggestionFirstLine), suggestionLines.count <= 1 {
126-
reset()
127-
return false
128-
}
129-
130-
// undo to a state before the suggestion was generated
131-
if editingLine.count < presentingSuggestion.position.character {
132-
reset()
133-
return false
134-
}
135-
136-
return true
137-
}
13887
}
13988

Tool/Sources/Workspace/OpenedFileRocoverableStorage.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import Foundation
22
import Preferences
33

4-
@ServiceActor
5-
final class OpenedFileRecoverableStorage {
4+
public final class OpenedFileRecoverableStorage {
65
let projectRootURL: URL
76
let userDefault = UserDefaults.shared
87
let key = "OpenedFileRecoverableStorage"
@@ -11,23 +10,23 @@ final class OpenedFileRecoverableStorage {
1110
self.projectRootURL = projectRootURL
1211
}
1312

14-
func openFile(fileURL: URL) {
13+
public func openFile(fileURL: URL) {
1514
var dict = userDefault.dictionary(forKey: key) ?? [:]
1615
var openedFiles = Set(dict[projectRootURL.path] as? [String] ?? [])
1716
openedFiles.insert(fileURL.path)
1817
dict[projectRootURL.path] = Array(openedFiles)
1918
userDefault.set(dict, forKey: key)
2019
}
2120

22-
func closeFile(fileURL: URL) {
21+
public func closeFile(fileURL: URL) {
2322
var dict = userDefault.dictionary(forKey: key) ?? [:]
2423
var openedFiles = dict[projectRootURL.path] as? [String] ?? []
2524
openedFiles.removeAll(where: { $0 == fileURL.path })
2625
dict[projectRootURL.path] = openedFiles
2726
userDefault.set(dict, forKey: key)
2827
}
2928

30-
var openedFiles: [URL] {
29+
public var openedFiles: [URL] {
3130
let dict = userDefault.dictionary(forKey: key) ?? [:]
3231
let openedFiles = dict[projectRootURL.path] as? [String] ?? []
3332
return openedFiles.map { URL(fileURLWithPath: $0) }

Tool/Sources/Workspace/Workspace.swift

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,62 +6,67 @@ import UserDefaultsObserver
66

77
public protocol WorkspacePropertyKey {
88
associatedtype Value
9+
static func createDefaultValue() -> Value
910
}
1011

11-
public struct WorkspacePropertyValues {
12+
@WorkspaceActor
13+
public class WorkspacePropertyValues {
1214
var storage: [ObjectIdentifier: Any] = [:]
1315

14-
public subscript<K: WorkspacePropertyKey>(key: K.Type) -> K.Value? {
16+
public subscript<K: WorkspacePropertyKey>(_ key: K.Type) -> K.Value {
1517
get {
16-
storage[ObjectIdentifier(key)] as? K.Value
18+
if let value = storage[ObjectIdentifier(key)] as? K.Value {
19+
return value
20+
}
21+
let value = key.createDefaultValue()
22+
storage[ObjectIdentifier(key)] = value
23+
return value
1724
}
1825
set {
1926
storage[ObjectIdentifier(key)] = newValue
2027
}
2128
}
2229
}
2330

31+
@WorkspaceActor
2432
open class WorkspacePlugin {
2533
public private(set) weak var workspace: Workspace?
34+
public var projectRootURL: URL { workspace?.projectRootURL ?? URL(fileURLWithPath: "/") }
2635
public var filespaces: [URL: Filespace] { workspace?.filespaces ?? [:] }
2736

2837
public init(workspace: Workspace) {
2938
self.workspace = workspace
3039
}
3140

3241
open func didOpenFilespace(_: Filespace) {}
33-
open func didSavedFilespace(_: Filespace) {}
42+
open func didSaveFilespace(_: Filespace) {}
3443
open func didCloseFilespace(_: URL) {}
3544
}
3645

46+
@WorkspaceActor
3747
@dynamicMemberLookup
3848
public final class Workspace {
39-
public struct SuggestionFeatureDisabledError: Error, LocalizedError {
40-
public var errorDescription: String? {
41-
"Suggestion feature is disabled for this project."
42-
}
43-
}
44-
4549
public struct UnsupportedFileError: Error, LocalizedError {
4650
public var extensionName: String
4751
public var errorDescription: String? {
4852
"File type \(extensionName) unsupported."
4953
}
54+
55+
public init(extensionName: String) {
56+
self.extensionName = extensionName
57+
}
5058
}
5159

5260
var additionalProperties = WorkspacePropertyValues()
53-
var plugins = [ObjectIdentifier: WorkspacePlugin]()
61+
public internal(set) var plugins = [ObjectIdentifier: WorkspacePlugin]()
5462
public let projectRootURL: URL
55-
let openedFileRecoverableStorage: OpenedFileRecoverableStorage
63+
public let openedFileRecoverableStorage: OpenedFileRecoverableStorage
5664
public private(set) var lastSuggestionUpdateTime = Environment.now()
5765
public var isExpired: Bool {
5866
Environment.now().timeIntervalSince(lastSuggestionUpdateTime) > 60 * 60 * 1
5967
}
6068

61-
private(set) var filespaces = [URL: Filespace]()
62-
var isRealtimeSuggestionEnabled: Bool {
63-
UserDefaults.shared.value(for: \.realtimeSuggestionToggle)
64-
}
69+
public private(set) var filespaces = [URL: Filespace]()
6570

6671
let userDefaultsObserver = UserDefaultsObserver(
6772
object: UserDefaults.shared, forKeyPaths: [
@@ -76,16 +81,14 @@ public final class Workspace {
7681
get { additionalProperties[keyPath: dynamicMember] }
7782
set { additionalProperties[keyPath: dynamicMember] = newValue }
7883
}
84+
85+
public func plugin<P: WorkspacePlugin>(for type: P.Type) -> P? {
86+
plugins[ObjectIdentifier(type)] as? P
87+
}
7988

8089
init(projectRootURL: URL) {
8190
self.projectRootURL = projectRootURL
8291
openedFileRecoverableStorage = .init(projectRootURL: projectRootURL)
83-
//
84-
// userDefaultsObserver.onChange = { [weak self] in
85-
// guard let self else { return }
86-
// _ = self.suggestionService
87-
// }
88-
8992
let openedFiles = openedFileRecoverableStorage.openedFiles
9093
for fileURL in openedFiles {
9194
_ = createFilespaceIfNeeded(fileURL: fileURL)
@@ -96,14 +99,14 @@ public final class Workspace {
9699
lastSuggestionUpdateTime = Environment.now()
97100
}
98101

99-
func createFilespaceIfNeeded(fileURL: URL) -> Filespace {
102+
public func createFilespaceIfNeeded(fileURL: URL) -> Filespace {
100103
let existedFilespace = filespaces[fileURL]
101104
let filespace = existedFilespace ?? .init(
102105
fileURL: fileURL,
103106
onSave: { [weak self] filespace in
104107
guard let self else { return }
105108
for plugin in self.plugins.values {
106-
plugin.didSavedFilespace(filespace)
109+
plugin.didSaveFilespace(filespace)
107110
}
108111
},
109112
onClose: { [weak self] url in
@@ -125,5 +128,9 @@ public final class Workspace {
125128
}
126129
return filespace
127130
}
131+
132+
public func closeFilespace(fileURL: URL) {
133+
filespaces[fileURL] = nil
134+
}
128135
}
129136

Tool/Sources/Workspace/WorkspacePool.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
11
import Environment
22
import Foundation
33

4+
@globalActor public enum WorkspaceActor {
5+
public actor TheActor {}
6+
public static let shared = TheActor()
7+
}
8+
9+
@WorkspaceActor
410
public class WorkspacePool {
511
public internal(set) var workspaces: [URL: Workspace] = [:]
612
var plugins = [ObjectIdentifier: (Workspace) -> WorkspacePlugin]()
713

14+
public init(
15+
workspaces: [URL: Workspace] = [:],
16+
plugins: [ObjectIdentifier: (Workspace) -> WorkspacePlugin] = [:]
17+
) {
18+
self.workspaces = workspaces
19+
self.plugins = plugins
20+
}
21+
822
public func registerPlugin<Plugin: WorkspacePlugin>(_ plugin: @escaping (Workspace) -> Plugin) {
923
let id = ObjectIdentifier(Plugin.self)
1024
let erasedPlugin: (Workspace) -> WorkspacePlugin = { plugin($0) }
@@ -18,7 +32,7 @@ public class WorkspacePool {
1832
public func unregisterPlugin<Plugin: WorkspacePlugin>(_: Plugin.Type) {
1933
let id = ObjectIdentifier(Plugin.self)
2034
plugins[id] = nil
21-
35+
2236
for workspace in workspaces.values {
2337
removePlugin(id: id, from: workspace)
2438
}
@@ -78,6 +92,10 @@ public class WorkspacePool {
7892
workspace.refreshUpdateTime()
7993
return (workspace, filespace)
8094
}
95+
96+
public func removeWorkspace(url: URL) {
97+
workspaces[url] = nil
98+
}
8199
}
82100

83101
extension WorkspacePool {
@@ -93,7 +111,7 @@ extension WorkspacePool {
93111
func removePlugin(id: ObjectIdentifier, from workspace: Workspace) {
94112
workspace.plugins[id] = nil
95113
}
96-
114+
97115
func createNewWorkspace(projectRootURL: URL) -> Workspace {
98116
let new = Workspace(projectRootURL: projectRootURL)
99117
for (id, plugin) in plugins {
@@ -102,3 +120,4 @@ extension WorkspacePool {
102120
return new
103121
}
104122
}
123+

0 commit comments

Comments
 (0)