@@ -8,6 +8,7 @@ private enum OutputMode: String, CaseIterable, Identifiable {
88 case graph = " Graph "
99
1010 var id : Self { self }
11+ var title : String { self == . text ? " Text tree " : " Graph " }
1112 var fileExtension : String { self == . text ? " txt " : " svg " }
1213 var contentType : UTType { UTType ( filenameExtension : fileExtension ) ?? . plainText }
1314}
@@ -21,8 +22,8 @@ private struct FormatRequest: Hashable, Sendable {
2122
2223struct ContentView : View {
2324 @ State private var source = " "
24- @ State private var formatted = " "
25- @ State private var graph : TypeGraphNode ?
25+ @ State private var conversion : FormattedType ?
26+ @ State private var selectedID : String ?
2627 @ State private var collapsedNodes : Set < String > = [ ]
2728 @ State private var completedRequest : FormatRequest ?
2829 @ State private var formatError : String ?
@@ -34,6 +35,27 @@ struct ContentView: View {
3435 @ AppStorage ( " expandSingleArguments " ) private var expandSingleArguments = false
3536 @ AppStorage ( " presentationMode " ) private var outputMode : OutputMode = . text
3637
38+ private var formatted : String { conversion ? . text ?? " " }
39+ private var graph : TypeGraphNode ? { conversion ? . graph }
40+ private var selectedMapping : TypeMapping ? {
41+ guard isCurrent else { return nil }
42+ return conversion ? . mappings . first { $0 . id == selectedID }
43+ }
44+
45+ private func selectNode ( _ id : String ? ) {
46+ selectedID = id
47+ guard let id else { return }
48+ let parts = id . split ( separator : " . " )
49+ for length in 1 ..< parts . count {
50+ collapsedNodes . remove ( parts . prefix ( length ) . joined ( separator : " . " ) )
51+ }
52+ }
53+
54+ private func selectRange ( _ range : NSRange , in representation : TypeRepresentation ) {
55+ guard isCurrent , let conversion else { return }
56+ selectNode ( conversion . mapping ( containing : UTF16Range ( range . location , NSMaxRange ( range ) ) , in : representation ) ? . id )
57+ }
58+
3759 private var request : FormatRequest {
3860 FormatRequest ( source : source , indentation : indentation ,
3961 expandSingleArguments : expandSingleArguments , revision : revision )
@@ -103,6 +125,7 @@ struct ContentView: View {
103125 }
104126 . task ( id : request ) { await format ( request ) }
105127 . onChange ( of : output ) { copied = false }
128+ . onChange ( of : source ) { selectedID = nil }
106129 . onChange ( of : outputMode ) { copied = false }
107130 . onChange ( of : collapsedNodes ) { copied = false }
108131 . alert ( " File operation failed " , isPresented : Binding (
@@ -134,7 +157,7 @@ struct ContentView: View {
134157 . disabled ( source . isEmpty )
135158 } else {
136159 Picker ( " Output format " , selection : $outputMode ) {
137- ForEach ( OutputMode . allCases ) { Text ( $0 . rawValue ) . tag ( $0 ) }
160+ ForEach ( OutputMode . allCases ) { Text ( $0 . title ) . tag ( $0 ) }
138161 }
139162 . pickerStyle ( . segmented )
140163 . labelsHidden ( )
@@ -146,12 +169,15 @@ struct ContentView: View {
146169 . background ( . bar )
147170 Divider ( )
148171 if ! isInput , outputMode == . graph , canExport , let graph , let graphLayout {
149- TypeGraphView ( root : graph , layout : graphLayout , collapsed : $collapsedNodes )
172+ TypeGraphView ( root : graph , layout : graphLayout , collapsed : $collapsedNodes ,
173+ selectedID : selectedID , onSelect : selectNode )
150174 } else {
151175 ZStack ( alignment : . topLeading ) {
152176 CodeEditor ( text : isInput ? $source : . constant ( output ) ,
153177 editable : isInput , accessibilityLabel : isInput ? " Source typealias " : " Formatted output " ,
154- wrapsLines : isInput && wrapSourceLines )
178+ wrapsLines : isInput && wrapSourceLines ,
179+ linkedRange : isInput ? selectedMapping ? . sourceRange : selectedMapping ? . formattedRange ,
180+ onSelectionChange : { selectRange ( $0 , in : isInput ? . source : . formatted ) } )
155181 if ( isInput ? source : output ) . isEmpty {
156182 Text ( isInput ? " Paste a typealias or type here… " : " Converted output appears here. " )
157183 . font ( . system ( size : 13 , design : . monospaced ) )
@@ -192,8 +218,7 @@ struct ContentView: View {
192218 do { try await Task . sleep ( for : . milliseconds ( 180 ) ) } catch { return }
193219 guard ! Task . isCancelled else { return }
194220 if request . source . trimmingCharacters ( in : . whitespacesAndNewlines ) . isEmpty {
195- formatted = " "
196- graph = nil
221+ conversion = nil
197222 formatError = nil
198223 completedRequest = request
199224 return
@@ -207,17 +232,15 @@ struct ContentView: View {
207232 guard ! Task . isCancelled else { return }
208233 switch result {
209234 case let . success ( value ) :
210- formatted = value . text
211235 if graph != value . graph {
212- graph = value . graph
213236 collapsedNodes = Set ( GraphLayout ( root : value . graph ) . nodes . filter {
214237 $0 . id . split ( separator : " . " ) . count >= 3 && $0 . childCount > 0
215238 } . map ( \. id ) )
216239 }
240+ conversion = value
217241 formatError = nil
218242 case let . failure ( error ) :
219- formatted = " "
220- graph = nil
243+ conversion = nil
221244 formatError = error . localizedDescription
222245 }
223246 completedRequest = request
0 commit comments