@@ -7,12 +7,16 @@ import JSONRPC
77import Logger
88import XcodeInspector
99import ChatAPIService
10+ import SystemUtils
11+ import Workspace
1012
1113public enum InsertEditError : LocalizedError {
1214 case missingEditorElement ( file : URL )
1315 case openingApplicationUnavailable
1416 case fileNotOpenedInXcode
1517 case fileURLMismatch ( expected : URL , actual : URL ? )
18+ case fileNotAccessible ( URL )
19+ case fileHasUnsavedChanges ( URL )
1620
1721 public var errorDescription : String ? {
1822 switch self {
@@ -24,6 +28,10 @@ public enum InsertEditError: LocalizedError {
2428 return " The file is not currently opened in Xcode. "
2529 case . fileURLMismatch ( let expected , let actual ) :
2630 return " The currently focused file URL \( actual ? . lastPathComponent ?? " unknown " ) does not match the expected file URL \( expected . lastPathComponent ) . "
31+ case . fileNotAccessible ( let fileURL ) :
32+ return " The file \( fileURL . lastPathComponent ) is not accessible. "
33+ case . fileHasUnsavedChanges ( let fileURL ) :
34+ return " The file \( fileURL . lastPathComponent ) seems to have unsaved changes in Xcode. Please save the file and try again. "
2735 }
2836 }
2937}
@@ -50,7 +58,7 @@ public class InsertEditIntoFileTool: ICopilotTool {
5058 let fileURL = URL ( fileURLWithPath : filePath )
5159 let originalContent = try String ( contentsOf : fileURL , encoding : . utf8 )
5260
53- InsertEditIntoFileTool . applyEdit ( for : fileURL , content : code , contextProvider : contextProvider ) { newContent , error in
61+ InsertEditIntoFileTool . applyEdit ( for : fileURL , content : code ) { newContent , error in
5462 if let error = error {
5563 self . completeResponse (
5664 request ,
@@ -106,7 +114,6 @@ public class InsertEditIntoFileTool: ICopilotTool {
106114 public static func applyEdit (
107115 for fileURL : URL ,
108116 content : String ,
109- contextProvider : any ToolContextProvider ,
110117 xcodeInstance : AppInstanceInspector
111118 ) throws -> String {
112119 guard let editorElement = Self . getEditorElement ( by : xcodeInstance , for : fileURL )
@@ -155,39 +162,21 @@ public class InsertEditIntoFileTool: ICopilotTool {
155162 public static func applyEdit (
156163 for fileURL : URL ,
157164 content : String ,
158- contextProvider : any ToolContextProvider ,
159165 completion : ( ( String ? , Error ? ) -> Void ) ? = nil
160166 ) {
161- NSWorkspace . openFileInXcode ( fileURL : fileURL ) { app , error in
162- do {
163- if let error = error { throw error }
164-
165- guard let app = app
166- else {
167- throw InsertEditError . openingApplicationUnavailable
168- }
169-
170- let appInstanceInspector = AppInstanceInspector ( runningApplication : app )
171- guard appInstanceInspector . isXcode
172- else {
173- throw InsertEditError . fileNotOpenedInXcode
174- }
175-
176- let newContent = try applyEdit (
177- for : fileURL ,
178- content : content ,
179- contextProvider : contextProvider ,
180- xcodeInstance : appInstanceInspector
181- )
182-
183- Task {
184- await WorkspaceInvocationCoordinator ( ) . invokeFilespaceUpdate ( fileURL : fileURL , content : newContent )
185- if let completion = completion { completion ( newContent , nil ) }
186- }
187- } catch {
188- if let completion = completion { completion ( nil , error ) }
189- Logger . client . info ( " Failed to apply edit for file at \( fileURL ) , \( error ) " )
190- }
167+ if SystemUtils . isDeveloperMode || SystemUtils . isPrereleaseBuild {
168+ /// Experimental solution: Use file system write for better reliability. Only enable in dev mode or prerelease builds.
169+ Self . applyEditWithFileSystem (
170+ for : fileURL ,
171+ content : content ,
172+ completion : completion
173+ )
174+ } else {
175+ Self . applyEditWithAccessibilityAPI (
176+ for : fileURL ,
177+ content : content ,
178+ completion : completion
179+ )
191180 }
192181 }
193182
@@ -248,3 +237,72 @@ private extension AppInstanceInspector {
248237 appElement . realtimeDocumentURL
249238 }
250239}
240+
241+ extension InsertEditIntoFileTool {
242+ static func applyEditWithFileSystem (
243+ for fileURL : URL ,
244+ content : String ,
245+ completion : ( ( String ? , Error ? ) -> Void ) ? = nil
246+ ) {
247+ do {
248+ guard let diskFileContent = try? String ( contentsOf : fileURL ) else {
249+ throw InsertEditError . fileNotAccessible ( fileURL )
250+ }
251+
252+ if let focusedElement = XcodeInspector . shared . focusedElement ,
253+ focusedElement . isSourceEditor ,
254+ focusedElement . value != diskFileContent
255+ {
256+ throw InsertEditError . fileHasUnsavedChanges ( fileURL )
257+ }
258+
259+ // write content to disk
260+ try content . write ( to : fileURL , atomically : true , encoding : . utf8 )
261+
262+ Task { @WorkspaceActor in
263+ await WorkspaceInvocationCoordinator ( ) . invokeFilespaceUpdate ( fileURL : fileURL , content : content )
264+ if let completion = completion { completion ( content , nil ) }
265+ }
266+ } catch {
267+ if let completion = completion { completion ( nil , error ) }
268+ Logger . client . info ( " Failed to apply edit for file at \( fileURL ) , \( error ) " )
269+ }
270+ }
271+
272+ static func applyEditWithAccessibilityAPI (
273+ for fileURL : URL ,
274+ content : String ,
275+ completion : ( ( String ? , Error ? ) -> Void ) ? = nil ,
276+ ) {
277+ NSWorkspace . openFileInXcode ( fileURL : fileURL ) { app , error in
278+ do {
279+ if let error = error { throw error }
280+
281+ guard let app = app
282+ else {
283+ throw InsertEditError . openingApplicationUnavailable
284+ }
285+
286+ let appInstanceInspector = AppInstanceInspector ( runningApplication : app )
287+ guard appInstanceInspector . isXcode
288+ else {
289+ throw InsertEditError . fileNotOpenedInXcode
290+ }
291+
292+ let newContent = try applyEdit (
293+ for : fileURL ,
294+ content : content ,
295+ xcodeInstance : appInstanceInspector
296+ )
297+
298+ Task {
299+ await WorkspaceInvocationCoordinator ( ) . invokeFilespaceUpdate ( fileURL : fileURL , content : newContent )
300+ if let completion = completion { completion ( newContent , nil ) }
301+ }
302+ } catch {
303+ if let completion = completion { completion ( nil , error ) }
304+ Logger . client . info ( " Failed to apply edit for file at \( fileURL ) , \( error ) " )
305+ }
306+ }
307+ }
308+ }
0 commit comments