Skip to content

Commit 4fddb24

Browse files
committed
Merge branch 'feature/prompt-to-code-with-line-annotation' into develop
2 parents 6a690f7 + 8f9bc5c commit 4fddb24

5 files changed

Lines changed: 122 additions & 49 deletions

File tree

Core/Sources/PromptToCodeService/OpenAIPromptToCodeService.swift

Lines changed: 79 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import Foundation
22
import OpenAIService
33
import Preferences
44
import SuggestionModel
5+
import XcodeInspector
56

67
public final class OpenAIPromptToCodeService: PromptToCodeServiceType {
78
var service: (any ChatGPTServiceType)?
8-
9+
910
public init() {}
1011

1112
public func stopResponding() {
@@ -14,13 +15,9 @@ public final class OpenAIPromptToCodeService: PromptToCodeServiceType {
1415

1516
public func modifyCode(
1617
code: String,
17-
language: CodeLanguage,
18-
indentSize: Int,
19-
usesTabsForIndentation: Bool,
2018
requirement: String,
21-
projectRootURL: URL,
22-
fileURL: URL,
23-
allCode: String,
19+
source: PromptToCodeSource,
20+
isDetached: Bool,
2421
extraSystemPrompt: String?,
2522
generateDescriptionRequirement: Bool?
2623
) async throws -> AsyncThrowingStream<(code: String, description: String), Error> {
@@ -31,9 +28,25 @@ public final class OpenAIPromptToCodeService: PromptToCodeServiceType {
3128
{
3229
return ""
3330
}
34-
return userPreferredLanguage.isEmpty ? "" : "in \(userPreferredLanguage)"
31+
return userPreferredLanguage.isEmpty ? "" : " in \(userPreferredLanguage)"
3532
}()
3633

34+
let editor: EditorInformation = XcodeInspector.shared.focusedEditorContent ?? .init(
35+
editorContent: .init(
36+
content: source.allCode,
37+
lines: [],
38+
selections: [source.range],
39+
cursorPosition: .outOfScope,
40+
lineAnnotations: []
41+
),
42+
selectedContent: code,
43+
selectedLines: [],
44+
documentURL: source.documentURL,
45+
projectURL: source.projectRootURL,
46+
relativePath: "",
47+
language: source.language
48+
)
49+
3750
let rule: String = {
3851
func generateDescription(index: Int) -> String {
3952
let generateDescription = generateDescriptionRequirement ?? UserDefaults.shared
@@ -46,7 +59,7 @@ public final class OpenAIPromptToCodeService: PromptToCodeServiceType {
4659
"""
4760
: "\(index). Reply with the result."
4861
}
49-
switch language {
62+
switch editor.language {
5063
case .builtIn(.markdown), .plaintext:
5164
if code.isEmpty {
5265
return """
@@ -82,20 +95,20 @@ public final class OpenAIPromptToCodeService: PromptToCodeServiceType {
8295
}()
8396

8497
let systemPrompt = {
85-
switch language {
98+
switch editor.language {
8699
case .builtIn(.markdown), .plaintext:
87100
if code.isEmpty {
88101
return """
89-
You are good at writing in \(language.rawValue).
90-
The active file is: \(fileURL.lastPathComponent).
102+
You are good at writing in \(editor.language.rawValue).
103+
The active file is: \(editor.documentURL.lastPathComponent).
91104
\(extraSystemPrompt ?? "")
92105
93106
\(rule)
94107
"""
95108
} else {
96109
return """
97-
You are good at writing in \(language.rawValue).
98-
The active file is: \(fileURL.lastPathComponent).
110+
You are good at writing in \(editor.language.rawValue).
111+
The active file is: \(editor.documentURL.lastPathComponent).
99112
\(extraSystemPrompt ?? "")
100113
101114
\(rule)
@@ -104,16 +117,16 @@ public final class OpenAIPromptToCodeService: PromptToCodeServiceType {
104117
default:
105118
if code.isEmpty {
106119
return """
107-
You are a senior programer in writing in \(language.rawValue).
108-
The active file is: \(fileURL.lastPathComponent).
120+
You are a senior programer in writing in \(editor.language.rawValue).
121+
The active file is: \(editor.documentURL.lastPathComponent).
109122
\(extraSystemPrompt ?? "")
110123
111124
\(rule)
112125
"""
113126
} else {
114127
return """
115-
You are a senior programer in writing in \(language.rawValue).
116-
The active file is: \(fileURL.lastPathComponent).
128+
You are a senior programer in writing in \(editor.language.rawValue).
129+
The active file is: \(editor.documentURL.lastPathComponent).
117130
\(extraSystemPrompt ?? "")
118131
119132
\(rule)
@@ -122,31 +135,44 @@ public final class OpenAIPromptToCodeService: PromptToCodeServiceType {
122135
}
123136
}()
124137

138+
let annotations = isDetached
139+
? []
140+
: extractAnnotations(editorInformation: editor, source: source)
141+
125142
let firstMessage: String? = {
126143
if code.isEmpty { return nil }
127-
switch language {
144+
switch editor.language {
128145
case .builtIn(.markdown), .plaintext:
129146
return """
130147
```
131148
\(code)
132149
```
150+
151+
line annotations found:
152+
\(annotations.map { "- \($0)" }.joined(separator: "\n"))
133153
"""
134154
default:
135155
return """
136156
```
137157
\(code)
138158
```
159+
160+
line annotations found:
161+
\(annotations.map { "- \($0)" }.joined(separator: "\n"))
139162
"""
140163
}
141164
}()
142165

166+
let indentation = getCommonLeadingSpaceCount(code)
167+
143168
let secondMessage = """
144-
Requirements:###
145-
\(requirement)
146-
###
169+
I will update the code you just provided.
170+
It looks like every line has an indentation of \(indentation) spaces, I will keep that.
171+
172+
What is your requirement?
147173
"""
148174

149-
let configuration = UserPreferenceChatGPTConfiguration()
175+
let configuration = UserPreferenceChatGPTConfiguration()
150176
.overriding(.init(temperature: 0))
151177
let memory = AutoManagedChatGPTMemory(
152178
systemPrompt: systemPrompt,
@@ -161,9 +187,10 @@ public final class OpenAIPromptToCodeService: PromptToCodeServiceType {
161187
if let firstMessage {
162188
await memory.mutateHistory { history in
163189
history.append(.init(role: .user, content: firstMessage))
190+
history.append(.init(role: .assistant, content: secondMessage))
164191
}
165192
}
166-
let stream = try await chatGPTService.send(content: secondMessage)
193+
let stream = try await chatGPTService.send(content: requirement)
167194
return .init { continuation in
168195
Task {
169196
var content = ""
@@ -231,5 +258,33 @@ public final class OpenAIPromptToCodeService: PromptToCodeServiceType {
231258

232259
return (code, description)
233260
}
261+
262+
func getCommonLeadingSpaceCount(_ code: String) -> Int {
263+
let lines = code.split(separator: "\n")
264+
guard !lines.isEmpty else { return 0 }
265+
var commonCount = Int.max
266+
for line in lines {
267+
let count = line.prefix(while: { $0 == " " }).count
268+
commonCount = min(commonCount, count)
269+
if commonCount == 0 { break }
270+
}
271+
return commonCount
272+
}
273+
274+
func extractAnnotations(
275+
editorInformation: EditorInformation,
276+
source: PromptToCodeSource
277+
) -> [String] {
278+
guard let annotations = editorInformation.editorContent?.lineAnnotations else { return [] }
279+
return annotations
280+
.lazy
281+
.filter { annotation in
282+
annotation.line >= source.range.start.line + 1
283+
&& annotation.line <= source.range.end.line + 1
284+
}.map { annotation in
285+
let relativeLine = annotation.line - source.range.start.line
286+
return "line \(relativeLine): \(annotation.type) \(annotation.message)"
287+
}
288+
}
234289
}
235290

Core/Sources/PromptToCodeService/PreviewPromptToCodeService.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,9 @@ public final class PreviewPromptToCodeService: PromptToCodeServiceType {
66

77
public func modifyCode(
88
code: String,
9-
language: CodeLanguage,
10-
indentSize: Int,
11-
usesTabsForIndentation: Bool,
129
requirement: String,
13-
projectRootURL: URL,
14-
fileURL: URL,
15-
allCode: String,
10+
source: PromptToCodeSource,
11+
isDetached: Bool,
1612
extraSystemPrompt: String?,
1713
generateDescriptionRequirement: Bool?
1814
) async throws -> AsyncThrowingStream<(code: String, description: String), Error> {

Core/Sources/PromptToCodeService/PromptToCodeServiceType.swift

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,38 @@ import SuggestionModel
55
public protocol PromptToCodeServiceType {
66
func modifyCode(
77
code: String,
8-
language: CodeLanguage,
9-
indentSize: Int,
10-
usesTabsForIndentation: Bool,
118
requirement: String,
12-
projectRootURL: URL,
13-
fileURL: URL,
14-
allCode: String,
9+
source: PromptToCodeSource,
10+
isDetached: Bool,
1511
extraSystemPrompt: String?,
1612
generateDescriptionRequirement: Bool?
1713
) async throws -> AsyncThrowingStream<(code: String, description: String), Error>
1814

1915
func stopResponding()
2016
}
2117

18+
public struct PromptToCodeSource {
19+
public var language: CodeLanguage
20+
public var documentURL: URL
21+
public var projectRootURL: URL
22+
public var allCode: String
23+
public var range: CursorRange
24+
25+
public init(
26+
language: CodeLanguage,
27+
documentURL: URL,
28+
projectRootURL: URL,
29+
allCode: String,
30+
range: CursorRange
31+
) {
32+
self.language = language
33+
self.documentURL = documentURL
34+
self.projectRootURL = projectRootURL
35+
self.allCode = allCode
36+
self.range = range
37+
}
38+
}
39+
2240
public struct PromptToCodeServiceDependencyKey: DependencyKey {
2341
public static let liveValue: PromptToCodeServiceType = PreviewPromptToCodeService()
2442
public static let previewValue: PromptToCodeServiceType = PreviewPromptToCodeService()

Core/Sources/Service/GUI/ChatTabFactory.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,15 @@ enum ChatTabFactory {
8787

8888
let result = try await service.modifyCode(
8989
code: prompt,
90-
language: .plaintext,
91-
indentSize: 4,
92-
usesTabsForIndentation: true,
9390
requirement: instruction ?? "Modify content.",
94-
projectRootURL: .init(fileURLWithPath: "/"),
95-
fileURL: .init(fileURLWithPath: "/"),
96-
allCode: prompt,
91+
source: .init(
92+
language: .plaintext,
93+
documentURL: .init(fileURLWithPath: "/"),
94+
projectRootURL: .init(fileURLWithPath: "/"),
95+
allCode: prompt,
96+
range: .outOfScope
97+
),
98+
isDetached: true,
9799
extraSystemPrompt: extraSystemPrompt,
98100
generateDescriptionRequirement: false
99101
)

Core/Sources/SuggestionWidget/FeatureReducers/PromptToCode.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public struct PromptToCode: ReducerProtocol {
6363
@BindingState public var prompt: String
6464
@BindingState public var isContinuous: Bool
6565
@BindingState public var isAttachedToSelectionRange: Bool
66-
66+
6767
public var filename: String { documentURL.lastPathComponent }
6868
public var canRevert: Bool { history != .empty }
6969

@@ -156,13 +156,15 @@ public struct PromptToCode: ReducerProtocol {
156156
do {
157157
let stream = try await promptToCodeService.modifyCode(
158158
code: copiedState.code,
159-
language: copiedState.language,
160-
indentSize: copiedState.indentSize,
161-
usesTabsForIndentation: copiedState.usesTabsForIndentation,
162159
requirement: copiedState.prompt,
163-
projectRootURL: copiedState.projectRootURL,
164-
fileURL: copiedState.documentURL,
165-
allCode: copiedState.allCode,
160+
source: .init(
161+
language: copiedState.language,
162+
documentURL: copiedState.documentURL,
163+
projectRootURL: copiedState.projectRootURL,
164+
allCode: copiedState.allCode,
165+
range: copiedState.selectionRange ?? .outOfScope
166+
),
167+
isDetached: !copiedState.isAttachedToSelectionRange,
166168
extraSystemPrompt: copiedState.extraSystemPrompt,
167169
generateDescriptionRequirement: copiedState
168170
.generateDescriptionRequirement

0 commit comments

Comments
 (0)