Skip to content

Commit 9030e70

Browse files
committed
Update to present the suggestion in the unfinished window
1 parent 947a15a commit 9030e70

File tree

4 files changed

+205
-12
lines changed

4 files changed

+205
-12
lines changed

Core/Sources/Service/GUI/SuggestionPanelController.swift

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,20 @@ final class SuggestionPanelController {
1414
)
1515
it.isReleasedWhenClosed = false
1616
it.isOpaque = false
17-
it.backgroundColor = .white
17+
it.backgroundColor = .clear
1818
it.level = .statusBar
1919
it.contentView = NSHostingView(
2020
rootView: SuggestionPanelView(viewModel: viewModel)
2121
.allowsHitTesting(false)
22-
.frame(width: 500, height: 300)
22+
.frame(width: 400, height: 250)
2323
)
2424
it.setIsVisible(true)
2525
return it
2626
}()
2727

2828
private var displayLinkTask: Task<Void, Never>?
2929
private var activeApplicationMonitorTask: Task<Void, Never>?
30-
private let viewModel = SuggestionPanelViewModel()
30+
let viewModel = SuggestionPanelViewModel()
3131
private var activeApplication: NSRunningApplication? {
3232
ActiveApplicationMonitor.activeApplication
3333
}
@@ -48,9 +48,10 @@ final class SuggestionPanelController {
4848
}
4949
}
5050

51-
/// Update the window location
51+
/// Update the window location.
5252
///
53-
/// - note:
53+
/// - note: It's possible to get the scroll view's postion by getting position on the focus
54+
/// element.
5455
private func updateWindowLocation() {
5556
if let activeXcode = activeApplication,
5657
activeXcode.bundleIdentifier == "com.apple.dt.Xcode"
@@ -76,11 +77,11 @@ final class SuggestionPanelController {
7677
if foundSize, foundPosition, let screen {
7778
frame.origin = .init(
7879
x: frame.maxX + 2,
79-
y: screen.frame.height - frame.minY - 300
80+
y: screen.frame.height - frame.minY - 250
8081
)
81-
frame.size = .init(width: 500, height: 300)
82+
frame.size = .init(width: 400, height: 300)
8283
window.alphaValue = 1
83-
window.setFrame(frame, display: false, animate: true)
84+
window.setFrame(frame, display: false)
8485
return
8586
}
8687
}
@@ -90,14 +91,53 @@ final class SuggestionPanelController {
9091
}
9192
}
9293

94+
@MainActor
9395
final class SuggestionPanelViewModel: ObservableObject {
94-
@Published var suggetion: String = "Hello World"
96+
@Published var startLineIndex: Int = 0
97+
@Published var suggestion: [String] = ["Hello", "World"] {
98+
didSet {
99+
isPanelDisplayed = !suggestion.isEmpty
100+
}
101+
}
102+
103+
@Published var isPanelDisplayed = true
104+
105+
func suggestCode(_ code: String, startLineIndex: Int) {
106+
suggestion = code.split(separator: "\n").map(String.init)
107+
self.startLineIndex = startLineIndex
108+
}
95109
}
96110

