Skip to content

Commit b2b2c0b

Browse files
authored
feat: Improve Shell Integration Setup Process (#1387) (#1388)
* feat: Set `TERM_PROGRAM` for terminal Signed-off-by: Qian Qian "Cubik"‎ <cubik65536@cubik65536.top> * style: Replace unnecessary `var` with `let` Signed-off-by: Qian Qian "Cubik"‎ <cubik65536@cubik65536.top> * feat: Improve Shell Integration Setup Process Signed-off-by: Qian Qian "Cubik"‎ <cubik65536@cubik65536.top> * style: Fix SwiftLint Errors Signed-off-by: Qian Qian "Cubik"‎ <cubik65536@cubik65536.top> * style: Fix Trailing Whitespace Violation Signed-off-by: Qian Qian "Cubik"‎ <cubik65536@cubik65536.top> --------- Signed-off-by: Qian Qian "Cubik"‎ <cubik65536@cubik65536.top>
1 parent fad219d commit b2b2c0b

File tree

2 files changed

+86
-13
lines changed

2 files changed

+86
-13
lines changed

CodeEdit/Features/DebugArea/Views/DebugAreaTerminalTab.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ struct DebugAreaTerminalTab: View {
1919
@FocusState private var isFocused: Bool
2020

2121
var body: some View {
22-
var terminalTitle = Binding<String>(
22+
let terminalTitle = Binding<String>(
2323
get: {
2424
self.terminal.title
2525
}, set: {

CodeEdit/Features/TerminalEmulator/TerminalEmulatorView.swift

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,6 @@ struct TerminalEmulatorView: NSViewRepresentable {
9292
}
9393
}
9494

95-
private func setupShellTitle(shell: String) {
96-
if let shellSetupScript = Bundle.main.url(forResource: "codeedit_shell_integration", withExtension: shell) {
97-
let scriptPath = (shellSetupScript.absoluteString[7..<shellSetupScript.absoluteString.count]) ?? ""
98-
terminal.send(txt: "source \(scriptPath)\n")
99-
}
100-
101-
terminal.send(txt: "clear\n")
102-
}
103-
10495
private func getTerminalCursor() -> CursorStyle {
10596
let blink = terminalSettings.cursorBlink
10697
switch terminalSettings.cursorStyle {
@@ -128,6 +119,84 @@ struct TerminalEmulatorView: NSViewRepresentable {
128119
return String(cString: pwd.pw_shell)
129120
}
130121

122+
/// Check if the source command for shell integration already exists
123+
/// Returns true if it already exists or encountered an error, no new commands will be added to user's source file
124+
/// Returns false if it's not there, new commands will be added to user's source file
125+
private func shellIntegrationInstalled(sourceScriptPath: String, command: String) -> Bool {
126+
do {
127+
// Get user's shell's source file
128+
let sourceScript = try String(contentsOfFile: sourceScriptPath)
129+
let sourceScriptSeperatedByLines = sourceScript.components(separatedBy: .newlines)
130+
// Check line by line
131+
for line in sourceScriptSeperatedByLines where line == command {
132+
// If one line matches the command, no new commands are needed
133+
return true
134+
}
135+
// If no line matches the command, new command is needed
136+
return false
137+
} catch {
138+
if let error = error as NSError? {
139+
switch error._code {
140+
case 260:
141+
// If error 260 is thrown, it's just the source file is missing
142+
// Create a new file and add new command
143+
FileManager.default.createFile(atPath: sourceScriptPath, contents: nil, attributes: nil)
144+
return false
145+
default:
146+
// Otherwise just abort the shell integration setup
147+
print("Cannot setup shell integration, error: \(error)")
148+
return true
149+
}
150+
}
151+
}
152+
}
153+
154+
/// Configure shell integration script
155+
private func setupShellIntegration(shell: String, environment: [String]) {
156+
// Get user's home dir
157+
var homePath: String = ""
158+
environment.forEach { value in
159+
if value.starts(with: "HOME=") {
160+
homePath = value
161+
}
162+
}
163+
homePath.removeSubrange(homePath.startIndex..<homePath.index(homePath.startIndex, offsetBy: 5))
164+
165+
if let shellIntegrationScript = Bundle.main.url(
166+
forResource: "codeedit_shell_integration", withExtension: shell
167+
) {
168+
// Get the path of shell integration script
169+
let shellIntegrationScriptPath = (
170+
shellIntegrationScript.absoluteString[7..<shellIntegrationScript.absoluteString.count]
171+
) ?? ""
172+
173+
// Get the path of user's shell's source file
174+
// Only zsh and bash are supported for now
175+
var sourceScriptPath: String = ""
176+
switch shell {
177+
case "bash":
178+
sourceScriptPath = homePath + "/.profile"
179+
case "zsh":
180+
sourceScriptPath = homePath + "/.zshrc"
181+
default:
182+
return
183+
}
184+
185+
// Get the command for setting up shell integration
186+
let sourceCommand = "[[ \"$TERM_PROGRAM\" == \"CodeEditApp_Terminal\" ]] &&"
187+
+ " . \"\(shellIntegrationScriptPath)\""
188+
189+
// Add the shell integration setup command if needed
190+
if !shellIntegrationInstalled(sourceScriptPath: sourceScriptPath, command: sourceCommand) {
191+
if let handle = FileHandle(forWritingAtPath: sourceScriptPath) {
192+
handle.seekToEndOfFile()
193+
handle.write("\n\(sourceCommand)\n".data(using: .utf8)!)
194+
handle.closeFile()
195+
}
196+
}
197+
}
198+
}
199+
131200
/// Returns true if the `option` key should be treated as the `meta` key.
132201
private var optionAsMeta: Bool {
133202
terminalSettings.optionAsMeta
@@ -214,7 +283,13 @@ struct TerminalEmulatorView: NSViewRepresentable {
214283
// multiple workspaces. This works for now but most probably will need
215284
// to be changed later on
216285
FileManager.default.changeCurrentDirectoryPath(url.path)
217-
terminal.startProcess(executable: shell, execName: shellIdiom)
286+
287+
var terminalEnvironment: [String] = Terminal.getEnvironmentVariables()
288+
terminalEnvironment.append("TERM_PROGRAM=CodeEditApp_Terminal")
289+
290+
setupShellIntegration(shell: shellName, environment: terminalEnvironment)
291+
292+
terminal.startProcess(executable: shell, environment: terminalEnvironment, execName: shellIdiom)
218293
terminal.font = font
219294
terminal.configureNativeColors()
220295
terminal.installColors(self.colors)
@@ -225,8 +300,6 @@ struct TerminalEmulatorView: NSViewRepresentable {
225300
terminal.cursorStyleChanged(source: terminal.getTerminal(), newStyle: getTerminalCursor())
226301
terminal.layer?.backgroundColor = .clear
227302
terminal.optionAsMetaKey = optionAsMeta
228-
229-
setupShellTitle(shell: shellName)
230303
}
231304
terminal.appearance = colorAppearance
232305
scroller?.isHidden = true

0 commit comments

Comments
 (0)