Skip to content

Commit 82d39cc

Browse files
Improve LSP Install UX (#2101)
### Description Implements an installing progress view for language servers from the mason registry. - Package managers have been reworked to return a list of 'steps' to execute. - Created a model for running steps and waiting for confirmation if required before a step. - Added UI that observes the running model for executed steps and output, as well as errors. ### Related Issues * #1997 ### Checklist - [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md) - [x] The issues this PR addresses are related to each other - [x] My changes generate no new warnings - [x] My code builds and runs on my machine - [x] My changes are all related to the related issue above - [x] I documented my code ### Screenshots https://github.com/user-attachments/assets/6f0f8a62-1034-4c15-bebe-8d01fff0019e
1 parent bdf21ac commit 82d39cc

39 files changed

+2023
-1194
lines changed

CodeEdit/Features/ActivityViewer/Notifications/TaskNotificationHandler.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ final class TaskNotificationHandler: ObservableObject {
132132
/// - toWorkspace: The workspace to restrict the task to. Defaults to `nil`, which is received by all workspaces.
133133
/// - action: The action being taken on the task.
134134
/// - model: The task contents.
135+
@MainActor
135136
static func postTask(toWorkspace: URL? = nil, action: Action, model: TaskNotificationModel) {
136137
NotificationCenter.default.post(name: .taskNotification, object: nil, userInfo: [
137138
"id": model.id,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// ErrorDescriptionLabel.swift
3+
// CodeEdit
4+
//
5+
// Created by Khan Winter on 8/14/25.
6+
//
7+
8+
import SwiftUI
9+
10+
struct ErrorDescriptionLabel: View {
11+
let error: Error
12+
13+
var body: some View {
14+
VStack(alignment: .leading) {
15+
if let error = error as? LocalizedError {
16+
if let description = error.errorDescription {
17+
Text(description)
18+
}
19+
20+
if let reason = error.failureReason {
21+
Text(reason)
22+
}
23+
24+
if let recoverySuggestion = error.recoverySuggestion {
25+
Text(recoverySuggestion)
26+
}
27+
} else {
28+
Text(error.localizedDescription)
29+
}
30+
}
31+
}
32+
}
33+
34+
#Preview {
35+
ErrorDescriptionLabel(error: CancellationError())
36+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// PackageManagerError.swift
3+
// CodeEdit
4+
//
5+
// Created by Abe Malla on 5/12/25.
6+
//
7+
8+
import Foundation
9+
10+
enum PackageManagerError: Error, LocalizedError {
11+
case unknown
12+
case packageManagerNotInstalled
13+
case initializationFailed(String)
14+
case installationFailed(String)
15+
case invalidConfiguration
16+
17+
var errorDescription: String? {
18+
switch self {
19+
case .unknown:
20+
"Unknown error occurred"
21+
case .packageManagerNotInstalled:
22+
"The required package manager is not installed."
23+
case .initializationFailed:
24+
"Installation directory initialization failed."
25+
case .installationFailed:
26+
"Package installation failed."
27+
case .invalidConfiguration:
28+
"The package registry contained an invalid installation configuration."
29+
}
30+
}
31+
32+
var failureReason: String? {
33+
switch self {
34+
case .unknown:
35+
nil
36+
case .packageManagerNotInstalled:
37+
nil
38+
case .initializationFailed(let string):
39+
string
40+
case .installationFailed(let string):
41+
string
42+
case .invalidConfiguration:
43+
nil
44+
}
45+
}
46+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// RegistryManagerError.swift
3+
// CodeEdit
4+
//
5+
// Created by Abe Malla on 5/12/25.
6+
//
7+
8+
import Foundation
9+
10+
enum RegistryManagerError: Error, LocalizedError {
11+
case installationRunning
12+
case invalidResponse(statusCode: Int)
13+
case downloadFailed(url: URL, error: Error)
14+
case maxRetriesExceeded(url: URL, lastError: Error)
15+
case writeFailed(error: Error)
16+
case failedToSaveRegistryCache
17+
18+
var errorDescription: String? {
19+
switch self {
20+
case .installationRunning:
21+
"A package is already being installed."
22+
case .invalidResponse(let statusCode):
23+
"Invalid response received: \(statusCode)"
24+
case .downloadFailed(let url, _):
25+
"Download for \(url) error."
26+
case .maxRetriesExceeded(let url, _):
27+
"Maximum retries exceeded for url: \(url)"
28+
case .writeFailed:
29+
"Failed to write to file."
30+
case .failedToSaveRegistryCache:
31+
"Failed to write to registry cache."
32+
}
33+
}
34+
35+
var failureReason: String? {
36+
switch self {
37+
case .installationRunning, .invalidResponse, .failedToSaveRegistryCache:
38+
return nil
39+
case .downloadFailed(_, let error), .maxRetriesExceeded(_, let error), .writeFailed(let error):
40+
return if let error = error as? LocalizedError {
41+
error.errorDescription
42+
} else {
43+
error.localizedDescription
44+
}
45+
}
46+
}
47+
}

CodeEdit/Features/LSP/Registry/InstallationQueueManager.swift

Lines changed: 0 additions & 185 deletions
This file was deleted.

CodeEdit/Features/LSP/Registry/InstallationMethod.swift renamed to CodeEdit/Features/LSP/Registry/Model/InstallationMethod.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,42 @@ enum InstallationMethod: Equatable {
5050
return nil
5151
}
5252
}
53+
54+
func packageManager(installPath: URL) -> PackageManagerProtocol? {
55+
switch packageManagerType {
56+
case .npm:
57+
return NPMPackageManager(installationDirectory: installPath)
58+
case .cargo:
59+
return CargoPackageManager(installationDirectory: installPath)
60+
case .pip:
61+
return PipPackageManager(installationDirectory: installPath)
62+
case .golang:
63+
return GolangPackageManager(installationDirectory: installPath)
64+
case .github, .sourceBuild:
65+
return GithubPackageManager(installationDirectory: installPath)
66+
case .nuget, .opam, .gem, .composer:
67+
// TODO: IMPLEMENT OTHER PACKAGE MANAGERS
68+
return nil
69+
default:
70+
return nil
71+
}
72+
}
73+
74+
var installerDescription: String {
75+
guard let packageManagerType else { return "Unknown" }
76+
switch packageManagerType {
77+
case .npm, .cargo, .golang, .pip, .sourceBuild, .github:
78+
return packageManagerType.userDescription
79+
case .nuget, .opam, .gem, .composer:
80+
return "(Unsupported) \(packageManagerType.userDescription)"
81+
}
82+
}
83+
84+
var packageDescription: String? {
85+
guard let packageName else { return nil }
86+
if let version {
87+
return "\(packageName)@\(version)"
88+
}
89+
return packageName
90+
}
5391
}

0 commit comments

Comments
 (0)