97111
struct SuggestionPanelView: View {
98112
@ObservedObject var viewModel: SuggestionPanelViewModel
99113

100114
var body: some View {
101-
Text(viewModel.suggetion)
115+
if viewModel.isPanelDisplayed {
116+
if !viewModel.suggestion.isEmpty {
117+
ZStack(alignment: .topLeading) {
118+
Color(red: 31 / 255, green: 31 / 255, blue: 36 / 255)
119+
ScrollView {
120+
VStack(alignment: .leading) {
121+
ForEach(0..<viewModel.suggestion.count, id: \.self) { index in
122+
HStack(alignment: .firstTextBaseline) {
123+
Text("\(index)")
124+
Text(viewModel.suggestion[index])
125+
}
126+
}
127+
Spacer()
128+
}
129+
.foregroundColor(.white)
130+
.font(.system(size: 12, design: .monospaced))
131+
.multilineTextAlignment(.leading)
132+
.padding()
133+
}
134+
}
135+
.frame(maxWidth: .infinity, maxHeight: .infinity)
136+
} else {
137+
Color(red: 31 / 255, green: 31 / 255, blue: 36 / 255)
138+
}
139+
} else {
140+
EmptyView()
141+
}
102142
}
103143
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import CopilotModel
2+
import Foundation
3+
import SuggestionInjector
4+
import XPCShared
5+
6+
@ServiceActor
7+
struct WindowBaseCommandHandler: SuggestionCommandHanlder {
8+
nonisolated init() {}
9+
10+
func presentSuggestions(editor: EditorContent) async throws -> UpdatedContent? {
11+
Task {
12+
try await _presentSuggestions(editor: editor)
13+
}
14+
return nil
15+
}
16+
17+
private func _presentSuggestions(editor: EditorContent) async throws {
18+
let fileURL = try await Environment.fetchCurrentFileURL()
19+
let (workspace, filespace) = try await Workspace
20+
.fetchOrCreateWorkspaceIfNeeded(fileURL: fileURL)
21+
22+
try Task.checkCancellation()
23+
24+
let snapshot = Filespace.Snapshot(
25+
linesHash: editor.lines.hashValue,
26+
cursorPosition: editor.cursorPosition
27+
)
28+
29+
// There is no need to regenerate suggestions for the same editor content.
30+
guard filespace.suggestionSourceSnapshot != snapshot else { return }
31+
32+
try await workspace.generateSuggestions(
33+
forFileAt: fileURL,
34+
content: editor.content,
35+
lines: editor.lines,
36+
cursorPosition: editor.cursorPosition,
37+
tabSize: editor.tabSize,
38+
indentSize: editor.indentSize,
39+
usesTabsForIndentation: editor.usesTabsForIndentation
40+
)
41+
42+
if let suggestion = filespace.presentingSuggestion {
43+
presentSuggestion(suggestion, lines: editor.lines)
44+
} else {
45+
Task { @MainActor in
46+
GraphicalUserInterfaceController.shared.suggestionPanelController.viewModel
47+
.suggestion = []
48+
}
49+
}
50+
}
51+
52+
func presentNextSuggestion(editor: EditorContent) async throws -> UpdatedContent? {
53+
Task {
54+
try await _presentNextSuggestion(editor: editor)
55+
}
56+
return nil
57+
}
58+
59+
private func _presentNextSuggestion(editor: EditorContent) async throws {
60+
let fileURL = try await Environment.fetchCurrentFileURL()
61+
let (workspace, filespace) = try await Workspace
62+
.fetchOrCreateWorkspaceIfNeeded(fileURL: fileURL)
63+
workspace.selectNextSuggestion(
64+
forFileAt: fileURL,
65+
content: editor.content,
66+
lines: editor.lines
67+
)
68+
69+
if let suggestion = filespace.presentingSuggestion {
70+
presentSuggestion(suggestion, lines: editor.lines)
71+
} else {
72+
Task { @MainActor in
73+
GraphicalUserInterfaceController.shared.suggestionPanelController.viewModel
74+
.suggestion = []
75+
}
76+
}
77+
}
78+
79+
func presentPreviousSuggestion(editor: EditorContent) async throws -> UpdatedContent? {
80+
Task {
81+
try await _presentPreviousSuggestion(editor: editor)
82+
}
83+
return nil
84+
}
85+
86+
private func _presentPreviousSuggestion(editor: EditorContent) async throws {
87+
let fileURL = try await Environment.fetchCurrentFileURL()
88+
let (workspace, filespace) = try await Workspace
89+
.fetchOrCreateWorkspaceIfNeeded(fileURL: fileURL)
90+
workspace.selectPreviousSuggestion(
91+
forFileAt: fileURL,
92+
content: editor.content,
93+
lines: editor.lines
94+
)
95+
96+
if let suggestion = filespace.presentingSuggestion {
97+
presentSuggestion(suggestion, lines: editor.lines)
98+
} else {
99+
Task { @MainActor in
100+
GraphicalUserInterfaceController.shared.suggestionPanelController.viewModel
101+
.suggestion = []
102+
}
103+
}
104+
}
105+
106+
func rejectSuggestion(editor: EditorContent) async throws -> UpdatedContent? {
107+
Task {
108+
try await _rejectSuggestion(editor: editor)
109+
}
110+
return nil
111+
}
112+
113+
private func _rejectSuggestion(editor: EditorContent) async throws {
114+
let fileURL = try await Environment.fetchCurrentFileURL()
115+
let (workspace, _) = try await Workspace.fetchOrCreateWorkspaceIfNeeded(fileURL: fileURL)
116+
workspace.rejectSuggestion(forFileAt: fileURL)
117+
118+
// hide it
119+
120+
Task { @MainActor in
121+
GraphicalUserInterfaceController.shared.suggestionPanelController.viewModel
122+
.suggestion = []
123+
}
124+
}
125+
126+
func acceptSuggestion(editor: EditorContent) async throws -> UpdatedContent? {
127+
Task { @MainActor in
128+
GraphicalUserInterfaceController.shared.suggestionPanelController.viewModel
129+
.suggestion = []
130+
}
131+
return try await CommentBaseCommandHandler().acceptSuggestion(editor: editor)
132+
}
133+
134+
func presentRealtimeSuggestions(editor: EditorContent) async throws -> UpdatedContent? {
135+
// not needed.
136+
return nil
137+
}
138+
139+
func generateRealtimeSuggestions(editor: EditorContent) async throws -> UpdatedContent? {
140+
try await presentSuggestions(editor: editor)
141+
}
142+
143+
func presentSuggestion(_ suggestion: CopilotCompletion, lines: [String]) {
144+
Task { @MainActor in
145+
let viewModel = GraphicalUserInterfaceController.shared.suggestionPanelController
146+
.viewModel
147+
viewModel.suggestCode(suggestion.text, startLineIndex: suggestion.position.line)
148+
}
149+
}
150+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
struct PresentInWindowSuggestionPresenter {
2+
3+
}

Core/Sources/Service/XPCService.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import XPCShared
1313
@ServiceActor
1414
var workspaces = [URL: Workspace]()
1515

16-
#warning("Todo: Find a better place to store it!")
16+
#warning("TODO: Find a better place to store it!")
1717
@ServiceActor
1818
var inflightRealtimeSuggestionsTasks = Set<Task<Void, Never>>()
1919

@@ -107,7 +107,7 @@ public class XPCService: NSObject, XPCServiceProtocol {
107107
let task = Task {
108108
do {
109109
let editor = try JSONDecoder().decode(EditorContent.self, from: editorContent)
110-
let handler = CommentBaseCommandHandler()
110+
let handler = WindowBaseCommandHandler()
111111
guard let updatedContent = try await getUpdatedContent(handler, editor) else {
112112
reply(nil, nil)
113113
return

0 commit comments

Comments
 (0)