Skip to content

Commit

Permalink
Merge branch 'release/0.35.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
intitni committed Nov 20, 2024
2 parents 35a955f + 1f68309 commit 582fed6
Show file tree
Hide file tree
Showing 141 changed files with 3,966 additions and 2,523 deletions.
8 changes: 8 additions & 0 deletions ChatPlugins/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ChatPlugins"
BuildableName = "ChatPlugins"
BlueprintName = "ChatPlugins"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ChatPlugins"
BuildableName = "ChatPlugins"
BlueprintName = "ChatPlugins"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
37 changes: 37 additions & 0 deletions ChatPlugins/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "ChatPlugins",
platforms: [.macOS(.v12)],
products: [
.library(
name: "ChatPlugins",
targets: ["TerminalChatPlugin", "ShortcutChatPlugin"]
),
],
dependencies: [
.package(path: "../Tool"),
],
targets: [
.target(
name: "TerminalChatPlugin",
dependencies: [
.product(name: "Chat", package: "Tool"),
.product(name: "Terminal", package: "Tool"),
.product(name: "AppMonitoring", package: "Tool"),
]
),
.target(
name: "ShortcutChatPlugin",
dependencies: [
.product(name: "Chat", package: "Tool"),
.product(name: "Terminal", package: "Tool"),
.product(name: "AppMonitoring", package: "Tool"),
]
),
]
)

118 changes: 118 additions & 0 deletions ChatPlugins/Sources/ShortcutChatPlugin/ShortcutChatPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import ChatBasic
import Foundation
import Terminal

public final class ShortcutChatPlugin: ChatPlugin {
public static var id: String { "com.intii.shortcut" }
public static var command: String { "shortcut" }
public static var name: String { "Shortcut" }
public static var description: String { """
Run a shortcut and use message content as input. You need to provide the shortcut name as an argument, for example, `/shortcut(Shortcut Name)`.
""" }

let terminal: TerminalType

init(terminal: TerminalType) {
self.terminal = terminal
}

public init() {
terminal = Terminal()
}

public func send(_ request: Request) async -> AsyncThrowingStream<Response, any Error> {
return .init { continuation in
let task = Task {
let id = "\(Self.command)-\(UUID().uuidString)"

guard let shortcutName = request.arguments.first, !shortcutName.isEmpty else {
continuation.yield(.content(.text(
"Please provide the shortcut name in format: `/\(Self.command)(shortcut name)`"
)))
return
}

var input = String(request.text).trimmingCharacters(in: .whitespacesAndNewlines)
if input.isEmpty {
// if no input detected, use the previous message as input
input = request.history.last?.content ?? ""
}

do {
continuation.yield(.startAction(
id: "run",
task: "Run shortcut `\(shortcutName)`"
))

let env = ProcessInfo.processInfo.environment
let shell = env["SHELL"] ?? "/bin/bash"
let temporaryURL = FileManager.default.temporaryDirectory
let temporaryInputFileURL = temporaryURL
.appendingPathComponent("\(id)-input.txt")
let temporaryOutputFileURL = temporaryURL
.appendingPathComponent("\(id)-output")

try input.write(to: temporaryInputFileURL, atomically: true, encoding: .utf8)

let command = """
shortcuts run "\(shortcutName)" \
-i "\(temporaryInputFileURL.path)" \
-o "\(temporaryOutputFileURL.path)"
"""

continuation.yield(.startAction(
id: "run",
task: "Run shortcut \(shortcutName)"
))

do {
let result = try await terminal.runCommand(
shell,
arguments: ["-i", "-l", "-c", command],
currentDirectoryURL: nil,
environment: [:]
)
continuation.yield(.finishAction(id: "run", result: .success(result)))
} catch {
continuation.yield(.finishAction(
id: "run",
result: .failure(error.localizedDescription)
))
throw error
}

await Task.yield()
try Task.checkCancellation()

if FileManager.default.fileExists(atPath: temporaryOutputFileURL.path) {
let data = try Data(contentsOf: temporaryOutputFileURL)
if let text = String(data: data, encoding: .utf8) {
var response = text
if response.isEmpty {
response = "Finished"
}
continuation.yield(.content(.text(response)))
} else {
let content = """
[View File](\(temporaryOutputFileURL))
"""
continuation.yield(.content(.text(content)))
}
} else {
continuation.yield(.content(.text("Finished")))
}

} catch {
continuation.yield(.content(.text(error.localizedDescription)))
}

continuation.finish()
}

continuation.onTermination = { _ in
task.cancel()
}
}
}
}

120 changes: 120 additions & 0 deletions ChatPlugins/Sources/TerminalChatPlugin/TerminalChatPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
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()
}
}
}
}
}

6 changes: 6 additions & 0 deletions ChatPlugins/Tests/ChatPluginsTests/ChatPluginsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Testing
@testable import ChatPlugins

@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
}
6 changes: 4 additions & 2 deletions Copilot for Xcode.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@
C8216B772980370100AD38C7 /* ReloadLaunchAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadLaunchAgent.swift; sourceTree = "<group>"; };
C828B27D2B1F241500E7612A /* ExtensionPoint.appextensionpoint */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = ExtensionPoint.appextensionpoint; sourceTree = "<group>"; };
C82E38492A1F025F00D4EADF /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
C84FD9D72CC671C600BE5093 /* ChatPlugins */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = ChatPlugins; sourceTree = "<group>"; };
C8520300293C4D9000460097 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
C8520308293D805800460097 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
C861A6A229E5503F005C41A3 /* PromptToCodeCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptToCodeCommand.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -341,6 +342,7 @@
C81458AE293A009800135263 /* Config.debug.xcconfig */,
C8CD828229B88006008D044D /* TestPlan.xctestplan */,
C828B27D2B1F241500E7612A /* ExtensionPoint.appextensionpoint */,
C84FD9D72CC671C600BE5093 /* ChatPlugins */,
C81D181E2A1B509B006C1B70 /* Tool */,
C8189B282938979000C9DCDA /* Core */,
C8189B182938972F00C9DCDA /* Copilot for Xcode */,
Expand Down Expand Up @@ -775,7 +777,7 @@
DEVELOPMENT_TEAM = 5YKZ4Y3DAW;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = EditorExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "$(EXTESNION_BUNDLE_NAME)";
INFOPLIST_KEY_CFBundleDisplayName = "$(EXTENSION_BUNDLE_NAME)";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down Expand Up @@ -803,7 +805,7 @@
DEVELOPMENT_TEAM = 5YKZ4Y3DAW;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = EditorExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "$(EXTESNION_BUNDLE_NAME)";
INFOPLIST_KEY_CFBundleDisplayName = "$(EXTENSION_BUNDLE_NAME)";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down
Loading

0 comments on commit 582fed6

Please sign in to comment.