forked from intitni/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPseudoCommandHandler.swift
More file actions
151 lines (142 loc) · 4.87 KB
/
PseudoCommandHandler.swift
File metadata and controls
151 lines (142 loc) · 4.87 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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import ActiveApplicationMonitor
import AppKit
import CopilotModel
import Environment
import Preferences
import SuggestionInjector
import XPCShared
/// It's used to run some commands without really triggering the menu bar item.
///
/// For example, we can use it to generate real-time suggestions without Apple Scripts.
struct PseudoCommandHandler {
func presentPreviousSuggestion() async {
let handler = WindowBaseCommandHandler()
_ = try? await handler.presentPreviousSuggestion(editor: .init(
content: "",
lines: [],
uti: "",
cursorPosition: .outOfScope,
selections: [],
tabSize: 0,
indentSize: 0,
usesTabsForIndentation: false
))
}
func presentNextSuggestion() async {
let handler = WindowBaseCommandHandler()
_ = try? await handler.presentNextSuggestion(editor: .init(
content: "",
lines: [],
uti: "",
cursorPosition: .outOfScope,
selections: [],
tabSize: 0,
indentSize: 0,
usesTabsForIndentation: false
))
}
func generateRealtimeSuggestions() async {
guard let editor = await getEditorContent() else {
try? await Environment.triggerAction("Prefetch Suggestions")
return
}
let mode = UserDefaults.shared.value(for: \.suggestionPresentationMode)
let handler: SuggestionCommandHandler = {
switch mode {
case .comment:
return CommentBaseCommandHandler()
case .floatingWidget:
return WindowBaseCommandHandler()
}
}()
_ = try? await handler.generateRealtimeSuggestions(editor: editor)
}
func rejectSuggestions() async {
let handler = WindowBaseCommandHandler()
_ = try? await handler.rejectSuggestion(editor: .init(
content: "",
lines: [],
uti: "",
cursorPosition: .outOfScope,
selections: [],
tabSize: 0,
indentSize: 0,
usesTabsForIndentation: false
))
}
}
private extension PseudoCommandHandler {
func getFileContent() async
-> (content: String, lines: [String], cursorPosition: CursorPosition)?
{
guard let xcode = ActiveApplicationMonitor.activeXcode else { return nil }
let application = AXUIElementCreateApplication(xcode.processIdentifier)
guard let focusElement = application.focusedElement,
focusElement.description == "Source Editor"
else { return nil }
guard let selectionRange = focusElement.selectedTextRange else { return nil }
let content = focusElement.value
let split = content.breakLines()
let selectedPosition = selectionRange.upperBound
// find row and col from content at selected position
var rowIndex = 0
var count = 0
var colIndex = 0
for (i, row) in split.enumerated() {
if count + row.count > selectedPosition {
rowIndex = i
colIndex = selectedPosition - count
break
}
count += row.count
}
return (content, split, CursorPosition(line: rowIndex, character: colIndex))
}
func getFileURL() async -> URL? {
try? await Environment.fetchCurrentFileURL()
}
@ServiceActor
func getFilespace() async -> Filespace? {
guard let fileURL = await getFileURL() else { return nil }
for (_, workspace) in workspaces {
if let space = workspace.filespaces[fileURL] { return space }
}
return nil
}
@ServiceActor
func getEditorContent() async -> EditorContent? {
guard
let filespace = await getFilespace(),
let uti = filespace.uti,
let tabSize = filespace.tabSize,
let indentSize = filespace.indentSize,
let usesTabsForIndentation = filespace.usesTabsForIndentation,
let content = await getFileContent()
else { return nil }
return .init(
content: content.content,
lines: content.lines,
uti: uti,
cursorPosition: content.cursorPosition,
selections: [],
tabSize: tabSize,
indentSize: indentSize,
usesTabsForIndentation: usesTabsForIndentation
)
}
}
public extension String {
/// Break a string into lines.
func breakLines() -> [String] {
let lines = split(separator: "\n", omittingEmptySubsequences: false)
var all = [String]()
for (index, line) in lines.enumerated() {
if index == lines.endIndex - 1 {
all.append(String(line))
} else {
all.append(String(line) + "\n")
}
}
return all
}
}