Skip to content

Commit 5ea9de5

Browse files
Language server installation menu
1 parent f24981a commit 5ea9de5

File tree

21 files changed

+2412
-6
lines changed

21 files changed

+2412
-6
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 55;
6+
objectVersion = 70;
77
objects = {
88

99
/* Begin PBXBuildFile section */
@@ -64,6 +64,8 @@
6464
3000516A2BBD3A8200A98562 /* ServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300051692BBD3A8200A98562 /* ServiceType.swift */; };
6565
3000516C2BBD3A9500A98562 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */; };
6666
3026F50F2AC006C80061227E /* InspectorAreaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */; };
67+
304672DF2D5235210037C8F1 /* Registry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304672DE2D52351F0037C8F1 /* Registry.swift */; };
68+
30818CB52D4E563900967860 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 30818CB42D4E563900967860 /* ZIPFoundation */; };
6769
30AB4EBB2BF718A100ED4431 /* DeveloperSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB4EBA2BF718A100ED4431 /* DeveloperSettings.swift */; };
6870
30AB4EBD2BF71CA800ED4431 /* DeveloperSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB4EBC2BF71CA800ED4431 /* DeveloperSettingsView.swift */; };
6971
30AB4EC22BF7253200ED4431 /* KeyValueTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB4EC12BF7253200ED4431 /* KeyValueTable.swift */; };
@@ -770,6 +772,7 @@
770772
300051692BBD3A8200A98562 /* ServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceType.swift; sourceTree = "<group>"; };
771773
3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceWrapper.swift; sourceTree = "<group>"; };
772774
3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorAreaViewModel.swift; sourceTree = "<group>"; };
775+
304672DE2D52351F0037C8F1 /* Registry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Registry.swift; sourceTree = "<group>"; };
773776
30AB4EBA2BF718A100ED4431 /* DeveloperSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettings.swift; sourceTree = "<group>"; };
774777
30AB4EBC2BF71CA800ED4431 /* DeveloperSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsView.swift; sourceTree = "<group>"; };
775778
30AB4EC12BF7253200ED4431 /* KeyValueTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueTable.swift; sourceTree = "<group>"; };
@@ -1339,6 +1342,11 @@
13391342
EC0870F62A455F6400EB8692 /* ProjectNavigatorViewController+NSMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectNavigatorViewController+NSMenuDelegate.swift"; sourceTree = "<group>"; };
13401343
/* End PBXFileReference section */
13411344

