Skip to content

Commit f24981a

Browse files
Fix: Tasks In Folder With Spaces (#1978)
* Fix: Tasks In Folder With Spaces * Escape Quotes, Fix Git Client Cases
1 parent 40d6016 commit f24981a

File tree

9 files changed

+77
-23
lines changed

9 files changed

+77
-23
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@
344344
661EF7BD2BEE215300C3E577 /* LoadingFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661EF7BC2BEE215300C3E577 /* LoadingFileView.swift */; };
345345
664935422C35A5BC00461C35 /* NSTableViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664935412C35A5BC00461C35 /* NSTableViewWrapper.swift */; };
346346
6653EE552C34817900B82DE2 /* QuickSearchResultLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6653EE542C34817900B82DE2 /* QuickSearchResultLabel.swift */; };
347-
669A50512C380C1800304CD8 /* String+escapedWhiteSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669A50502C380C1800304CD8 /* String+escapedWhiteSpaces.swift */; };
347+
669A50512C380C1800304CD8 /* String+Escaped.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669A50502C380C1800304CD8 /* String+Escaped.swift */; };
348348
669A50532C380C8E00304CD8 /* Collection+subscript_safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669A50522C380C8E00304CD8 /* Collection+subscript_safe.swift */; };
349349
669BC4082BED306400D1197C /* AnyFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669BC4072BED306400D1197C /* AnyFileView.swift */; };
350350
66AF6CE22BF17CC300D83C9D /* StatusBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AF6CE12BF17CC300D83C9D /* StatusBarViewModel.swift */; };
@@ -1049,7 +1049,7 @@
10491049
661EF7BC2BEE215300C3E577 /* LoadingFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingFileView.swift; sourceTree = "<group>"; };
10501050
664935412C35A5BC00461C35 /* NSTableViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSTableViewWrapper.swift; sourceTree = "<group>"; };
10511051
6653EE542C34817900B82DE2 /* QuickSearchResultLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSearchResultLabel.swift; sourceTree = "<group>"; };
1052-
669A50502C380C1800304CD8 /* String+escapedWhiteSpaces.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+escapedWhiteSpaces.swift"; sourceTree = "<group>"; };
1052+
669A50502C380C1800304CD8 /* String+Escaped.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Escaped.swift"; sourceTree = "<group>"; };
10531053
669A50522C380C8E00304CD8 /* Collection+subscript_safe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+subscript_safe.swift"; sourceTree = "<group>"; };
10541054
669BC4072BED306400D1197C /* AnyFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyFileView.swift; sourceTree = "<group>"; };
10551055
66AF6CE12BF17CC300D83C9D /* StatusBarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarViewModel.swift; sourceTree = "<group>"; };
@@ -2534,7 +2534,7 @@
25342534
children = (
25352535
61538B8F2B111FE800A88846 /* String+AppearancesOfSubstring.swift */,
25362536
61538B922B11201900A88846 /* String+Character.swift */,
2537-
669A50502C380C1800304CD8 /* String+escapedWhiteSpaces.swift */,
2537+
669A50502C380C1800304CD8 /* String+Escaped.swift */,
25382538
85745D622A38F8D900089AAB /* String+HighlightOccurrences.swift */,
25392539
6CED16E32A3E660D000EC962 /* String+Lines.swift */,
25402540
58D01C8E293167DC00C5B6B4 /* String+MD5.swift */,
@@ -4537,7 +4537,7 @@
45374537
B65B10F52B081A0C002852CF /* SourceControlAddExistingRemoteView.swift in Sources */,
45384538
58D01C99293167DC00C5B6B4 /* String+MD5.swift in Sources */,
45394539
20EBB505280C329800F3A5DA /* CommitListItemView.swift in Sources */,
4540-
669A50512C380C1800304CD8 /* String+escapedWhiteSpaces.swift in Sources */,
4540+
669A50512C380C1800304CD8 /* String+Escaped.swift in Sources */,
45414541
5878DAB2291D627C00DD95A3 /* EditorJumpBarView.swift in Sources */,
45424542
664935422C35A5BC00461C35 /* NSTableViewWrapper.swift in Sources */,
45434543
04BC1CDE2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift in Sources */,

