Skip to content

Commit 8a21272

Browse files
committed
Let space-joined path segments keep their trailing extension
A space-separated segment only counts as part of an unquoted path when it contains '/' or '.', but the segment pattern also had to *end* with one of those, so greedy backtracking cut '/x/face cropped.png' at 'cropped.' and left the extension out of the match. Allow trailing word characters after the last '/' or '.' so the full extension is absorbed. Prose words after a path (' and', ' at') still contain neither character and are still excluded.
1 parent 20b4315 commit 8a21272

2 files changed

Lines changed: 21 additions & 1 deletion

File tree

Sources/SwiftTerm/Terminal.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6275,7 +6275,11 @@ open class Terminal {
62756275
let trailingSpacesAtEOL = #"(?: +(?= *$))?"#
62766276
let dottedPathLookahead = #"(?=[\w\-.~:\/?#@!$&*+;=%]*\.)"#
62776277
let nonDottedPathLookahead = #"(?![\w\-.~:\/?#@!$&*+;=%]*\.)"#
6278-
let dottedPathSpaceSegments = #"(?:(?<!:) (?!\w+:\/\/)[\w\-.~:\/?#@!$&*+;=%]*[\/.])*"#
6278+
// A space-joined segment must contain '/' or '.' to count as part of
6279+
// the path, but may keep trailing word characters after the last one,
6280+
// so "face cropped.png" absorbs the full extension instead of the
6281+
// greedy match cutting the segment at "cropped.".
6282+
let dottedPathSpaceSegments = #"(?:(?<!:) (?!\w+:\/\/)[\w\-.~:\/?#@!$&*+;=%]*[\/.]\w*)*"#
62796283
let anyPathSpaceSegments = #"(?:(?<!:) (?!\w+:\/\/)[\w\-.~:\/?#@!$&*+;=%]+)*"#
62806284

62816285
let schemeURLBranch =

Tests/SwiftTermTests/LinkLookupTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,22 @@ final class LinkLookupTests: TerminalDelegate {
165165
#expect(wrappedRowLink == path)
166166
}
167167

168+
@Test func testUnquotedSpacePathKeepsExtension() {
169+
let terminal = Terminal(delegate: self, options: TerminalOptions(cols: 80, rows: 1))
170+
terminal.feed(text: "/Users/me/Assets.xcassets/face.imageset/face cropped.png")
171+
172+
let link = terminal.link(at: .buffer(Position(col: 10, row: 0)), mode: .explicitAndImplicit)
173+
#expect(link == "/Users/me/Assets.xcassets/face.imageset/face cropped.png")
174+
}
175+
176+
@Test func testUnquotedSpacePathDoesNotAbsorbProse() {
177+
let terminal = Terminal(delegate: self, options: TerminalOptions(cols: 60, rows: 1))
178+
terminal.feed(text: "see /tmp/foo.txt and more")
179+
180+
let link = terminal.link(at: .buffer(Position(col: 8, row: 0)), mode: .explicitAndImplicit)
181+
#expect(link == "/tmp/foo.txt")
182+
}
183+
168184
@Test func testQuotedNonPathDoesNotMatch() {
169185
let terminal = Terminal(delegate: self, options: TerminalOptions(cols: 30, rows: 1))
170186
terminal.feed(text: "'hello world'")

0 commit comments

Comments
 (0)