@@ -41,6 +41,14 @@ final class IssueNavigatorViewController: NSViewController {
4141 /// to open the file a second time.
4242 var shouldSendSelectionUpdate : Bool = true
4343
44+ /// Key for storing expansion state in UserDefaults
45+ private var expansionStateKey : String {
46+ guard let workspaceURL = workspace? . workspaceFileManager? . folderUrl else {
47+ return " IssueNavigatorExpansionState "
48+ }
49+ return " IssueNavigatorExpansionState_ \( workspaceURL. path. hashValue) "
50+ }
51+
4452 /// Setup the ``scrollView`` and ``outlineView``
4553 override func loadView( ) {
4654 self . scrollView = NSScrollView ( )
@@ -50,7 +58,8 @@ final class IssueNavigatorViewController: NSViewController {
5058 self . outlineView = NSOutlineView ( )
5159 self . outlineView. dataSource = self
5260 self . outlineView. delegate = self
53- self . outlineView. autosaveExpandedItems = false
61+ self . outlineView. autosaveExpandedItems = true
62+ self . outlineView. autosaveName = workspace? . workspaceFileManager? . folderUrl. path ?? " "
5463 self . outlineView. headerView = nil
5564 self . outlineView. menu = IssueNavigatorMenu ( self )
5665 self . outlineView. menu? . delegate = self
@@ -72,15 +81,14 @@ final class IssueNavigatorViewController: NSViewController {
7281 scrollView. hasHorizontalScroller = false
7382 scrollView. autohidesScrollers = true
7483
75- outlineView . expandItem ( outlineView . item ( atRow : 0 ) )
84+ loadExpansionState ( )
7685
77- /// Get autosave expanded items.
78- for row in 0 ..< outlineView. numberOfRows {
79- if let item = outlineView. item ( atRow: row) as? FileIssueNode {
80- if outlineView. isItemExpanded ( item) {
81- expandedItems. insert ( item)
82- }
86+ // Expand the project node by default
87+ DispatchQueue . main. async { [ weak self] in
88+ if let rootItem = self ? . outlineView. item ( atRow: 0 ) {
89+ self ? . outlineView. expandItem ( rootItem)
8390 }
91+ self ? . restoreExpandedState ( )
8492 }
8593 }
8694
@@ -89,6 +97,7 @@ final class IssueNavigatorViewController: NSViewController {
8997 }
9098
9199 deinit {
100+ saveExpansionState ( )
92101 outlineView? . removeFromSuperview ( )
93102 scrollView? . removeFromSuperview ( )
94103 }
@@ -97,6 +106,45 @@ final class IssueNavigatorViewController: NSViewController {
97106 fatalError ( )
98107 }
99108
109+ /// Saves the current expansion state to UserDefaults
110+ private func saveExpansionState( ) {
111+ guard let viewModel = workspace? . issueNavigatorViewModel else { return }
112+
113+ let expandedUris = viewModel. getExpandedFileUris ( )
114+ let urisArray = Array ( expandedUris)
115+
116+ UserDefaults . standard. set ( urisArray, forKey: expansionStateKey)
117+ }
118+
119+ /// Loads the expansion state from UserDefaults
120+ private func loadExpansionState( ) {
121+ guard let viewModel = workspace? . issueNavigatorViewModel else { return }
122+
123+ if let urisArray = UserDefaults . standard. stringArray ( forKey: expansionStateKey) {
124+ let expandedUris = Set ( urisArray)
125+ viewModel. restoreExpandedFileUris ( expandedUris)
126+ }
127+ }
128+
129+ /// Restores the expanded state of items based on their model state
130+ public func restoreExpandedState( ) {
131+ // Expand root if it should be expanded
132+ if let rootItem = outlineView. item ( atRow: 0 ) as? ProjectIssueNode ,
133+ rootItem. isExpanded {
134+ outlineView. expandItem ( rootItem)
135+ }
136+
137+ // Expand file nodes based on their expansion state
138+ for row in 0 ..< outlineView. numberOfRows {
139+ if let fileItem = outlineView. item ( atRow: row) as? FileIssueNode {
140+ if fileItem. isExpanded {
141+ outlineView. expandItem ( fileItem)
142+ expandedItems. insert ( fileItem)
143+ }
144+ }
145+ }
146+ }
147+
100148 /// Expand or collapse the folder on double click
101149 @objc
102150 private func onItemDoubleClicked( ) {
@@ -111,7 +159,9 @@ final class IssueNavigatorViewController: NSViewController {
111159 toggleExpansion ( of: fileNode)
112160 openFileTab ( fileUri: fileNode. uri)
113161 } else if let diagnosticNode = item as? DiagnosticIssueNode {
114- openFileTab ( fileUri: diagnosticNode. fileUri)
162+ openFileTab ( fileUri: diagnosticNode. fileUri,
163+ line: diagnosticNode. diagnostic. range. start. line,
164+ column: diagnosticNode. diagnostic. range. start. character)
115165 }
116166 }
117167
@@ -125,13 +175,20 @@ final class IssueNavigatorViewController: NSViewController {
125175 }
126176 }
127177
128- /// Opens a file as a permanent tab
178+ /// Opens a file as a permanent tab, optionally at a specific line and column
129179 @inline ( __always)
130- private func openFileTab( fileUri: String ) {
180+ private func openFileTab( fileUri: String , line : Int ? = nil , column : Int ? = nil ) {
131181 guard let fileURL = URL ( string: fileUri) ,
132182 let file = workspace? . workspaceFileManager? . getFile ( fileURL. path) else {
133183 return
134184 }
185+
135186 workspace? . editorManager? . activeEditor. openTab ( file: file, asTemporary: false )
136187 }
188+
189+ /// Called when view will disappear - save state
190+ override func viewWillDisappear( ) {
191+ super. viewWillDisappear ( )
192+ saveExpansionState ( )
193+ }
137194}
0 commit comments