-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Language server installation menu #1997
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
FastestMolasses
wants to merge
43
commits into
main
Choose a base branch
from
lsp-install
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
5ea9de5
Language server installation menu
FastestMolasses d8ca6b2
Lint
FastestMolasses 95479a5
Merge branch 'main' into lsp-install
FastestMolasses ca48313
Added notification on file open
FastestMolasses 80d16d9
Small update
FastestMolasses 4c22bc3
Merge branch 'main' into lsp-install
FastestMolasses 21f7138
Refactors
FastestMolasses c827108
Connect install button
FastestMolasses b9d3945
Refactors
FastestMolasses 6c9c48a
Fix lint
FastestMolasses ee93abb
Refactors, added settings loading
FastestMolasses 2f2c022
Refactors, added text icon colors
FastestMolasses ed9dfe6
Update notifications
FastestMolasses 64ceb74
Add more documentation
FastestMolasses 04fde2c
Added installation queuing, fix runtime warnings
FastestMolasses 6b40bb3
Convert to actor
FastestMolasses 0fe73ef
Refactors, fix and update removals
FastestMolasses b5d7186
Fix queue feedback and clean up memory
FastestMolasses 1db6a66
Small refactors
FastestMolasses 1ec3820
Temporarily removed Language Servers menu
FastestMolasses 6747bf5
Small refactors
FastestMolasses 08963a2
Merge branch 'main' into lsp-install
FastestMolasses ae57197
Merge branch 'main' into lsp-install
thecoolwinter 74bf7e5
Correct CESE Version In XcodeProj File
thecoolwinter 24b3c37
[chore:] Bump Version Number (#2021)
thecoolwinter 2fb1157
Update pre-release.yml - xcpretty
thecoolwinter 8afeb08
Bump Build Number to 45 (#2022)
github-actions[bot] 64ad6a3
Add workflow_dispatch to Release Notes CI
thecoolwinter a0d1fb9
docs: add pro100filipp as a contributor for code (#2023)
allcontributors[bot] 29bf445
Source Control Filter (#2024)
LeonardoLarranaga 3bafb1c
Disable 'Export All Custom Themes' if custom themes is empty (#2027)
LeonardoLarranaga f2337c8
Update README.md
austincondiff 14d9709
Sort alphabetically - Folders on top (#2028)
LeonardoLarranaga 1ec7811
Upload dSYMs When Archiving
thecoolwinter 32a3791
Fix Tasks Not Saving (#2034)
thecoolwinter 89c6cc5
Add A Minimap (#2032)
thecoolwinter e464603
Fix Getting Stuck in Sub-View within Settings (#2038)
Kihron 3ca4a3a
Fixed History Inspector popover UI bug (#2041)
austincondiff d92d6c6
Language Server Syntax Highlights (#1985)
thecoolwinter 4bd724e
Refactors
FastestMolasses f358ab9
Refactors
FastestMolasses daae205
Refactors
FastestMolasses 9fcbfd8
Merge branch 'main' into lsp-install
FastestMolasses File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// | ||
// InstallationMethod.swift | ||
// CodeEdit | ||
// | ||
// Created by Abe Malla on 5/12/25. | ||
// | ||
|
||
import Foundation | ||
|
||
/// Installation method enum with all supported types | ||
enum InstallationMethod: Equatable { | ||
/// For standard package manager installations | ||
case standardPackage(source: PackageSource) | ||
/// For packages that need to be built from source with custom build steps | ||
case sourceBuild(source: PackageSource, command: String) | ||
/// For direct binary downloads | ||
case binaryDownload(source: PackageSource, url: URL) | ||
/// For installations that aren't recognized | ||
case unknown | ||
|
||
var packageName: String? { | ||
switch self { | ||
case .standardPackage(let source), | ||
.sourceBuild(let source, _), | ||
.binaryDownload(let source, _): | ||
return source.pkgName | ||
case .unknown: | ||
return nil | ||
} | ||
} | ||
|
||
var version: String? { | ||
switch self { | ||
case .standardPackage(let source), | ||
.sourceBuild(let source, _), | ||
.binaryDownload(let source, _): | ||
return source.version | ||
case .unknown: | ||
return nil | ||
} | ||
} | ||
|
||
var packageManagerType: PackageManagerType? { | ||
switch self { | ||
case .standardPackage(let source), | ||
.sourceBuild(let source, _), | ||
.binaryDownload(let source, _): | ||
return source.type | ||
case .unknown: | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
185 changes: 185 additions & 0 deletions
185
CodeEdit/Features/LSP/Registry/InstallationQueueManager.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
// | ||
// InstallationQueueManager.swift | ||
// CodeEdit | ||
// | ||
// Created by Abe Malla on 3/13/25. | ||
// | ||
|
||
import Foundation | ||
|
||
/// A class to manage queued installations of language servers | ||
final class InstallationQueueManager { | ||
static let shared: InstallationQueueManager = .init() | ||
|
||
/// The maximum number of concurrent installations allowed | ||
private let maxConcurrentInstallations: Int = 2 | ||
/// Queue of pending installations | ||
private var installationQueue: [(RegistryItem, (Result<Void, Error>) -> Void)] = [] | ||
/// Currently running installations | ||
private var runningInstallations: Set<String> = [] | ||
/// Installation status dictionary | ||
private var installationStatus: [String: PackageInstallationStatus] = [:] | ||
|
||
/// Add a package to the installation queue | ||
func queueInstallation(package: RegistryItem, completion: @escaping (Result<Void, Error>) -> Void) { | ||
// If we're already at max capacity and this isn't already running, mark as queued | ||
if runningInstallations.count >= maxConcurrentInstallations && !runningInstallations.contains(package.name) { | ||
installationStatus[package.name] = .queued | ||
installationQueue.append((package, completion)) | ||
|
||
// Notify UI that package is queued | ||
DispatchQueue.main.async { | ||
NotificationCenter.default.post( | ||
name: .installationStatusChanged, | ||
object: nil, | ||
userInfo: ["packageName": package.name, "status": PackageInstallationStatus.queued] | ||
) | ||
} | ||
} else { | ||
startInstallation(package: package, completion: completion) | ||
} | ||
} | ||
|
||
/// Starts the actual installation process for a package | ||
private func startInstallation(package: RegistryItem, completion: @escaping (Result<Void, Error>) -> Void) { | ||
installationStatus[package.name] = .installing | ||
runningInstallations.insert(package.name) | ||
|
||
// Notify UI that installation is now in progress | ||
DispatchQueue.main.async { | ||
NotificationCenter.default.post( | ||
name: .installationStatusChanged, | ||
object: nil, | ||
userInfo: ["packageName": package.name, "status": PackageInstallationStatus.installing] | ||
) | ||
} | ||
|
||
Task { | ||
do { | ||
try await RegistryManager.shared.installPackage(package: package) | ||
|
||
// Notify UI that installation is complete | ||
installationStatus[package.name] = .installed | ||
DispatchQueue.main.async { | ||
NotificationCenter.default.post( | ||
name: .installationStatusChanged, | ||
object: nil, | ||
userInfo: ["packageName": package.name, "status": PackageInstallationStatus.installed] | ||
) | ||
completion(.success(())) | ||
} | ||
} catch { | ||
// Notify UI that installation failed | ||
installationStatus[package.name] = .failed(error) | ||
DispatchQueue.main.async { | ||
NotificationCenter.default.post( | ||
name: .installationStatusChanged, | ||
object: nil, | ||
userInfo: ["packageName": package.name, "status": PackageInstallationStatus.failed(error)] | ||
) | ||
completion(.failure(error)) | ||
} | ||
} | ||
|
||
runningInstallations.remove(package.name) | ||
processNextInstallations() | ||
} | ||
} | ||
|
||
/// Process next installations from the queue if possible | ||
private func processNextInstallations() { | ||
while runningInstallations.count < maxConcurrentInstallations && !installationQueue.isEmpty { | ||
let (package, completion) = installationQueue.removeFirst() | ||
if runningInstallations.contains(package.name) { | ||
continue | ||
} | ||
|
||
startInstallation(package: package, completion: completion) | ||
} | ||
} | ||
|
||
/// Cancel an installation if it's in the queue | ||
func cancelInstallation(packageName: String) { | ||
installationQueue.removeAll { $0.0.name == packageName } | ||
installationStatus[packageName] = .cancelled | ||
runningInstallations.remove(packageName) | ||
|
||
// Notify UI that installation was cancelled | ||
DispatchQueue.main.async { | ||
NotificationCenter.default.post( | ||
name: .installationStatusChanged, | ||
object: nil, | ||
userInfo: ["packageName": packageName, "status": PackageInstallationStatus.cancelled] | ||
) | ||
} | ||
processNextInstallations() | ||
} | ||
|
||
/// Get the current status of an installation | ||
func getInstallationStatus(packageName: String) -> PackageInstallationStatus { | ||
return installationStatus[packageName] ?? .notQueued | ||
} | ||
|
||
/// Cleans up installation status by removing completed or failed installations | ||
func cleanUpInstallationStatus() { | ||
let statusKeys = installationStatus.keys.map { $0 } | ||
for packageName in statusKeys { | ||
if let status = installationStatus[packageName] { | ||
switch status { | ||
case .installed, .failed, .cancelled: | ||
installationStatus.removeValue(forKey: packageName) | ||
case .queued, .installing, .notQueued: | ||
break | ||
} | ||
} | ||
} | ||
|
||
// If an item is in runningInstallations but not in an active state in the status dictionary, | ||
// it might be a stale reference | ||
let currentRunning = runningInstallations.map { $0 } | ||
for packageName in currentRunning { | ||
let status = installationStatus[packageName] | ||
if status != .installing { | ||
runningInstallations.remove(packageName) | ||
} | ||
} | ||
|
||
// Check for orphaned queue items | ||
installationQueue = installationQueue.filter { item, _ in | ||
return installationStatus[item.name] == .queued | ||
} | ||
} | ||
} | ||
|
||
/// Status of a package installation | ||
enum PackageInstallationStatus: Equatable { | ||
case notQueued | ||
case queued | ||
case installing | ||
case installed | ||
case failed(Error) | ||
case cancelled | ||
|
||
static func == (lhs: PackageInstallationStatus, rhs: PackageInstallationStatus) -> Bool { | ||
switch (lhs, rhs) { | ||
case (.notQueued, .notQueued): | ||
return true | ||
case (.queued, .queued): | ||
return true | ||
case (.installing, .installing): | ||
return true | ||
case (.installed, .installed): | ||
return true | ||
case (.cancelled, .cancelled): | ||
return true | ||
case (.failed, .failed): | ||
return true | ||
default: | ||
return false | ||
} | ||
} | ||
} | ||
|
||
extension Notification.Name { | ||
static let installationStatusChanged = Notification.Name("installationStatusChanged") | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// | ||
// PackageManagerError.swift | ||
// CodeEdit | ||
// | ||
// Created by Abe Malla on 5/12/25. | ||
// | ||
|
||
enum PackageManagerError: Error { | ||
case packageManagerNotInstalled | ||
case initializationFailed(String) | ||
case installationFailed(String) | ||
case invalidConfiguration | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.