@@ -92,6 +92,7 @@ struct BreadCrumbView: View {
9292 let text : String // shown (may be truncated)
9393 let fullName : String // full name for tooltip + hover-expand
9494 let originalIndex : Int // index in pathComponents for navigation
95+ let isEnvironmentVariable : Bool
9596 var isTruncated : Bool { text != fullName }
9697 }
9798
@@ -100,12 +101,14 @@ struct BreadCrumbView: View {
100101 /// remote → ["SFTP demo@host", "pub", "docs"] (first segment = origin label)
101102 /// archive → ["archive.zip", "subdir"]
102103 /// local → ["Users", "senat", "Develop"]
103- private var pathComponents : [ String ] {
104+ private var pathComponents : [ BreadCrumbDisplayComponent ] {
104105 let panelURL = panelURL
105106
106107 // ── Remote (SFTP / FTP) ──────────────────────────────────────────────
107108 if AppState . isRemotePath ( panelURL) {
108- return remoteComponents ( for: panelURL)
109+ return remoteComponents ( for: panelURL) . map {
110+ BreadCrumbDisplayComponent ( text: $0, isEnvironmentVariable: false )
111+ }
109112 }
110113
111114 // ── Archive (virtual) ────────────────────────────────────────────────
@@ -117,14 +120,24 @@ struct BreadCrumbView: View {
117120 currentPath: panelURL. path,
118121 archiveName: archiveURL. lastPathComponent,
119122 tempDir: tempDir. standardizedFileURL. path
120- )
123+ ) . map { BreadCrumbDisplayComponent ( text : $0 , isEnvironmentVariable : false ) }
121124 }
122125
123126 // ── Local filesystem ─────────────────────────────────────────────────
124- return panelURL. path
125- . split ( separator: " / " )
126- . map ( String . init)
127- . filter { !$0. isEmpty }
127+ return PathEnvironmentResolver . displayComponents ( from: appState. breadcrumbDisplayPath ( for: panelSide) )
128+ }
129+
130+ private var pathComponentTexts : [ String ] {
131+ pathComponents. map ( \. text)
132+ }
133+
134+ private var localDisplayPath : String {
135+ appState. breadcrumbDisplayPath ( for: panelSide)
136+ }
137+
138+ private func makeLocalDisplayPath( through index: Int ) -> String {
139+ let joined = pathComponentTexts. prefix ( index + 1 ) . joined ( separator: " / " )
140+ return localDisplayPath. hasPrefix ( " / " ) ? " / " + joined : joined
128141 }
129142
130143 // MARK: - remoteComponents
@@ -196,20 +209,32 @@ struct BreadCrumbView: View {
196209 guard !components. isEmpty else { return [ ] }
197210
198211 if components. count == 1 {
199- return [ DisplaySegment ( text: components [ 0 ] , fullName: components [ 0 ] , originalIndex: 0 ) ]
212+ return [
213+ DisplaySegment (
214+ text: components [ 0 ] . text,
215+ fullName: components [ 0 ] . text,
216+ originalIndex: 0 ,
217+ isEnvironmentVariable: components [ 0 ] . isEnvironmentVariable
218+ )
219+ ]
200220 }
201221
202222 let charWidth : CGFloat = 7.5
203223 let totalSepWidth = CGFloat ( components. count - 1 ) * separatorWidth
204224 let budgetForText = availableWidth - totalSepWidth - 16
205225
206- let widths = components. map { CGFloat ( $0. count) * charWidth }
226+ let widths = components. map { CGFloat ( $0. text . count) * charWidth }
207227 let totalWidth = widths. reduce ( 0 , + )
208228
209229 if totalWidth <= budgetForText {
210230 return components. enumerated ( )
211- . map { i, name in
212- DisplaySegment ( text: name, fullName: name, originalIndex: i)
231+ . map { i, component in
232+ DisplaySegment (
233+ text: component. text,
234+ fullName: component. text,
235+ originalIndex: i,
236+ isEnvironmentVariable: component. isEnvironmentVariable
237+ )
213238 }
214239 }
215240
@@ -222,8 +247,9 @@ struct BreadCrumbView: View {
222247 var priority : Int
223248 }
224249 var segs = components. enumerated ( )
225- . map { i, name in
226- Seg (
250+ . map { i, component in
251+ let name = component. text
252+ return Seg (
227253 index: i, name: name, display: name, width: widths [ i] ,
228254 priority: truncPriority ( index: i, total: components. count, len: name. count) )
229255 }
@@ -243,7 +269,14 @@ struct BreadCrumbView: View {
243269 segs [ idx] . width = newWidth
244270 segs [ idx] . priority = 0
245271 }
246- return segs. map { DisplaySegment ( text: $0. display, fullName: $0. name, originalIndex: $0. index) }
272+ return segs. map {
273+ DisplaySegment (
274+ text: $0. display,
275+ fullName: $0. name,
276+ originalIndex: $0. index,
277+ isEnvironmentVariable: components [ $0. index] . isEnvironmentVariable
278+ )
279+ }
247280 }
248281
249282 // MARK: - truncPriority — never truncate first/last; longer middle first
@@ -267,20 +300,20 @@ struct BreadCrumbView: View {
267300 return origin + " / "
268301 }
269302
270- let parts = Array ( pathComponents [ 1 ... segment. originalIndex] )
303+ let parts = Array ( pathComponentTexts [ 1 ... segment. originalIndex] )
271304 return origin + " / " + parts. joined ( separator: " / " )
272305 }
273306
274307 private func archiveTargetPath( for segment: DisplaySegment ) -> String ? {
275308 guard let tempDir = archiveTempDir else { return nil }
276309 guard segment. originalIndex > 0 else { return nil }
277310
278- let sub = Array ( pathComponents [ 1 ... segment. originalIndex] )
311+ let sub = Array ( pathComponentTexts [ 1 ... segment. originalIndex] )
279312 return tempDir. standardizedFileURL. path + " / " + sub. joined ( separator: " / " )
280313 }
281314
282315 private func localTargetPath( for segment: DisplaySegment ) -> String {
283- ( " / " + pathComponents . prefix ( segment. originalIndex + 1 ) . joined ( separator : " / " ) )
316+ makeLocalDisplayPath ( through : segment. originalIndex)
284317 . replacingOccurrences ( of: " // " , with: " / " )
285318 }
286319
@@ -297,6 +330,8 @@ struct BreadCrumbView: View {
297330 ExpandableSegmentButton (
298331 segment: segment,
299332 textColor: textColor,
333+ variableTextColor: colorStore. activeTheme. breadcrumbVariableColor,
334+ variableItalic: colorStore. breadcrumbVariableItalic,
300335 fontSize: fontSize,
301336 onTap: { handleTap ( segment: segment) } ,
302337 helpText: tooltip ( for: segment) ,
@@ -336,19 +371,19 @@ struct BreadCrumbView: View {
336371 if segment. originalIndex == 0 {
337372 return " 🌐 \( segment. fullName) — tap to go to root "
338373 }
339- let parts = Array ( pathComponents [ 1 ... segment. originalIndex] )
374+ let parts = Array ( pathComponentTexts [ 1 ... segment. originalIndex] )
340375 return " 📂 / \( parts. joined ( separator: " / " ) ) "
341376 }
342377
343378 if isInsideArchive {
344379 if segment. originalIndex == 0 {
345380 return " 📦 \( segment. fullName) — tap to exit archive "
346381 }
347- let parts = pathComponents . prefix ( segment. originalIndex + 1 )
382+ let parts = pathComponentTexts . prefix ( segment. originalIndex + 1 )
348383 return " 📂 \( parts. joined ( separator: " / " ) ) "
349384 }
350385
351- let fullPath = " / " + pathComponents . prefix ( segment. originalIndex + 1 ) . joined ( separator : " / " )
386+ let fullPath = makeLocalDisplayPath ( through : segment. originalIndex)
352387 return " 📂 Open \( fullPath) "
353388 }
354389
@@ -360,7 +395,7 @@ struct BreadCrumbView: View {
360395 ?? segment. fullName
361396 }
362397
363- let parts = Array ( pathComponents [ 1 ... segment. originalIndex] )
398+ let parts = Array ( pathComponentTexts [ 1 ... segment. originalIndex] )
364399 return " / " + parts. joined ( separator: " / " )
365400 }
366401
@@ -370,7 +405,7 @@ struct BreadCrumbView: View {
370405 }
371406
372407 guard let tempDir = archiveTempDir else { return " " }
373- let sub = Array ( pathComponents [ 1 ... segment. originalIndex] )
408+ let sub = Array ( pathComponentTexts [ 1 ... segment. originalIndex] )
374409 return tempDir. standardizedFileURL. path + " / " + sub. joined ( separator: " / " )
375410 }
376411
@@ -383,7 +418,7 @@ struct BreadCrumbView: View {
383418 } else if isInsideArchive {
384419 pathToCopy = archiveCopyPath ( for: segment)
385420 } else {
386- pathToCopy = " / " + pathComponents . prefix ( segment. originalIndex + 1 ) . joined ( separator : " / " )
421+ pathToCopy = makeLocalDisplayPath ( through : segment. originalIndex)
387422 }
388423
389424 NSPasteboard . general. clearContents ( )
0 commit comments