@@ -14,19 +14,20 @@ extension String {
1414struct FileModel : Identifiable {
1515 let id = UUID ( )
1616 let path : String
17- var url : URL {
17+ var url : URL {
1818 URL ( fileURLWithPath: path)
1919 }
2020}
2121
2222class FileSearch : ObservableObject {
2323 @Published var searchText : String = " "
24- var pickedPath = " "
24+ @ Published var pickedPath = " "
2525// @Published var foundFiles: [String] = []
2626 @Published var foundFiles : [ FileModel ] = [ ]
2727// @Published var selectedFile: String = ""
2828 @Published var selectedFile : FileModel ? = nil // .init(name: "")
2929 @Published var selectedFileContent : String = " "
30+ @Published var isSearching = false
3031 var cancellables : Set < AnyCancellable > = [ ]
3132
3233 init ( ) {
@@ -35,7 +36,7 @@ class FileSearch: ObservableObject {
3536
3637 func filePicker( ) {
3738 guard !searchText. isEmpty else {
38- foundFiles = [ FileModel ( path: " insert a text to search for" ) ]
39+ foundFiles = [ FileModel ( path: " Insert a text to search for" ) ]
3940 return
4041 }
4142 let openPanel = NSOpenPanel ( )
@@ -46,17 +47,9 @@ class FileSearch: ObservableObject {
4647 openPanel. canChooseFiles = false
4748 openPanel. begin { [ weak self] result in
4849 if result. rawValue == NSApplication . ModalResponse. OK. rawValue {
49- if let selectedPath = openPanel. url? . path. escapingSpaces ( ) , let searchText = self ? . searchText {
50+ if let selectedPath = openPanel. url? . path. escapingSpaces ( ) {
51+ self ? . isSearching = true
5052 self ? . pickedPath = selectedPath
51- let command = " grep -rl ' \( searchText) ' \( selectedPath) "
52- do {
53- print ( command)
54- self ? . foundFiles = try safeShell ( command) . components ( separatedBy: " \n " ) . map { name in
55- FileModel ( path: name)
56- }
57- } catch {
58- print ( error)
59- }
6053 }
6154 }
6255 }
@@ -68,16 +61,77 @@ class FileSearch: ObservableObject {
6861 }
6962 }
7063
64+ func runCommand( _ command: String ) async -> [ FileModel ] {
65+ do {
66+ let shellOutput = try safeShell ( command)
67+ guard !shellOutput. isEmpty else { return [ ] }
68+
69+ let lines = shellOutput. components ( separatedBy: " \n " )
70+ return lines. map { name in
71+ FileModel ( path: name)
72+ }
73+ } catch {
74+ return [ FileModel ( path: error. localizedDescription) ]
75+ }
76+ }
77+
78+ func readFile( contentsOfFile path: String ) async -> String {
79+ do {
80+ let content = try String ( contentsOfFile: path)
81+ return content
82+ } catch {
83+ return error. localizedDescription
84+ }
85+ }
86+
7187 func makePublishers( ) {
72- $selectedFile. sink { file in
73- do {
88+ $selectedFile
89+ . asyncMap { file -> String in
7490 if let fileUnwrap = file {
75- self . selectedFileContent = try String ( contentsOfFile: fileUnwrap. path)
91+ return await self . readFile ( contentsOfFile: fileUnwrap. path)
92+ } else {
93+ return " No file selected "
94+ }
95+ }
96+ . receive ( on: RunLoop . main)
97+ . sink { [ weak self] content in
98+ self ? . selectedFileContent = content
99+ }
100+ . store ( in: & cancellables)
101+
102+ $pickedPath
103+ . asyncMap { selectedPath -> [ FileModel ] in
104+ guard !selectedPath. isEmpty else {
105+ return [ FileModel ( path: " No directory selected " ) ]
106+ }
107+ let command = " grep -rl ' \( self . searchText) ' \( selectedPath) "
108+ let files = await self . runCommand ( command)
109+
110+ if files. isEmpty {
111+ return [ FileModel ( path: " No files found in \( selectedPath) " ) ]
112+ }
113+ return files
114+ }
115+ . receive ( on: RunLoop . main)
116+ . sink { [ weak self] files in
117+ self ? . foundFiles = files
118+ self ? . isSearching = false
119+ }
120+ . store ( in: & cancellables)
121+ }
122+ }
123+
124+ extension Publisher {
125+ func asyncMap< T> (
126+ _ transform: @escaping ( Output ) async -> T
127+ ) -> Publishers . FlatMap < Future < T , Never > , Self > {
128+ flatMap { value in
129+ Future { promise in
130+ Task {
131+ let output = await transform ( value)
132+ promise ( . success( output) )
76133 }
77- } catch {
78- self . selectedFileContent = error. localizedDescription
79134 }
80135 }
81- . store ( in: & cancellables)
82136 }
83137}
0 commit comments