forked from intitni/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTerminalChatPlugin.swift
More file actions
120 lines (99 loc) · 4 KB
/
Copy pathTerminalChatPlugin.swift
File metadata and controls
120 lines (99 loc) · 4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import ChatBasic
import Foundation
import Terminal
import XcodeInspector
public final class TerminalChatPlugin: ChatPlugin {
public static var id: String { "com.intii.terminal" }
public static var command: String { "run" }
public static var name: String { "Terminal" }
public static var description: String { """
Run the command in the message from terminal.
You can use environment variable `$FILE_PATH` and `$PROJECT_ROOT` to access the current file path and project root.
""" }
let terminal: TerminalType
init(terminal: TerminalType) {
self.terminal = terminal
}
public init() {
terminal = Terminal()
}
public func formatContent(_ content: Response.Content) -> Response.Content {
switch content {
case let .text(content):
return .text("""
```sh
\(content)
```
""")
}
}
public func send(_ request: Request) async -> AsyncThrowingStream<Response, any Error> {
return .init { continuation in
let task = Task {
var updateTime = Date()
func streamOutput(_ content: String) {
defer { updateTime = Date() }
if Date().timeIntervalSince(updateTime) > 60 * 2 {
continuation.yield(.startNewMessage)
continuation.yield(.startAction(
id: "run",
task: "Continue `\(request.text)`"
))
continuation.yield(.finishAction(
id: "run",
result: .success("Executed.")
))
continuation.yield(.content(.text("[continue]\n")))
continuation.yield(.content(.text(content)))
} else {
continuation.yield(.content(.text(content)))
}
}
do {
let fileURL = await XcodeInspector.shared.safe.realtimeActiveDocumentURL
let projectURL = await XcodeInspector.shared.safe.realtimeActiveProjectURL
var environment = [String: String]()
if let fileURL {
environment["FILE_PATH"] = fileURL.path
}
if let projectURL {
environment["PROJECT_ROOT"] = projectURL.path
}
try Task.checkCancellation()
let env = ProcessInfo.processInfo.environment
let shell = env["SHELL"] ?? "/bin/bash"
continuation.yield(.startAction(id: "run", task: "Run `\(request.text)`"))
let output = terminal.streamCommand(
shell,
arguments: ["-i", "-l", "-c", request.text],
currentDirectoryURL: projectURL,
environment: environment
)
continuation.yield(.finishAction(
id: "run",
result: .success("Executed.")
))
for try await content in output {
try Task.checkCancellation()
streamOutput(content)
}
} catch let error as Terminal.TerminationError {
continuation.yield(.content(.text("""
[error: \(error.reason)]
""")))
} catch {
continuation.yield(.content(.text("""
[error: \(error.localizedDescription)]
""")))
}
continuation.finish()
}
continuation.onTermination = { _ in
task.cancel()
Task {
await self.terminal.terminate()
}
}
}
}
}