@@ -9,6 +9,7 @@ import Foundation
9
9
import Light_Swift_Untar
10
10
import CodeEditKit
11
11
import GRDB
12
+ import LSP
12
13
13
14
/// Class which handles all extensions (its bundles, instances for each workspace and so on)
14
15
public final class ExtensionsManager {
@@ -28,6 +29,7 @@ public final class ExtensionsManager {
28
29
29
30
var loadedBundles : [ UUID : Bundle ] = [ : ]
30
31
var loadedPlugins : [ PluginWorkspaceKey : ExtensionInterface ] = [ : ]
32
+ var loadedLanguageServers : [ PluginWorkspaceKey : LSPClient ] = [ : ]
31
33
32
34
init ( ) throws {
33
35
self . codeeditFolder = try FileManager . default
@@ -53,6 +55,11 @@ public final class ExtensionsManager {
53
55
table. column ( " loadable " , . boolean)
54
56
}
55
57
}
58
+ migrator. registerMigration ( " v1.0.1 " ) { database in
59
+ try database. alter ( table: DownloadedPlugin . databaseTableName) { body in
60
+ body. add ( column: " sdk " , . text) . defaults ( to: " swift " )
61
+ }
62
+ }
56
63
try migrator. migrate ( self . dbQueue)
57
64
}
58
65
@@ -64,58 +71,119 @@ public final class ExtensionsManager {
64
71
} . forEach { ( key: PluginWorkspaceKey , _) in
65
72
loadedPlugins. removeValue ( forKey: key)
66
73
}
67
- }
68
74
69
- private func getExtensionBuilder( id: UUID ) throws -> ExtensionBuilder . Type ? {
70
- if loadedBundles. keys. contains ( id) {
71
- return loadedBundles [ id] ? . principalClass as? ExtensionBuilder . Type
75
+ loadedLanguageServers. filter { elem in
76
+ return elem. key. workspace == url
77
+ } . forEach { ( key: PluginWorkspaceKey , client: LSPClient ) in
78
+ client. close ( )
79
+ loadedLanguageServers. removeValue ( forKey: key)
72
80
}
81
+ }
73
82
83
+ private func loadBundle( id: UUID , withExtension ext: String ) throws -> Bundle ? {
74
84
guard let bundleURL = try FileManager . default. contentsOfDirectory (
75
85
at: extensionsFolder. appendingPathComponent ( id. uuidString,
76
86
isDirectory: true ) ,
77
87
includingPropertiesForKeys: nil ,
78
88
options: . skipsPackageDescendants
79
- ) . first else { return nil }
89
+ ) . first ( where : { $0 . pathExtension == ext } ) else { return nil }
80
90
81
- guard bundleURL. pathExtension == " ceext " else { return nil }
82
91
guard let bundle = Bundle ( url: bundleURL) else { return nil }
83
92
84
- guard bundle. load ( ) else { return nil }
85
-
86
93
loadedBundles [ id] = bundle
87
94
95
+ return bundle
96
+ }
97
+
98
+ private func getExtensionBuilder( id: UUID ) throws -> ExtensionBuilder . Type ? {
99
+ if loadedBundles. keys. contains ( id) {
100
+ return loadedBundles [ id] ? . principalClass as? ExtensionBuilder . Type
101
+ }
102
+
103
+ guard let bundle = try loadBundle ( id: id, withExtension: " ceext " ) else {
104
+ return nil
105
+ }
106
+
107
+ guard bundle. load ( ) else { return nil }
108
+
88
109
return bundle. principalClass as? ExtensionBuilder . Type
89
110
}
90
111
112
+ private func getLSPClient( id: UUID , workspaceURL: URL ) throws -> LSPClient ? {
113
+ if loadedBundles. keys. contains ( id) {
114
+ guard let lspFile = loadedBundles [ id] ? . infoDictionary ? [ " CELSPExecutable " ] as? String else {
115
+ return nil
116
+ }
117
+
118
+ guard let lspURL = loadedBundles [ id] ? . url ( forResource: lspFile, withExtension: nil ) else {
119
+ return nil
120
+ }
121
+
122
+ return try LSPClient ( lspURL,
123
+ workspace: workspaceURL,
124
+ arguments: loadedBundles [ id] ? . infoDictionary ? [ " CELSPArguments " ] as? [ String ] )
125
+ }
126
+
127
+ guard let bundle = try loadBundle ( id: id, withExtension: " celsp " ) else {
128
+ return nil
129
+ }
130
+
131
+ guard let lspFile = bundle. infoDictionary ? [ " CELSPExecutable " ] as? String else {
132
+ return nil
133
+ }
134
+
135
+ guard let lspURL = bundle. url ( forResource: lspFile, withExtension: nil ) else {
136
+ return nil
137
+ }
138
+
139
+ return try LSPClient ( lspURL,
140
+ workspace: workspaceURL,
141
+ arguments: loadedBundles [ id] ? . infoDictionary ? [ " CELSPArguments " ] as? [ String ] )
142
+ }
143
+
91
144
/// Preloads all extensions' bundles to `loadedBundles`
92
145
public func preload( ) throws {
93
146
let plugins = try self . dbQueue. read { database in
94
147
try DownloadedPlugin . filter ( Column ( " loadable " ) == true ) . fetchAll ( database)
95
148
}
96
149
97
150
try plugins. forEach { plugin in
98
- _ = try getExtensionBuilder ( id: plugin. release)
151
+ switch plugin. sdk {
152
+ case . swift:
153
+ _ = try loadBundle ( id: plugin. release, withExtension: " ceext " )
154
+ case . languageServer:
155
+ _ = try loadBundle ( id: plugin. release, withExtension: " celsp " )
156
+ }
99
157
}
100
158
}
101
159
102
- /// Loads extensions' bundles which were not loaded before and creates `ExtensionInterface` instances
103
- /// with `ExtensionAPI` created using specified initializer
104
- /// - Parameter apiInitializer : function which creates `ExtensionAPI` instance based on plugin's ID
105
- public func load( _ apiInitializer : ( String ) -> ExtensionAPI ) throws {
160
+ /// Loads extensions' bundles which were not loaded before and passes `ExtensionAPI` as a whole class
161
+ /// or workspace's URL
162
+ /// - Parameter apiBuilder : function which creates `ExtensionAPI` instance based on plugin's ID
163
+ public func load( _ apiBuilder : ( String ) -> ExtensionAPI ) throws {
106
164
let plugins = try self . dbQueue. read { database in
107
- try DownloadedPlugin . filter ( Column ( " loadable " ) == true ) . fetchAll ( database)
165
+ try DownloadedPlugin
166
+ . filter ( Column ( " loadable " ) == true )
167
+ . fetchAll ( database)
108
168
}
109
169
110
170
try plugins. forEach { plugin in
111
- guard let builder = try getExtensionBuilder ( id: plugin. release) else {
112
- return
113
- }
171
+ let api = apiBuilder ( plugin. plugin. uuidString)
172
+ let key = PluginWorkspaceKey ( releaseID: plugin. release, workspace: api. workspaceURL)
114
173
115
- let api = apiInitializer ( plugin. plugin. uuidString)
174
+ switch plugin. sdk {
175
+ case . swift:
176
+ guard let builder = try getExtensionBuilder ( id: plugin. release) else {
177
+ return
178
+ }
116
179
117
- let key = PluginWorkspaceKey ( releaseID: plugin. release, workspace: api. workspaceURL)
118
- loadedPlugins [ key] = builder. init ( ) . build ( withAPI: api)
180
+ loadedPlugins [ key] = builder. init ( ) . build ( withAPI: api)
181
+ case . languageServer:
182
+ guard let client = try getLSPClient ( id: plugin. release, workspaceURL: api. workspaceURL) else {
183
+ return
184
+ }
185
+ loadedLanguageServers [ key] = client
186
+ }
119
187
}
120
188
}
121
189
@@ -144,6 +212,7 @@ public final class ExtensionsManager {
144
212
)
145
213
. appendingPathComponent ( " \( release. id. uuidString) .tar " )
146
214
215
+ // TODO: show progress
147
216
let ( source, _) = try await URLSession . shared. download ( from: tarball)
148
217
149
218
if FileManager . default. fileExists ( atPath: cacheTar. path) {
@@ -168,7 +237,7 @@ public final class ExtensionsManager {
168
237
// save to db
169
238
170
239
try await dbQueue. write { database in
171
- try DownloadedPlugin ( plugin: plugin. id, release: release. id, loadable: true )
240
+ try DownloadedPlugin ( plugin: plugin. id, release: release. id, loadable: true , sdk : plugin . sdk )
172
241
. insert ( database)
173
242
}
174
243
}
@@ -203,6 +272,13 @@ public final class ExtensionsManager {
203
272
} . forEach { ( key: PluginWorkspaceKey , _) in
204
273
loadedPlugins. removeValue ( forKey: key)
205
274
}
275
+
276
+ loadedLanguageServers. filter { elem in
277
+ return elem. key. releaseID == entry. release
278
+ } . forEach { ( key: PluginWorkspaceKey , client: LSPClient ) in
279
+ client. close ( )
280
+ loadedLanguageServers. removeValue ( forKey: key)
281
+ }
206
282
}
207
283
208
284
/// Checks whether extension's bundle (plugin) is installed
0 commit comments