Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CodeEdit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@
DE513F52281B672D002260B9 /* EditorTabBarAccessory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE513F51281B672D002260B9 /* EditorTabBarAccessory.swift */; };
DE6F77872813625500D00A76 /* EditorTabBarDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6F77862813625500D00A76 /* EditorTabBarDivider.swift */; };
EC0870F72A455F6400EB8692 /* ProjectNavigatorViewController+NSMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0870F62A455F6400EB8692 /* ProjectNavigatorViewController+NSMenuDelegate.swift */; };
F3176C932CC2177100670B85 /* codeedit_shell_integration.fish in Resources */ = {isa = PBXBuildFile; fileRef = F3176C922CC2177100670B85 /* codeedit_shell_integration.fish */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -1282,6 +1283,7 @@
DE513F51281B672D002260B9 /* EditorTabBarAccessory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBarAccessory.swift; sourceTree = "<group>"; };
DE6F77862813625500D00A76 /* EditorTabBarDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBarDivider.swift; sourceTree = "<group>"; };
EC0870F62A455F6400EB8692 /* ProjectNavigatorViewController+NSMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectNavigatorViewController+NSMenuDelegate.swift"; sourceTree = "<group>"; };
F3176C922CC2177100670B85 /* codeedit_shell_integration.fish */ = {isa = PBXFileReference; lastKnownFileType = text; path = codeedit_shell_integration.fish; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -1598,6 +1600,7 @@
isa = PBXGroup;
children = (
3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */,
F3176C922CC2177100670B85 /* codeedit_shell_integration.fish */,
3E0196722A3921AC002648D8 /* codeedit_shell_integration_rc.zsh */,
6C48B5D52C0D08C5001E9955 /* codeedit_shell_integration_profile.zsh */,
6C48B5D72C0D5DB5001E9955 /* codeedit_shell_integration_login.zsh */,
Expand Down Expand Up @@ -3825,6 +3828,7 @@
3E0196732A3921AC002648D8 /* codeedit_shell_integration_rc.zsh in Resources */,
58A5DFA529339F6400D1BD5D /* default_keybindings.json in Resources */,
6C48B5D42C0D0743001E9955 /* codeedit_shell_integration_env.zsh in Resources */,
F3176C932CC2177100670B85 /* codeedit_shell_integration.fish in Resources */,
3E01967A2A392B45002648D8 /* codeedit_shell_integration.bash in Resources */,
D7211D4727E06BFE008F2ED7 /* Localizable.strings in Resources */,
6C48B5D62C0D08C5001E9955 /* codeedit_shell_integration_profile.zsh in Resources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,12 @@ extension SettingsData {

/// The shell options.
/// - **bash**: uses the default bash shell
/// - **fish**: uses the default fish shell
/// - **zsh**: uses the ZSH shell
/// - **system**: uses the system default shell (most likely ZSH)
enum TerminalShell: String, Codable, Hashable {
case bash
case fish
case zsh
case system
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ private extension TerminalSettingsView {
.tag(SettingsData.TerminalShell.zsh)
Text("Bash")
.tag(SettingsData.TerminalShell.bash)
Text("Fish")
.tag(SettingsData.TerminalShell.fish)
}
}

Expand Down Expand Up @@ -95,7 +97,7 @@ private extension TerminalSettingsView {
VStack {
Toggle("Shell Integration", isOn: $settings.useShellIntegration)
// swiftlint:disable:next line_length
.help("CodeEdit supports integrating with common shells such as Bash and Zsh. This enables features like terminal title detection.")
.help("CodeEdit supports integrating with common shells such as Bash, Fish and Zsh. This enables features like terminal title detection.")
if !settings.useShellIntegration {
HStack {
Image(systemName: "exclamationmark.triangle.fill")
Expand Down
34 changes: 33 additions & 1 deletion CodeEdit/Features/TerminalEmulator/Model/Shell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ import Foundation
enum Shell: String, CaseIterable {
case bash
case zsh
case fish

var url: String {
switch self {
case .bash:
"/bin/bash"
case .zsh:
"/bin/zsh"
case .fish:
"/opt/homebrew/bin/fish"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nis-ship-it Is this safe to assume that the fish binary will always be installed by homebrew?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it might be different so I will try to figure on how to obtain path

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might look at how other editors handle this

}
}

var isSh: Bool {
switch self {
case .bash, .zsh:
case .bash, .zsh, .fish:
return true
}
}
Expand Down Expand Up @@ -84,6 +87,8 @@ enum Shell: String, CaseIterable {
"/bin/bash"
case .zsh:
"/bin/zsh"
case .fish:
Shell.getFishShellPath()
}
}

Expand All @@ -96,4 +101,31 @@ enum Shell: String, CaseIterable {
}
return currentUser.shell
}

static func getFishShellPath() -> String {
let command = "which fish"
let process = Process()
let outputPipe = Pipe()

process.executableURL = URL(fileURLWithPath: "/bin/zsh")
process.arguments = ["--login", "-c", command]
process.standardOutput = outputPipe
process.standardError = outputPipe

do {
try process.run()
process.waitUntilExit()

let data = outputPipe.fileHandleForReading.readDataToEndOfFile()
guard let shellPath = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines), !shellPath.isEmpty
else {
print("Fish shell not found.")
return ""
}
return shellPath
} catch {
print("Error running command: \(error.localizedDescription)")
return ""
}
}
}
34 changes: 34 additions & 0 deletions CodeEdit/Features/TerminalEmulator/Model/ShellIntegration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ enum ShellIntegration {
enum Error: Swift.Error, LocalizedError {
case bashShellFileNotFound
case zshShellFileNotFound
case fishShellFileNotFound

var localizedDescription: String {
switch self {
case .bashShellFileNotFound:
return "Failed to find bash injection file."
case .zshShellFileNotFound:
return "Failed to find zsh injection file."
case .fishShellFileNotFound:
return "Failed to find fish injection file."
}
}
}
Expand Down Expand Up @@ -64,6 +67,8 @@ enum ShellIntegration {
try bash(&args)
case .zsh:
try zsh(&args, &environment, useLogin)
case .fish:
try fish(&args, &environment)
}

if useLogin {
Expand Down Expand Up @@ -155,6 +160,35 @@ enum ShellIntegration {
try copyFile(rcScriptURL, toDir: tempDir.appending(path: ".zshrc"))
}

/// Sets up the `fish` shell integration.
///
/// Sets the fish init directory to a temporary directory containing CE setup scripts. Each script corresponds to an
/// available zsh init script, and will source the user's real init script.
/// Also sets up an interactive session using the `-i` parameter.
///
/// - Parameters:
/// - shellExecArgs: The args to use for shell exec, will be modified by this function.
/// - environment: Environment variables in an array. Formatted as `EnvVar=Value`. Will be modified by this
/// function.
private static func fish(_ args: inout [String], _ environment: inout [String]) throws {
// Set the args for executing Fish shell
args.append("-i")

// Locate the Fish integration script
guard let fishScriptURL = Bundle.main.url(
forResource: "codeedit_shell_integration",
withExtension: "fish"
) else {
throw Error.fishShellFileNotFound
}

// Make a temporary directory for storing the integration script
let tempDir = try makeTempDir(forShell: .fish)

// Copy the Fish integration script to the temporary directory
try copyFile(fishScriptURL, toDir: tempDir.appending(path: "config.fish"))
}

/// Helper function for safely copying files, removing existing ones if needed.
/// - Parameters:
/// - origin: The path of the file to copy from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ struct TerminalEmulatorView: NSViewRepresentable {
return "/bin/bash"
case .zsh:
return "/bin/zsh"
case .fish:
return Shell.getFishShellPath()
}
}

Expand Down
14 changes: 14 additions & 0 deletions CodeEdit/ShellIntegration/codeedit_shell_integration.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

# Check if the FISH_CONFIG_DIR is set and if the config.fish exists
if test -n "$USER_CONFIG_DIR" -a -f "$USER_CONFIG_DIR/config.fish"
set CE_CONFIG_DIR $FISH_CONFIG_DIR
set FISH_CONFIG_DIR $USER_CONFIG_DIR

# Source the user's config.fish
. "$USER_CONFIG_DIR/config.fish"

# Restore the original FISH_CONFIG_DIR
set FISH_CONFIG_DIR $CE_CONFIG_DIR
end

# Additional integration functions can go here