@@ -24,22 +24,23 @@ public final class FileDiffViewModel: Sendable {
2424 filePath: String ,
2525 llmDiff: String )
2626 {
27- // Variables used to log debug info if something fails.
28- var _oldContent : String ?
27+ let fileContent : String
28+ do {
29+ fileContent = try Self . getCurrentContent ( of: URL ( fileURLWithPath: filePath) )
30+ } catch {
31+ defaultLogger. error ( " Error reading file \( filePath) " , error)
32+ return nil
33+ }
2934 do {
30- let path = URL ( fileURLWithPath: filePath)
31- @Dependency ( \. fileManager) var fileManager
32- let fileContent = try fileManager. read ( contentsOf: path)
33- _oldContent = fileContent
3435 let changes = try FileDiff . parse ( searchReplacePattern: llmDiff, for: fileContent)
35- self . init ( filePath: filePath, changes: changes)
36+ self . init ( filePath: filePath, changes: changes, oldContent : fileContent )
3637 } catch {
3738 defaultLogger. error ( """
3839 Could not format diff for \( filePath) : \( error)
3940 -- Diff:
4041 \( llmDiff)
4142 -- Current content:
42- \( _oldContent ?? " ? " )
43+ \( fileContent )
4344 --
4445 """ )
4546 return nil
@@ -48,53 +49,53 @@ public final class FileDiffViewModel: Sendable {
4849
4950 public convenience init ? (
5051 filePath: String ,
51- changes: [ FileDiff . SearchReplace ] )
52+ changes: [ FileDiff . SearchReplace ] ,
53+ oldContent: String ? = nil )
5254 {
53- // Variables used to log debug info if something fails.
54- var _oldContent : String ?
55- var _newContent : String ?
56- do {
57- let path = URL ( fileURLWithPath: filePath)
58- @Dependency ( \. fileManager) var fileManager
59- guard let oldContent = try ? fileManager. read ( contentsOf: path) else {
60- throw AppError ( message: " File content not available for \( path) " )
55+ let path = URL ( fileURLWithPath: filePath)
56+ let fileContent : String
57+ if let oldContent {
58+ fileContent = oldContent
59+ } else {
60+ do {
61+ fileContent = try Self . getCurrentContent ( of: path)
62+ } catch {
63+ defaultLogger. error ( " Error reading file \( filePath) " , error)
64+ return nil
6165 }
62- _oldContent = oldContent
63-
64- let newContent = try FileDiff . apply ( changes: changes, to: oldContent)
65- _newContent = newContent
66+ }
6667
67- if newContent == oldContent {
68+ do {
69+ let newContent = try FileDiff . apply ( changes: changes, to: fileContent)
70+ if newContent == fileContent {
6871 return nil
6972 }
7073
71- let gitDiff = try FileDiff . getGitDiff ( oldContent: oldContent , newContent: newContent)
74+ let gitDiff = try FileDiff . getGitDiff ( oldContent: fileContent , newContent: newContent)
7275
7376 self . init (
7477 filePath: path,
75- baseLineContent: oldContent ,
78+ baseLineContent: fileContent ,
7679 targetContent: newContent,
7780 changes: changes,
7881 canBeApplied: true ,
7982 formattedDiff: nil )
8083
8184 diffingTasks. queue {
8285 let formattedDiff = try await FileDiff . getColoredDiff (
83- oldContent: oldContent ,
86+ oldContent: fileContent ,
8487 newContent: newContent,
8588 gitDiff: gitDiff,
8689 highlightColors: . dark( . xcode) )
87- return . init( canBeApplied: true , formattedDiff: formattedDiff, baseLineContent: oldContent )
90+ return . init( canBeApplied: true , formattedDiff: formattedDiff, baseLineContent: fileContent )
8891 }
8992 } catch {
9093 defaultLogger. error ( """
9194 Could not format diff for \( filePath) : \( error)
9295 -- Changes:
9396 \( changes. map { " replace: \n \( $0. replace) \n with: \n \( $0. replace) " } . joined ( separator: " \n ------- \n " ) )
9497 -- Previous Content:
95- \( _oldContent ?? " ? " )
96- -- New Content:
97- \( _newContent ?? " ? " )
98+ \( fileContent)
9899 --
99100 """ )
100101 return nil
@@ -251,4 +252,20 @@ public final class FileDiffViewModel: Sendable {
251252 private let xcodeController : XcodeController
252253
253254 private let diffingTasks = ReplaceableTaskQueue < SuggestionUpdate ? > ( )
255+
256+ /// Get the current content of the file. It is possible that the editor has content that is not yet saved to disk.
257+ private static func getCurrentContent( of file: URL ) throws -> String {
258+ @Dependency ( \. fileManager) var fileManager
259+ @Dependency ( \. xcodeObserver) var xcodeObserver
260+ let editorContent = xcodeObserver. state. wrapped? . xcodesState. compactMap { xc in
261+ xc. workspaces. compactMap { ws in
262+ ws. tabs. compactMap { tab in
263+ tab. knownPath == file ? tab. lastKnownContent : nil
264+ } . first
265+ } . first
266+ } . first
267+ // TODO: is it fine to run on the main thread?
268+ return try editorContent ?? fileManager. read ( contentsOf: file, encoding: . utf8)
269+ }
270+
254271}
0 commit comments