@@ -21,23 +21,42 @@ class IgnorePatternModel: ObservableObject {
2121 @Published var selection : Set < UUID > = [ ]
2222
2323 private let gitConfig = GitConfigClient ( shellClient: currentWorld. shellClient)
24- private let fileURL = FileManager . default. homeDirectoryForCurrentUser. appendingPathComponent ( " .gitignore_global " )
2524 private var fileMonitor : DispatchSourceFileSystemObject ?
2625
2726 init ( ) {
28- loadPatterns ( )
29- startFileMonitor ( )
27+ Task {
28+ try ? await startFileMonitor ( )
29+ await loadPatterns ( )
30+ }
3031 }
3132
3233 deinit {
3334 stopFileMonitor ( )
3435 }
3536
36- private func startFileMonitor( ) {
37- let fileDescriptor = open ( fileURL. path, O_EVTONLY)
38- guard fileDescriptor != - 1 else {
39- return
37+ private func gitIgnoreURL( ) async throws -> URL {
38+ let excludesfile = try await gitConfig. get ( key: " core.excludesfile " ) ?? " "
39+ if !excludesfile. isEmpty {
40+ if excludesfile. starts ( with: " ~/ " ) {
41+ let relativePath = String ( excludesfile. dropFirst ( 2 ) ) // Remove "~/"
42+ return FileManager . default. homeDirectoryForCurrentUser. appendingPathComponent ( relativePath)
43+ } else if excludesfile. starts ( with: " / " ) {
44+ return URL ( fileURLWithPath: excludesfile) // Absolute path
45+ } else {
46+ return FileManager . default. homeDirectoryForCurrentUser. appendingPathComponent ( excludesfile)
47+ }
48+ } else {
49+ let defaultPath = " .gitignore_global "
50+ let fileURL = FileManager . default. homeDirectoryForCurrentUser. appendingPathComponent ( defaultPath)
51+ await gitConfig. set ( key: " core.excludesfile " , value: " ~/ \( defaultPath) " , global: true )
52+ return fileURL
4053 }
54+ }
55+
56+ private func startFileMonitor( ) async throws {
57+ let fileURL = try await gitIgnoreURL ( )
58+ let fileDescriptor = open ( fileURL. path, O_EVTONLY)
59+ guard fileDescriptor != - 1 else { return }
4160
4261 let source = DispatchSource . makeFileSystemObjectSource (
4362 fileDescriptor: fileDescriptor,
@@ -46,14 +65,16 @@ class IgnorePatternModel: ObservableObject {
4665 )
4766
4867 source. setEventHandler { [ weak self] in
49- self ? . loadPatterns ( )
68+ Task {
69+ await self ? . loadPatterns ( )
70+ }
5071 }
5172
5273 source. setCancelHandler {
5374 close ( fileDescriptor)
5475 }
5576
56- fileMonitor? . cancel ( ) // Cancel any existing monitor
77+ fileMonitor? . cancel ( )
5778 fileMonitor = source
5879 source. resume ( )
5980 }
@@ -63,50 +84,76 @@ class IgnorePatternModel: ObservableObject {
6384 fileMonitor = nil
6485 }
6586
66- func loadPatterns( ) {
67- loadingPatterns = true
87+ func loadPatterns( ) async {
88+ await MainActor . run { loadingPatterns = true } // Ensure `loadingPatterns` is updated on the main thread
6889
69- guard FileManager . default. fileExists ( atPath: fileURL. path) else {
70- patterns = [ ]
71- return
72- }
90+ do {
91+ let fileURL = try await gitIgnoreURL ( )
92+ guard FileManager . default. fileExists ( atPath: fileURL. path) else {
93+ await MainActor . run {
94+ patterns = [ ]
95+ loadingPatterns = false // Update on the main thread
96+ }
97+ return
98+ }
7399
74- if let content = try ? String ( contentsOf: fileURL) {
75- patterns = content. split ( separator: " \n " )
76- . map { $0. trimmingCharacters ( in: . whitespacesAndNewlines) }
77- . filter { !$0. isEmpty && !$0. starts ( with: " # " ) }
78- . map { GlobPattern ( value: String ( $0) ) }
100+ if let content = try ? String ( contentsOf: fileURL) {
101+ let parsedPatterns = content. split ( separator: " \n " )
102+ . map { $0. trimmingCharacters ( in: . whitespacesAndNewlines) }
103+ . filter { !$0. isEmpty && !$0. starts ( with: " # " ) }
104+ . map { GlobPattern ( value: String ( $0) ) }
105+
106+ await MainActor . run {
107+ patterns = parsedPatterns // Update `patterns` on the main thread
108+ loadingPatterns = false // Ensure `loadingPatterns` is updated on the main thread
109+ }
110+ } else {
111+ await MainActor . run {
112+ patterns = [ ]
113+ loadingPatterns = false
114+ }
115+ }
116+ } catch {
117+ print ( " Error loading patterns: \( error) " )
118+ await MainActor . run {
119+ patterns = [ ]
120+ loadingPatterns = false
121+ }
79122 }
80123 }
81124
82- // Map to track the line numbers of patterns.
83- var patternLineMapping : [ String : Int ] = [ : ]
84-
85125 func getPattern( for id: UUID ) -> GlobPattern ? {
86126 return patterns. first ( where: { $0. id == id } )
87127 }
88128
89129 func savePatterns( ) {
90- stopFileMonitor ( ) // Suspend the file monitor to avoid self-triggered updates
91- defer { startFileMonitor ( ) }
92-
93- guard let fileContent = try ? String ( contentsOf: fileURL) else {
94- writeAllPatterns ( )
95- return
96- }
130+ Task {
131+ stopFileMonitor ( )
132+ defer { Task { try ? await startFileMonitor ( ) } }
133+
134+ do {
135+ let fileURL = try await gitIgnoreURL ( )
136+ guard let fileContent = try ? String ( contentsOf: fileURL) else {
137+ writeAllPatterns ( )
138+ return
139+ }
97140
98- let lines = fileContent. split ( separator: " \n " , omittingEmptySubsequences: false ) . map ( String . init)
99- let ( patternToLineIndex, nonPatternLines) = mapLines ( lines)
100- let globalCommentLines = extractGlobalComments ( nonPatternLines, patternToLineIndex)
141+ let lines = fileContent. split ( separator: " \n " , omittingEmptySubsequences: false ) . map ( String . init)
142+ let ( patternToLineIndex, nonPatternLines) = mapLines ( lines)
143+ let globalCommentLines = extractGlobalComments ( nonPatternLines, patternToLineIndex)
101144
102- var reorderedLines = reorderPatterns ( globalCommentLines, patternToLineIndex, nonPatternLines, lines)
145+ var reorderedLines = reorderPatterns ( globalCommentLines, patternToLineIndex, nonPatternLines, lines)
103146
104- // Ensure single blank line at the end
105- reorderedLines = cleanUpWhitespace ( in: reorderedLines)
147+ // Ensure single blank line at the end
148+ reorderedLines = cleanUpWhitespace ( in: reorderedLines)
106149
107- // Write the updated content back to the file
108- let updatedContent = reorderedLines. joined ( separator: " \n " )
109- try ? updatedContent. write ( to: fileURL, atomically: true , encoding: . utf8)
150+ // Write the updated content back to the file
151+ let updatedContent = reorderedLines. joined ( separator: " \n " )
152+ try updatedContent. write ( to: fileURL, atomically: true , encoding: . utf8)
153+ } catch {
154+ print ( " Error saving patterns: \( error) " )
155+ }
156+ }
110157 }
111158
112159 private func mapLines( _ lines: [ String ] ) -> ( [ String : Int ] , [ ( line: String , index: Int ) ] ) {
@@ -181,53 +228,18 @@ class IgnorePatternModel: ObservableObject {
181228 }
182229
183230 private func writeAllPatterns( ) {
184- let content = patterns. map ( \. value) . joined ( separator: " \n " )
185231 Task {
186- let excludesfile : String ? = try await gitConfig. get ( key: " core.excludesfile " )
187- if excludesfile == " " {
188- await gitConfig. set ( key: " core.excludesfile " , value: " ~/ \( fileURL. lastPathComponent) " )
189- }
190- }
191- try ? content. write ( to: fileURL, atomically: true , encoding: . utf8)
192- }
193-
194- private func handlePatterns(
195- _ lines: inout [ String ] ,
196- existingPatterns: inout Set < String > ,
197- patternLineMap: inout [ String : Int ]
198- ) {
199- var handledPatterns = Set < String > ( )
200-
201- // Update or preserve existing patterns
202- for pattern in patterns {
203- let value = pattern. value
204- if let lineIndex = patternLineMap [ value] {
205- // Pattern already exists, update it in place
206- lines [ lineIndex] = value
207- handledPatterns. insert ( value)
208- } else {
209- // Check if the pattern has been edited and corresponds to a previous pattern
210- if let oldPattern = existingPatterns. first ( where: { !handledPatterns. contains ( $0) && $0 != value } ) ,
211- let lineIndex = patternLineMap [ oldPattern] {
212- lines [ lineIndex] = value
213- existingPatterns. remove ( oldPattern)
214- patternLineMap [ value] = lineIndex
215- handledPatterns. insert ( value)
216- } else {
217- // Append new patterns at the end
218- if let lastLine = lines. last, lastLine. trimmingCharacters ( in: . whitespaces) . isEmpty {
219- lines. removeLast ( ) // Remove trailing blank line before appending
220- }
221- lines. append ( value)
232+ do {
233+ let fileURL = try await gitIgnoreURL ( )
234+ if !FileManager. default. fileExists ( atPath: fileURL. path) {
235+ FileManager . default. createFile ( atPath: fileURL. path, contents: nil )
222236 }
223- }
224- }
225237
226- // Remove patterns no longer in the list
227- let currentPatterns = Set ( patterns . map ( \ . value ) )
228- lines = lines . filter { line in
229- let trimmedLine = line . trimmingCharacters ( in : . whitespaces )
230- return trimmedLine . isEmpty || trimmedLine . hasPrefix ( " # " ) || currentPatterns . contains ( trimmedLine )
238+ let content = patterns . map ( \ . value ) . joined ( separator : " \n " )
239+ try content . write ( to : fileURL , atomically : true , encoding : . utf8 )
240+ } catch {
241+ print ( " Failed to write all patterns: \( error ) " )
242+ }
231243 }
232244 }
233245
0 commit comments