@@ -9,6 +9,7 @@ import Foundation
99import Light_Swift_Untar
1010import CodeEditKit
1111import GRDB
12+ import LSP
1213
1314/// Class which handles all extensions (its bundles, instances for each workspace and so on)
1415public final class ExtensionsManager {
@@ -28,6 +29,7 @@ public final class ExtensionsManager {
2829
2930 var loadedBundles : [ UUID : Bundle ] = [ : ]
3031 var loadedPlugins : [ PluginWorkspaceKey : ExtensionInterface ] = [ : ]
32+ var loadedLanguageServers : [ PluginWorkspaceKey : LSPClient ] = [ : ]
3133
3234 init ( ) throws {
3335 self . codeeditFolder = try FileManager . default
@@ -53,6 +55,11 @@ public final class ExtensionsManager {
5355 table. column ( " loadable " , . boolean)
5456 }
5557 }
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+ }
5663 try migrator. migrate ( self . dbQueue)
5764 }
5865
@@ -64,58 +71,119 @@ public final class ExtensionsManager {
6471 } . forEach { ( key: PluginWorkspaceKey , _) in
6572 loadedPlugins. removeValue ( forKey: key)
6673 }
67- }
6874
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)
7280 }
81+ }
7382
83+ private func loadBundle( id: UUID , withExtension ext: String ) throws -> Bundle ? {
7484 guard let bundleURL = try FileManager . default. contentsOfDirectory (
7585 at: extensionsFolder. appendingPathComponent ( id. uuidString,
7686 isDirectory: true ) ,
7787 includingPropertiesForKeys: nil ,
7888 options: . skipsPackageDescendants
79- ) . first else { return nil }
89+ ) . first ( where : { $0 . pathExtension == ext } ) else { return nil }
8090
81- guard bundleURL. pathExtension == " ceext " else { return nil }
8291 guard let bundle = Bundle ( url: bundleURL) else { return nil }
8392
84- guard bundle. load ( ) else { return nil }
85-
8693 loadedBundles [ id] = bundle
8794
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+
88109 return bundle. principalClass as? ExtensionBuilder . Type
89110 }
90111
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+
91144 /// Preloads all extensions' bundles to `loadedBundles`
92145 public func preload( ) throws {
93146 let plugins = try self . dbQueue. read { database in
94147 try DownloadedPlugin . filter ( Column ( " loadable " ) == true ) . fetchAll ( database)
95148 }
96149
97150 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+ }
99157 }
100158 }
101159
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 {
106164 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)
108168 }
109169
110170 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)
114173
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+ }
116179
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+ }
119187 }
120188 }
121189
@@ -144,6 +212,7 @@ public final class ExtensionsManager {
144212 )
145213 . appendingPathComponent ( " \( release. id. uuidString) .tar " )
146214
215+ // TODO: show progress
147216 let ( source, _) = try await URLSession . shared. download ( from: tarball)
148217
149218 if FileManager . default. fileExists ( atPath: cacheTar. path) {
@@ -168,7 +237,7 @@ public final class ExtensionsManager {
168237 // save to db
169238
170239 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 )
172241 . insert ( database)
173242 }
174243 }
@@ -203,6 +272,13 @@ public final class ExtensionsManager {
203272 } . forEach { ( key: PluginWorkspaceKey , _) in
204273 loadedPlugins. removeValue ( forKey: key)
205274 }
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+ }
206282 }
207283
208284 /// Checks whether extension's bundle (plugin) is installed
0 commit comments