CodeEdit/Features/CEWorkspaceSettings/Models/CETask.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class CETask: ObservableObject, Identifiable, Hashable, Codable {
4242
/// Ensures that the shell navigates to the correct folder, and then executes the specified command.
4343
var fullCommand: String {
4444
// Move into the specified folder if needed
45-
let changeDirectoryCommand = workingDirectory.isEmpty ? "" : "cd \(workingDirectory) && "
45+
let changeDirectoryCommand = workingDirectory.isEmpty ? "" : "cd \(workingDirectory.escapedDirectory()) && "
4646

4747
// Construct the full command
4848
return "\(changeDirectoryCommand)\(command)"

CodeEdit/Features/SourceControl/Client/GitClient+Clone.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ extension GitClient {
4141
remoteUrl: URL,
4242
localPath: URL
4343
) -> AsyncThrowingMapSequence<LiveCommandStream, CloneProgress> {
44-
let command = "clone \(remoteUrl.absoluteString) \(localPath.relativePath.escapedWhiteSpaces()) --progress"
44+
let command = "clone \(remoteUrl.absoluteString) \(localPath.relativePath.escapedDirectory()) --progress"
4545

4646
return self.runLive(command)
4747
.map { line in

CodeEdit/Features/SourceControl/Client/GitClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class GitClient {
7878
}
7979

8080
private func generateCommand(_ command: String) -> String {
81-
"cd \(directoryURL.relativePath.escapedWhiteSpaces());git \(command)"
81+
"cd \(directoryURL.relativePath.escapedDirectory());git \(command)"
8282
}
8383

8484
private func processCommonErrors(_ output: String) throws -> String {

CodeEdit/Features/SourceControl/Client/GitConfigClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class GitConfigClient {
3434
if global {
3535
fullCommand += " --global"
3636
} else if let projectURL = projectURL {
37-
fullCommand = "cd \(projectURL.relativePath.escapedWhiteSpaces()); " + fullCommand
37+
fullCommand = "cd \(projectURL.relativePath.escapedDirectory()); " + fullCommand
3838
}
3939

4040
fullCommand += " \(command)"

CodeEdit/Features/Tasks/Models/CEActiveTask.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable {
4040
// Because: CETask only contains information about the relative path.
4141
let fullCommand: String
4242
if let workspaceURL = workspaceURL {
43-
fullCommand = "cd \(workspaceURL.relativePath) && \(task.fullCommand)"
43+
fullCommand = "cd \(workspaceURL.relativePath.escapedDirectory()) && \(task.fullCommand)"
4444
} else {
4545
fullCommand = task.fullCommand
4646
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// String+escapedWhiteSpaces.swift
3+
// CodeEdit
4+
//
5+
// Created by Paul Ebose on 2024/07/05.
6+
//
7+
8+
import Foundation
9+
10+
extension String {
11+
/// Escapes the string so it's an always-valid directory
12+
func escapedDirectory() -> String {
13+
"\"\(self.escapedQuotes())\""
14+
}
15+
16+
/// Returns a new string, replacing all occurrences of ` ` with `\ ` if they aren't already escaped.
17+
func escapedWhiteSpaces() -> String {
18+
escape(replacing: " ")
19+
}
20+
21+
/// Returns a new string, replacing all occurrences of `"` with `\"` if they aren't already escaped.
22+
func escapedQuotes() -> String {
23+
escape(replacing: #"""#)
24+
}
25+
26+
func escape(replacing: Character) -> String {
27+
var string = ""
28+
var lastChar: Character?
29+
30+
for char in self {
31+
defer {
32+
lastChar = char
33+
}
34+
35+
guard char == replacing else {
36+
string.append(char)
37+
continue
38+
}
39+
40+
if let lastChar, lastChar == #"\"# {
41+
string.append(char)
42+
continue
43+
}
44+
45+
string.append(#"\"#)
46+
string.append(char)
47+
}
48+
49+
return string
50+
}
51+
}

CodeEdit/Utils/Extensions/String/String+escapedWhiteSpaces.swift

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

CodeEditTests/Utils/UnitTests_Extensions.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,21 @@ final class CodeEditUtilsExtensionsUnitTests: XCTestCase {
179179
XCTAssertFalse(invalidCase.isValidFilename, "Detected valid case \"\(invalidCase)\", should be invalid.")
180180
}
181181
}
182+
183+
// MARK: - STRING + ESCAPED
184+
185+
func testEscapeQuotes() {
186+
let string = #"this/is/"a path/Hello "world"#
187+
XCTAssertEqual(string.escapedQuotes(), #"this/is/\"a path/Hello \"world"#)
188+
}
189+
190+
func testEscapeQuotesForAlreadyEscapedString() {
191+
let string = #"this/is/"a path/Hello \"world"#
192+
XCTAssertEqual(string.escapedQuotes(), #"this/is/\"a path/Hello \"world"#)
193+
}
194+
195+
func testEscapedDirectory() {
196+
let path = #"/Hello World/ With Spaces/ And " Characters "#
197+
XCTAssertEqual(path.escapedDirectory(), #""/Hello World/ With Spaces/ And \" Characters ""#)
198+
}
182199
}

0 commit comments

Comments
 (0)