Skip to content

Commit ee93abb

Browse files
Refactors, added settings loading
1 parent 6c9c48a commit ee93abb

13 files changed

+140
-76
lines changed

CodeEdit/Features/LSP/Registry/PackageManagers/PackageManagerProtocol.swift renamed to CodeEdit/Features/LSP/Registry/PackageManagerProtocol.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ protocol PackageManagerProtocol {
1919
extension PackageManagerProtocol {
2020
/// Creates the directory for the language server to be installed in
2121
internal func createDirectoryStructure(for packagePath: URL) throws {
22-
if !FileManager.default.fileExists(atPath: packagePath.path) {
22+
let decodedPath = packagePath.path.removingPercentEncoding ?? packagePath.path
23+
if !FileManager.default.fileExists(atPath: decodedPath) {
2324
try FileManager.default.createDirectory(
2425
at: packagePath,
2526
withIntermediateDirectories: true,
@@ -145,6 +146,17 @@ enum InstallationMethod: Equatable {
145146
}
146147
}
147148

149+
var version: String? {
150+
switch self {
151+
case .standardPackage(let source),
152+
.sourceBuild(let source, _),
153+
.binaryDownload(let source, _):
154+
return source.version
155+
case .unknown:
156+
return nil
157+
}
158+
}
159+
148160
var packageManagerType: PackageManagerType? {
149161
switch self {
150162
case .standardPackage(let source),

CodeEdit/Features/LSP/Registry/PackageManagers/GolangPackageManager.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class GolangPackageManager: PackageManagerProtocol {
5656

5757
// If there's a subpath, build the binary
5858
if let subpath = source.options["subpath"] {
59-
let binPath = packagePath.appendingPathComponent("bin")
59+
let binPath = packagePath.appending(path: "bin")
6060
if !FileManager.default.fileExists(atPath: binPath.path) {
6161
try FileManager.default.createDirectory(at: binPath, withIntermediateDirectories: true)
6262
}
@@ -91,7 +91,7 @@ class GolangPackageManager: PackageManagerProtocol {
9191
func getBinaryPath(for package: String) -> String {
9292
let binPath = installationDirectory.appending(path: package).appending(path: "bin")
9393
let binaryName = package.components(separatedBy: "/").last ?? package
94-
let specificBinPath = binPath.appendingPathComponent(binaryName).path
94+
let specificBinPath = binPath.appending(path: binaryName).path
9595
if FileManager.default.fileExists(atPath: specificBinPath) {
9696
return specificBinPath
9797
}
@@ -117,7 +117,7 @@ class GolangPackageManager: PackageManagerProtocol {
117117

118118
/// Clean up after a failed installation
119119
private func cleanupFailedInstallation(packagePath: URL) throws {
120-
let goSumPath = packagePath.appendingPathComponent("go.sum")
120+
let goSumPath = packagePath.appending(path: "go.sum")
121121
if FileManager.default.fileExists(atPath: goSumPath.path) {
122122
try FileManager.default.removeItem(at: goSumPath)
123123
}

CodeEdit/Features/LSP/Registry/PackageManagers/NPMPackageManager.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class NPMPackageManager: PackageManagerProtocol {
4040
in: packagePath.path, ["npm init --yes --scope=codeedit"]
4141
)
4242

43-
let npmrcPath = packagePath.appendingPathComponent(".npmrc")
43+
let npmrcPath = packagePath.appending(path: ".npmrc")
4444
if !FileManager.default.fileExists(atPath: npmrcPath.path) {
4545
try "install-strategy=shallow".write(to: npmrcPath, atomically: true, encoding: .utf8)
4646
}
@@ -77,7 +77,7 @@ class NPMPackageManager: PackageManagerProtocol {
7777
print("Successfully installed \(source.name)@\(source.version)")
7878
} catch {
7979
print("Installation failed: \(error)")
80-
let nodeModulesPath = packagePath.appendingPathComponent("node_modules").path
80+
let nodeModulesPath = packagePath.appending(path: "node_modules").path
8181
try? FileManager.default.removeItem(atPath: nodeModulesPath)
8282
throw error
8383
}
@@ -89,7 +89,7 @@ class NPMPackageManager: PackageManagerProtocol {
8989
.appending(path: package)
9090
.appending(path: "node_modules")
9191
.appending(path: ".bin")
92-
return binDirectory.appendingPathComponent(package).path
92+
return binDirectory.appending(path: package).path
9393
}
9494

9595
/// Checks if npm is installed
@@ -109,7 +109,7 @@ class NPMPackageManager: PackageManagerProtocol {
109109
/// Verify the installation was successful
110110
private func verifyInstallation(package: String, version: String) throws {
111111
let packagePath = installationDirectory.appending(path: package)
112-
let packageJsonPath = packagePath.appendingPathComponent("package.json").path
112+
let packageJsonPath = packagePath.appending(path: "package.json").path
113113

114114
// Verify package.json contains the installed package
115115
guard let packageJsonData = FileManager.default.contents(atPath: packageJsonPath),
@@ -132,8 +132,8 @@ class NPMPackageManager: PackageManagerProtocol {
132132

133133
// Verify the package exists in node_modules
134134
let packageDirectory = packagePath
135-
.appendingPathComponent("node_modules")
136-
.appendingPathComponent(package)
135+
.appending(path: "node_modules")
136+
.appending(path: package)
137137
guard FileManager.default.fileExists(atPath: packageDirectory.path) else {
138138
throw PackageManagerError.installationFailed("Package not found in node_modules")
139139
}

CodeEdit/Features/LSP/Registry/PackageManagers/PipPackageManager.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class PipPackageManager: PackageManagerProtocol {
2828
in: packagePath.path, ["python -m venv venv"]
2929
)
3030

31-
let requirementsPath = packagePath.appendingPathComponent("requirements.txt")
31+
let requirementsPath = packagePath.appending(path: "requirements.txt")
3232
if !FileManager.default.fileExists(atPath: requirementsPath.path) {
3333
try "# Package requirements\n".write(to: requirementsPath, atomically: true, encoding: .utf8)
3434
}
@@ -108,15 +108,15 @@ class PipPackageManager: PackageManagerProtocol {
108108

109109
private func getPipCommand(in packagePath: URL) -> String {
110110
let venvPip = "venv/bin/pip"
111-
return FileManager.default.fileExists(atPath: packagePath.appendingPathComponent(venvPip).path)
111+
return FileManager.default.fileExists(atPath: packagePath.appending(path: venvPip).path)
112112
? venvPip
113113
: "python -m pip"
114114
}
115115

116116
/// Update the requirements.txt file with the installed package and extras
117117
private func updateRequirements(packagePath: URL) async throws {
118118
let pipCommand = getPipCommand(in: packagePath)
119-
let requirementsPath = packagePath.appendingPathComponent("requirements.txt")
119+
let requirementsPath = packagePath.appending(path: "requirements.txt")
120120

121121
let freezeOutput = try await executeInDirectory(
122122
in: packagePath.path,

CodeEdit/Features/LSP/Registry/PackageSourceParser/PackageSourceParser+Cargo.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ extension PackageSourceParser {
99
static func parseCargoPackage(_ entry: RegistryItem) -> InstallationMethod {
1010
// Format: pkg:cargo/PACKAGE@VERSION?PARAMS
1111
let pkgPrefix = "pkg:cargo/"
12-
let sourceId = entry.source.id
12+
let sourceId = entry.source.id.removingPercentEncoding ?? entry.source.id
1313
guard sourceId.hasPrefix(pkgPrefix) else { return .unknown }
1414

1515
let pkgString = sourceId.dropFirst(pkgPrefix.count)

CodeEdit/Features/LSP/Registry/PackageSourceParser/PackageSourceParser+Gem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ extension PackageSourceParser {
99
static func parseRubyGem(_ entry: RegistryItem) -> InstallationMethod {
1010
// Format: pkg:gem/PACKAGE@VERSION?PARAMS
1111
let pkgPrefix = "pkg:gem/"
12-
let sourceId = entry.source.id
12+
let sourceId = entry.source.id.removingPercentEncoding ?? entry.source.id
1313
guard sourceId.hasPrefix(pkgPrefix) else { return .unknown }
1414

1515
let pkgString = sourceId.dropFirst(pkgPrefix.count)

CodeEdit/Features/LSP/Registry/PackageSourceParser/PackageSourceParser+Golang.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ extension PackageSourceParser {
99
static func parseGolangPackage(_ entry: RegistryItem) -> InstallationMethod {
1010
// Format: pkg:golang/PACKAGE@VERSION#SUBPATH?PARAMS
1111
let pkgPrefix = "pkg:golang/"
12-
let sourceId = entry.source.id
12+
let sourceId = entry.source.id.removingPercentEncoding ?? entry.source.id
1313
guard sourceId.hasPrefix(pkgPrefix) else { return .unknown }
1414

1515
let pkgString = sourceId.dropFirst(pkgPrefix.count)

CodeEdit/Features/LSP/Registry/PackageSourceParser/PackageSourceParser+NPM.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ extension PackageSourceParser {
99
static func parseNpmPackage(_ entry: RegistryItem) -> InstallationMethod {
1010
// Format: pkg:npm/PACKAGE@VERSION?PARAMS
1111
let pkgPrefix = "pkg:npm/"
12-
let sourceId = entry.source.id
12+
let sourceId = entry.source.id.removingPercentEncoding ?? entry.source.id
1313
guard sourceId.hasPrefix(pkgPrefix) else { return .unknown }
1414

1515
let pkgString = sourceId.dropFirst(pkgPrefix.count)

CodeEdit/Features/LSP/Registry/RegistryManager.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import ZIPFoundation
1111

1212
let homeDirectory = FileManager.default.homeDirectoryForCurrentUser
1313
let installPath = homeDirectory
14-
.appendingPathComponent("Library")
15-
.appendingPathComponent("Application Support")
16-
.appendingPathComponent("CodeEdit")
17-
.appendingPathComponent("extensions")
14+
.appending(path: "Library")
15+
.appending(path: "Application Support")
16+
.appending(path: "CodeEdit")
17+
.appending(path: "extensions")
1818

1919
final class RegistryManager {
2020
static let shared: RegistryManager = .init()
@@ -56,6 +56,9 @@ final class RegistryManager {
5656
return []
5757
}
5858

59+
@AppSettings(\.extensions.installedLanguageServers)
60+
var installedLanguageServers: [String: SettingsData.InstalledLanguageServer]
61+
5962
deinit {
6063
cleanupTimer?.invalidate()
6164
}
@@ -114,12 +117,18 @@ final class RegistryManager {
114117
}
115118
}
116119

117-
static func installPackage(package entry: RegistryItem) async throws {
120+
func installPackage(package entry: RegistryItem) async throws {
118121
let method = Self.parseRegistryEntry(entry)
119122
guard let manager = Self.createPackageManager(for: method) else {
120123
throw PackageManagerError.invalidConfiguration
121124
}
122125
try await manager.install(method: method)
126+
127+
installedLanguageServers[entry.name] = .init(
128+
packageName: entry.name,
129+
isEnabled: true,
130+
version: method.version ?? ""
131+
)
123132
}
124133

125134
/// Attempts downloading from `url`, with error handling and a retry policy
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
//
2-
// ExtensionsSettingsRowView.swift
2+
// LanguageServerRowView.swift
33
// CodeEdit
44
//
55
// Created by Abe Malla on 2/2/25.
66
//
77

88
import SwiftUI
99

10-
struct ExtensionsSettingsRowView: View, Equatable {
11-
let title: String
10+
struct LanguageServerRowView: View, Equatable {
11+
let packageName: String
1212
let subtitle: String
1313
let icon: String
1414
let onCancel: (() -> Void)
@@ -24,19 +24,23 @@ struct ExtensionsSettingsRowView: View, Equatable {
2424
@State private var installProgress: Double = 0.0
2525

2626
init(
27-
title: String,
27+
packageName: String,
2828
subtitle: String,
2929
icon: String,
30+
isInstalled: Bool = false,
31+
isEnabled: Bool = false,
3032
onCancel: @escaping (() -> Void),
3133
onInstall: @escaping () async -> Void
3234
) {
33-
self.title = title
35+
self.packageName = packageName
3436
self.subtitle = subtitle
3537
self.icon = icon
38+
self.isInstalled = isInstalled
39+
self.isEnabled = isEnabled
3640
self.onCancel = onCancel
3741
self.onInstall = onInstall
3842

39-
self.cleanedTitle = title
43+
self.cleanedTitle = packageName
4044
.replacingOccurrences(of: "-", with: " ")
4145
.replacingOccurrences(of: "_", with: " ")
4246
.split(separator: " ")
@@ -46,8 +50,7 @@ struct ExtensionsSettingsRowView: View, Equatable {
4650
if str == "ls" || str == "lsp" || str == "ci" || str == "cli" {
4751
return str.uppercased()
4852
}
49-
// Normal capitalization for other words
50-
return str.prefix(1).uppercased() + str.dropFirst()
53+
return str.capitalized
5154
}
5255
.joined(separator: " ")
5356
self.cleanedSubtitle = subtitle.replacingOccurrences(of: "\n", with: " ")
@@ -88,57 +91,75 @@ struct ExtensionsSettingsRowView: View, Equatable {
8891
@ViewBuilder
8992
private func installationButton() -> some View {
9093
if isInstalled {
91-
HStack {
92-
if isHovering {
93-
Button {
94-
isInstalling = false
95-
isInstalled = false
96-
} label: {
97-
Text("Remove")
98-
}
99-
}
100-
Toggle("", isOn: $isEnabled)
101-
.toggleStyle(.switch)
102-
.controlSize(.small)
103-
.labelsHidden()
104-
}
94+
installedRow()
10595
} else if isInstalling {
106-
ZStack {
107-
CECircularProgressView(progress: installProgress)
108-
.frame(width: 20, height: 20)
96+
isInstallingRow()
97+
} else if isHovering {
98+
isHoveringRow()
99+
}
100+
}
101+
102+
@ViewBuilder
103+
private func installedRow() -> some View {
104+
HStack {
105+
if isHovering {
109106
Button {
110107
isInstalling = false
111-
onCancel()
108+
isInstalled = false
112109
} label: {
113-
Image(systemName: "stop.fill")
114-
.font(.system(size: 8))
115-
.foregroundColor(.blue)
110+
Text("Remove")
116111
}
117-
.buttonStyle(.plain)
118-
.contentShape(Rectangle())
119112
}
120-
} else if isHovering {
121-
Button {
122-
isInstalling = true
123-
withAnimation(.linear(duration: 3)) {
124-
installProgress = 0.75
125-
}
126-
Task {
127-
await onInstall()
128-
withAnimation(.linear(duration: 1)) {
129-
installProgress = 1.0
130-
}
131-
isInstalling = false
132-
isInstalled = true
133-
isEnabled = true
113+
Toggle("", isOn: $isEnabled)
114+
.onChange(of: isEnabled) { newValue in
115+
RegistryManager.shared.installedLanguageServers[packageName]?.isEnabled = newValue
134116
}
117+
.toggleStyle(.switch)
118+
.controlSize(.small)
119+
.labelsHidden()
120+
}
121+
}
122+
123+
@ViewBuilder
124+
private func isInstallingRow() -> some View {
125+
ZStack {
126+
CECircularProgressView(progress: installProgress)
127+
.frame(width: 20, height: 20)
128+
Button {
129+
isInstalling = false
130+
onCancel()
135131
} label: {
136-
Text("Install")
132+
Image(systemName: "stop.fill")
133+
.font(.system(size: 8))
134+
.foregroundColor(.blue)
135+
}
136+
.buttonStyle(.plain)
137+
.contentShape(Rectangle())
138+
}
139+
}
140+
141+
@ViewBuilder
142+
private func isHoveringRow() -> some View {
143+
Button {
144+
isInstalling = true
145+
withAnimation(.linear(duration: 3)) {
146+
installProgress = 0.75
147+
}
148+
Task {
149+
await onInstall()
150+
withAnimation(.linear(duration: 1)) {
151+
installProgress = 1.0
152+
}
153+
isInstalling = false
154+
isInstalled = true
155+
isEnabled = true
137156
}
157+
} label: {
158+
Text("Install")
138159
}
139160
}
140161

141-
static func == (lhs: ExtensionsSettingsRowView, rhs: ExtensionsSettingsRowView) -> Bool {
142-
lhs.title == rhs.title && lhs.subtitle == rhs.subtitle
162+
static func == (lhs: LanguageServerRowView, rhs: LanguageServerRowView) -> Bool {
163+
lhs.packageName == rhs.packageName && lhs.subtitle == rhs.subtitle
143164
}
144165
}

0 commit comments

Comments
 (0)