1345+
/* Begin PBXFileSystemSynchronizedRootGroup section */
1346+
304672E02D5244E50037C8F1 /* Extensions */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Extensions; sourceTree = "<group>"; };
1347+
30818CAE2D4E39B600967860 /* Registry */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Registry; sourceTree = "<group>"; };
1348+
/* End PBXFileSystemSynchronizedRootGroup section */
1349+
13421350
/* Begin PBXFrameworksBuildPhase section */
13431351
2BE487E928245162003F3F64 /* Frameworks */ = {
13441352
isa = PBXFrameworksBuildPhase;
@@ -1352,6 +1360,7 @@
13521360
buildActionMask = 2147483647;
13531361
files = (
13541362
6C85BB402C2105ED00EB5DEF /* CodeEditKit in Frameworks */,
1363+
30818CB52D4E563900967860 /* ZIPFoundation in Frameworks */,
13551364
6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */,
13561365
58F2EB1E292FB954004A9BDE /* Sparkle in Frameworks */,
13571366
6C147C4529A329350089B630 /* OrderedCollections in Frameworks */,
@@ -1617,6 +1626,7 @@
16171626
children = (
16181627
6C3B4CD22D0E2C5400C6759E /* Editor */,
16191628
6CD26C732C8EA71F00ADBA38 /* LanguageServer */,
1629+
30818CAE2D4E39B600967860 /* Registry */,
16201630
6CD26C742C8EA79100ADBA38 /* Service */,
16211631
30B087FA2C0D53080063A882 /* LSPUtil.swift */,
16221632
);
@@ -3179,6 +3189,7 @@
31793189
6CD26C882C8F91B600ADBA38 /* LSP */ = {
31803190
isa = PBXGroup;
31813191
children = (
3192+
304672DE2D52351F0037C8F1 /* Registry.swift */,
31823193
6C7D6D452C9092EC00B69EE0 /* BufferingServerConnection.swift */,
31833194
6CD26C892C8F91ED00ADBA38 /* LanguageServer+DocumentTests.swift */,
31843195
6C3B4CD32D0E2CB000C6759E /* SemanticTokenMapTests.swift */,
@@ -3256,6 +3267,7 @@
32563267
children = (
32573268
B6E41C6E29DD15540088F9F4 /* AccountsSettings */,
32583269
30AB4EB72BF7170B00ED4431 /* DeveloperSettings */,
3270+
304672E02D5244E50037C8F1 /* Extensions */,
32593271
B61DA9E129D929F900BF4A43 /* GeneralSettings */,
32603272
B6CF632629E5417C0085880A /* Keybindings */,
32613273
B6F0516E29D9E35300D72287 /* LocationsSettings */,
@@ -3805,6 +3817,10 @@
38053817
6C7B1C762A1D57CE005CBBFC /* PBXTargetDependency */,
38063818
2BE487F328245162003F3F64 /* PBXTargetDependency */,
38073819
);
3820+
fileSystemSynchronizedGroups = (
3821+
304672E02D5244E50037C8F1 /* Extensions */,
3822+
30818CAE2D4E39B600967860 /* Registry */,
3823+
);
38083824
name = CodeEdit;
38093825
packageProductDependencies = (
38103826
2816F593280CF50500DD548B /* CodeEditSymbols */,
@@ -3826,6 +3842,7 @@
38263842
6CB94D022CA1205100E8651C /* AsyncAlgorithms */,
38273843
6CC00A8A2CBEF150004E8134 /* CodeEditSourceEditor */,
38283844
6C05CF9D2CDE8699006AAECD /* CodeEditSourceEditor */,
3845+
30818CB42D4E563900967860 /* ZIPFoundation */,
38293846
);
38303847
productName = CodeEdit;
38313848
productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */;
@@ -3924,6 +3941,7 @@
39243941
6C4E37FA2C73E00700AEE7B5 /* XCRemoteSwiftPackageReference "SwiftTerm" */,
39253942
6CB94D012CA1205100E8651C /* XCRemoteSwiftPackageReference "swift-async-algorithms" */,
39263943
6C05CF9C2CDE8699006AAECD /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */,
3944+
30818CB32D4E563900967860 /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
39273945
);
39283946
productRefGroup = B658FB2D27DA9E0F00EA4DBD /* Products */;
39293947
projectDirPath = "";
@@ -4621,6 +4639,7 @@
46214639
6130535F2B23A31300D767E3 /* MemorySearchTests.swift in Sources */,
46224640
587B61012934170A00D5CD8F /* UnitTests_Extensions.swift in Sources */,
46234641
6C1F3DA22C18C55800F6DEF6 /* ShellIntegrationTests.swift in Sources */,
4642+
304672DF2D5235210037C8F1 /* Registry.swift in Sources */,
46244643
283BDCC52972F236002AFF81 /* AcknowledgementsTests.swift in Sources */,
46254644
6CD26C8A2C8F91ED00ADBA38 /* LanguageServer+DocumentTests.swift in Sources */,
46264645
4EE96ECB2960565E00FFBEA8 /* DocumentsUnitTests.swift in Sources */,
@@ -5754,6 +5773,14 @@
57545773
minimumVersion = 0.13.2;
57555774
};
57565775
};
5776+
30818CB32D4E563900967860 /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = {
5777+
isa = XCRemoteSwiftPackageReference;
5778+
repositoryURL = "https://github.com/weichsel/ZIPFoundation";
5779+
requirement = {
5780+
kind = upToNextMajorVersion;
5781+
minimumVersion = 0.9.19;
5782+
};
5783+
};
57575784
30CB648F2C16CA8100CC8A9E /* XCRemoteSwiftPackageReference "LanguageServerProtocol" */ = {
57585785
isa = XCRemoteSwiftPackageReference;
57595786
repositoryURL = "https://github.com/ChimeHQ/LanguageServerProtocol";
@@ -5866,6 +5893,11 @@
58665893
package = 2816F592280CF50500DD548B /* XCRemoteSwiftPackageReference "CodeEditSymbols" */;
58675894
productName = CodeEditSymbols;
58685895
};
5896+
30818CB42D4E563900967860 /* ZIPFoundation */ = {
5897+
isa = XCSwiftPackageProductDependency;
5898+
package = 30818CB32D4E563900967860 /* XCRemoteSwiftPackageReference "ZIPFoundation" */;
5899+
productName = ZIPFoundation;
5900+
};
58695901
30CB64902C16CA8100CC8A9E /* LanguageServerProtocol */ = {
58705902
isa = XCSwiftPackageProductDependency;
58715903
package = 30CB648F2C16CA8100CC8A9E /* XCRemoteSwiftPackageReference "LanguageServerProtocol" */;

CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CodeEdit/Features/CEWorkspaceSettings/Models/CEWorkspaceSettings.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ final class CEWorkspaceSettings: ObservableObject {
1818
private(set) var folderURL: URL
1919

2020
private var settingsURL: URL {
21-
folderURL.appendingPathComponent("settings").appendingPathExtension("json")
21+
folderURL.appending(path: "settings").appending(path: "json")
2222
}
2323

2424
init(workspaceURL: URL) {
25-
folderURL = workspaceURL.appendingPathComponent(".codeedit", isDirectory: true)
25+
folderURL = workspaceURL.appending(path: ".codeedit", directoryHint: .isDirectory)
2626
loadSettings()
2727

2828
storeTask = $settings
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
//
2+
// PackageManagerFactory.swift
3+
// CodeEdit
4+
//
5+
// Created by Abe Malla on 2/3/25.
6+
//
7+
8+
import Foundation
9+
10+
/// Factory for creating the appropriate package manager based on installation method
11+
final class PackageManagerFactory {
12+
let installationDirectory: URL
13+
14+
init(installationDirectory: URL) {
15+
self.installationDirectory = installationDirectory
16+
}
17+
18+
/// Create the appropriate package manager for the given installation method
19+
func createPackageManager(for method: InstallationMethod) -> PackageManagerProtocol? {
20+
switch method.packageManagerType {
21+
case .npm:
22+
return NPMPackageManager(installationDirectory: installationDirectory)
23+
case .cargo:
24+
return CargoPackageManager(installationDirectory: installationDirectory)
25+
case .pip:
26+
return PipPackageManager(installationDirectory: installationDirectory)
27+
case .golang:
28+
return GolangPackageManager(installationDirectory: installationDirectory)
29+
case .nuget, .opam, .customBuild, .gem:
30+
// TODO: IMPLEMENT OTHER PACKAGE MANAGERS
31+
return nil
32+
case .github:
33+
return createPackageManagerFromGithub(for: method)
34+
case .none:
35+
return nil
36+
}
37+
}
38+
39+
/// Parse a registry entry and create the appropriate installation method
40+
static func parseRegistryEntry(_ entry: [String: Any]) -> InstallationMethod? {
41+
guard let source = entry["source"] as? [String: Any],
42+
let sourceId = source["id"] as? String else {
43+
return nil
44+
}
45+
46+
let buildInstructions = source["build"] as? [[String: Any]]
47+
48+
// Detect the build tool from the registry entry
49+
var buildTool: String?
50+
if let bin = entry["bin"] as? [String: String] {
51+
let binValues = Array(bin.values)
52+
if !binValues.isEmpty {
53+
let value = binValues[0]
54+
if value.hasPrefix("cargo:") {
55+
buildTool = "cargo"
56+
} else if value.hasPrefix("npm:") {
57+
buildTool = "npm"
58+
} else if value.hasPrefix("pypi:") {
59+
buildTool = "pip"
60+
} else if value.hasPrefix("gem:") {
61+
buildTool = "gem"
62+
} else if value.hasPrefix("golang:") {
63+
buildTool = "golang"
64+
}
65+
}
66+
}
67+
68+
var method = PackageSourceParser.parse(sourceId, buildInstructions: buildInstructions)
69+
70+
if let buildTool = buildTool {
71+
switch method {
72+
case .standardPackage(var source):
73+
var options = source.options
74+
options["buildTool"] = buildTool
75+
source = PackageSource(
76+
sourceId: source.sourceId,
77+
type: source.type,
78+
name: source.name,
79+
version: source.version,
80+
subpath: source.subpath,
81+
repositoryUrl: source.repositoryUrl,
82+
gitReference: source.gitReference,
83+
options: options
84+
)
85+
method = .standardPackage(source: source)
86+
case .sourceBuild(var source, let instructions):
87+
var options = source.options
88+
options["buildTool"] = buildTool
89+
source = PackageSource(
90+
sourceId: source.sourceId,
91+
type: source.type,
92+
name: source.name,
93+
version: source.version,
94+
subpath: source.subpath,
95+
repositoryUrl: source.repositoryUrl,
96+
gitReference: source.gitReference,
97+
options: options
98+
)
99+
method = .sourceBuild(source: source, buildInstructions: instructions)
100+
case .binaryDownload(var source, let url):
101+
var options = source.options
102+
options["buildTool"] = buildTool
103+
source = PackageSource(
104+
sourceId: source.sourceId,
105+
type: source.type,
106+
name: source.name,
107+
version: source.version,
108+
subpath: source.subpath,
109+
repositoryUrl: source.repositoryUrl,
110+
gitReference: source.gitReference,
111+
options: options
112+
)
113+
method = .binaryDownload(source: source, url: url)
114+
case .unknown:
115+
break
116+
}
117+
}
118+
return method
119+
}
120+
121+
/// Install a package from a registry entry
122+
func installFromRegistryEntry(_ entry: [String: Any]) async throws {
123+
guard let method = PackageManagerFactory.parseRegistryEntry(entry),
124+
let manager = createPackageManager(for: method) else {
125+
throw PackageManagerError.invalidConfiguration
126+
}
127+
try await manager.install(method: method)
128+
}
129+
130+
/// Install a package from a source ID string
131+
func installFromSourceID(_ sourceID: String) async throws {
132+
let method = PackageSourceParser.parse(sourceID)
133+
guard let manager = createPackageManager(for: method) else {
134+
throw PackageManagerError.packageManagerNotInstalled
135+
}
136+
try await manager.install(method: method)
137+
}
138+
139+
private func createPackageManagerFromGithub(for method: InstallationMethod) -> PackageManagerProtocol? {
140+
if case let .sourceBuild(source, instructions) = method {
141+
if let buildTool = source.options["buildTool"] {
142+
switch buildTool {
143+
case "cargo": return CargoPackageManager(installationDirectory: installationDirectory)
144+
case "npm": return NPMPackageManager(installationDirectory: installationDirectory)
145+
case "pip": return PipPackageManager(installationDirectory: installationDirectory)
146+
case "golang": return GolangPackageManager(installationDirectory: installationDirectory)
147+
default: break
148+
}
149+
}
150+
151+
// If no buildTool option, try to determine from build instructions
152+
for instruction in instructions {
153+
for command in instruction.commands {
154+
if command.contains("cargo ") {
155+
return CargoPackageManager(installationDirectory: installationDirectory)
156+
} else if command.contains("npm ") {
157+
return NPMPackageManager(installationDirectory: installationDirectory)
158+
} else if command.contains("pip ") || command.contains("python ") {
159+
return PipPackageManager(installationDirectory: installationDirectory)
160+
} else if command.contains("go ") {
161+
return GolangPackageManager(installationDirectory: installationDirectory)
162+
}
163+
}
164+
}
165+
166+
// Check the binary path for clues if needed
167+
let binPath = instructions.first?.binaryPath ?? ""
168+
if binPath.contains("target/release") || binPath.hasSuffix(".rs") {
169+
return CargoPackageManager(installationDirectory: installationDirectory)
170+
} else if binPath.contains("node_modules") {
171+
return NPMPackageManager(installationDirectory: installationDirectory)
172+
} else if binPath.contains(".py") {
173+
return PipPackageManager(installationDirectory: installationDirectory)
174+
} else if binPath.hasSuffix(".go") || binPath.contains("/go/bin") {
175+
return GolangPackageManager(installationDirectory: installationDirectory)
176+
}
177+
}
178+
179+
// Default to cargo
180+
return CargoPackageManager(installationDirectory: installationDirectory)
181+
}
182+
}

0 commit comments

Comments
 (0)