Skip to content

Commit 430d693

Browse files
committed
WIP
1 parent afd7975 commit 430d693

File tree

6 files changed

+315
-67
lines changed

6 files changed

+315
-67
lines changed

Core/Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ let package = Package(
362362
.product(name: "Preferences", package: "Tool"),
363363
.product(name: "ASTParser", package: "Tool"),
364364
.product(name: "SwiftSyntax", package: "swift-syntax"),
365+
.product(name: "SwiftParser", package: "swift-syntax"),
365366
],
366367
path: "Sources/ChatContextCollectors/ActiveDocumentChatContextCollector"
367368
),

Core/Sources/ChatContextCollectors/ActiveDocumentChatContextCollector/GetEditorInfo.swift

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,6 @@ import Foundation
22
import SuggestionModel
33
import XcodeInspector
44

5-
struct EditorInformation {
6-
let editorContent: SourceEditor.Content?
7-
let selectedContent: String
8-
let selectedLines: [String]
9-
let documentURL: URL
10-
let projectURL: URL
11-
let relativePath: String
12-
let language: CodeLanguage
13-
14-
func code(in range: CursorRange) -> String {
15-
return EditorInformation.code(in: selectedLines, inside: range).code
16-
}
17-
18-
static func lines(in code: [String], containing range: CursorRange) -> [String] {
19-
let startIndex = min(max(0, range.start.line), code.endIndex - 1)
20-
let endIndex = min(max(startIndex, range.end.line), code.endIndex - 1)
21-
let selectedLines = code[startIndex...endIndex]
22-
return Array(selectedLines)
23-
}
24-
25-
static func code(in code: [String], inside range: CursorRange) -> (code: String, lines: [String]) {
26-
let rangeLines = lines(in: code, containing: range)
27-
var selectedContent = rangeLines
28-
if !selectedContent.isEmpty {
29-
selectedContent[0] = String(selectedContent[0].dropFirst(range.start.character))
30-
selectedContent[selectedContent.endIndex - 1] = String(
31-
selectedContent[selectedContent.endIndex - 1].dropLast(
32-
selectedContent[selectedContent.endIndex - 1].count - range.end.character
33-
)
34-
)
35-
}
36-
return (selectedContent.joined(), rangeLines)
37-
}
38-
}
39-
405
func getEditorInformation() -> EditorInformation {
416
let editorContent = XcodeInspector.shared.focusedEditor?.content
427
let documentURL = XcodeInspector.shared.activeDocumentURL

Core/Sources/ChatContextCollectors/ActiveDocumentChatContextCollector/SwiftASTReader.swift

Lines changed: 204 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import ASTParser
22
import Foundation
33
import SuggestionModel
4+
import SwiftParser
5+
import SwiftSyntax
46

57
protocol ASTReader {
68
func contextContainingRange(
@@ -44,7 +46,106 @@ struct SwiftASTReader: ASTReader {
4446
case propertyDeclaration = "property_declaration"
4547
case computedProperty = "computed_property"
4648
}
47-
49+
50+
final class SwiftScopeHierarchySyntaxVisitor: SyntaxVisitor {
51+
let tree: SyntaxProtocol
52+
let code: String
53+
let range: CursorRange
54+
private var _scopeHierarchy: [SyntaxProtocol] = []
55+
56+
/// The nodes containing the current range, sorted from inner to outer.
57+
func findScopeHierarchy(_ node: some SyntaxProtocol) -> [SyntaxProtocol] {
58+
walk(node)
59+
return _scopeHierarchy.sorted { $0.position.utf8Offset > $1.position.utf8Offset }
60+
}
61+
62+
/// The nodes containing the current range, sorted from inner to outer.
63+
func findScopeHierarchy() -> [SyntaxProtocol] {
64+
walk(tree)
65+
return _scopeHierarchy.sorted { $0.position.utf8Offset > $1.position.utf8Offset }
66+
}
67+
68+
init(tree: SyntaxProtocol, code: String, range: CursorRange) {
69+
self.tree = tree
70+
self.code = code
71+
self.range = range
72+
super.init(viewMode: .all)
73+
}
74+
75+
func skipChildrenIfPossible(_ node: SyntaxProtocol) -> SyntaxVisitorContinueKind {
76+
if !nodeContainsRange(node) { return .skipChildren }
77+
return .visitChildren
78+
}
79+
80+
func captureNodeIfPossible(_ node: SyntaxProtocol) -> SyntaxVisitorContinueKind {
81+
if !nodeContainsRange(node) { return .skipChildren }
82+
_scopeHierarchy.append(node)
83+
return .visitChildren
84+
}
85+
86+
func nodeContainsRange(_ node: SyntaxProtocol) -> Bool {
87+
let sourceRange = node.sourceRange(converter: .init(file: code, tree: tree))
88+
let cursorRange = CursorRange(sourceRange: sourceRange)
89+
return cursorRange.contains(range)
90+
}
91+
92+
// skip if possible
93+
94+
override func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind {
95+
skipChildrenIfPossible(node)
96+
}
97+
98+
override func visit(_ node: MemberDeclBlockSyntax) -> SyntaxVisitorContinueKind {
99+
skipChildrenIfPossible(node)
100+
}
101+
102+
override func visit(_ node: MemberDeclListItemSyntax) -> SyntaxVisitorContinueKind {
103+
skipChildrenIfPossible(node)
104+
}
105+
106+
// capture if possible
107+
108+
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
109+
captureNodeIfPossible(node)
110+
}
111+
112+
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
113+
captureNodeIfPossible(node)
114+
}
115+
116+
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
117+
captureNodeIfPossible(node)
118+
}
119+
120+
override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
121+
captureNodeIfPossible(node)
122+
}
123+
124+
override func visit(_ node: MacroDeclSyntax) -> SyntaxVisitorContinueKind {
125+
captureNodeIfPossible(node)
126+
}
127+
128+
override func visit(_ node: MacroExpansionDeclSyntax) -> SyntaxVisitorContinueKind {
129+
captureNodeIfPossible(node)
130+
}
131+
132+
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
133+
captureNodeIfPossible(node)
134+
}
135+
136+
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
137+
captureNodeIfPossible(node)
138+
}
139+
140+
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
141+
captureNodeIfPossible(node)
142+
}
143+
144+
override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind {
145+
captureNodeIfPossible(node)
146+
}
147+
}
148+
48149
func createExtraKnowledge(_ code: String) -> String {
49150
var all = [String]()
50151
if code.contains("macro") {
@@ -57,6 +158,99 @@ struct SwiftASTReader: ASTReader {
57158
_ range: CursorRange,
58159
code: String,
59160
codeLines: [String]
161+
) -> CodeContext {
162+
let tree = Parser.parse(source: code)
163+
let visitor = SwiftScopeHierarchySyntaxVisitor(tree: tree, code: code, range: range)
164+
let nodes = visitor.findScopeHierarchy()
165+
166+
func convertRange(_ node: SyntaxProtocol) -> CursorRange {
167+
.init(sourceRange: node.sourceRange(converter: .init(file: code, tree: tree)))
168+
}
169+
170+
if let node = nodes.first {
171+
switch node.kind {
172+
case .structDecl:
173+
guard let node = node as? StructDeclSyntax else { break }
174+
175+
return .init(scope: .scope(
176+
type: node.structKeyword.text,
177+
identifier: node.identifier.text,
178+
range: convertRange(node)
179+
))
180+
case .classDecl:
181+
guard let node = node as? ClassDeclSyntax else { break }
182+
183+
return .init(scope: .scope(
184+
type: node.classKeyword.text,
185+
identifier: node.identifier.text,
186+
range: convertRange(node)
187+
))
188+
case .enumDecl:
189+
guard let node = node as? EnumDeclSyntax else { break }
190+
191+
return .init(scope: .scope(
192+
type: node.enumKeyword.text,
193+
identifier: node.identifier.text,
194+
range: convertRange(node)
195+
))
196+
case .actorDecl:
197+
guard let node = node as? ActorDeclSyntax else { break }
198+
199+
return .init(scope: .scope(
200+
type: node.actorKeyword.text,
201+
identifier: node.identifier.text,
202+
range: convertRange(node)
203+
))
204+
case .macroDecl:
205+
guard let node = node as? MacroDeclSyntax else { break }
206+
207+
return .init(scope: .scope(
208+
type: node.macroKeyword.text,
209+
identifier: node.identifier.text,
210+
range: convertRange(node)
211+
))
212+
case .macroExpansionDecl:
213+
guard let node = node as? MacroExpansionDeclSyntax else { break }
214+
215+
return .init(scope: .scope(
216+
type: "macro expansion",
217+
identifier: node.macro.text,
218+
range: convertRange(node)
219+
))
220+
case .protocolDecl:
221+
guard let node = node as? ProtocolDeclSyntax else { break }
222+
223+
return .init(scope: .scope(
224+
type: node.protocolKeyword.text,
225+
identifier: node.identifier.text,
226+
range: convertRange(node)
227+
))
228+
case .extensionDecl:
229+
guard let node = node as? ExtensionDeclSyntax else { break }
230+
231+
return .init(scope: .scope(
232+
type: node.extensionKeyword.text,
233+
identifier: node.extendedType.description,
234+
range: convertRange(node)
235+
))
236+
case .functionDecl:
237+
guard let node = node as? FunctionDeclSyntax else { break }
238+
239+
return .init(scope: .scope(
240+
type: node.funcKeyword.text,
241+
identifier: node.identifier.text,
242+
range: convertRange(node)
243+
))
244+
}
245+
}
246+
247+
return .init(scope: .top)
248+
}
249+
250+
func contextContainingRange2(
251+
_ range: CursorRange,
252+
code: String,
253+
codeLines: [String]
60254
) -> CodeContext {
61255
let parser = ASTParser(language: .swift)
62256
guard let tree = parser.parse(code) else {
@@ -162,3 +356,12 @@ struct SwiftASTReader: ASTReader {
162356
}
163357
}
164358

359+
extension CursorRange {
360+
init(sourceRange: SourceRange) {
361+
self.init(
362+
start: .init(line: sourceRange.start.line - 1, character: sourceRange.start.column - 1),
363+
end: .init(line: sourceRange.end.line - 1, character: sourceRange.end.column - 1)
364+
)
365+
}
366+
}
367+
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import Foundation
2+
3+
public struct EditorInformation {
4+
public struct SourceEditorContent {
5+
/// The content of the source editor.
6+
public var content: String
7+
/// The content of the source editor in lines.
8+
public var lines: [String]
9+
/// The selection ranges of the source editor.
10+
public var selections: [CursorRange]
11+
/// The cursor position of the source editor.
12+
public var cursorPosition: CursorPosition
13+
/// Line annotations of the source editor.
14+
public var lineAnnotations: [String]
15+
16+
public var selectedContent: String {
17+
if let range = selections.first {
18+
let startIndex = min(
19+
max(0, range.start.line),
20+
lines.endIndex - 1
21+
)
22+
let endIndex = min(
23+
max(startIndex, range.end.line),
24+
lines.endIndex - 1
25+
)
26+
let selectedContent = lines[startIndex...endIndex]
27+
return selectedContent.joined()
28+
}
29+
return ""
30+
}
31+
32+
public init(
33+
content: String,
34+
lines: [String],
35+
selections: [CursorRange],
36+
cursorPosition: CursorPosition,
37+
lineAnnotations: [String]
38+
) {
39+
self.content = content
40+
self.lines = lines
41+
self.selections = selections
42+
self.cursorPosition = cursorPosition
43+
self.lineAnnotations = lineAnnotations
44+
}
45+
}
46+
47+
public let editorContent: SourceEditorContent?
48+
public let selectedContent: String
49+
public let selectedLines: [String]
50+
public let documentURL: URL
51+
public let projectURL: URL
52+
public let relativePath: String
53+
public let language: CodeLanguage
54+
55+
public init(
56+
editorContent: SourceEditorContent?,
57+
selectedContent: String,
58+
selectedLines: [String],
59+
documentURL: URL,
60+
projectURL: URL,
61+
relativePath: String,
62+
language: CodeLanguage
63+
) {
64+
self.editorContent = editorContent
65+
self.selectedContent = selectedContent
66+
self.selectedLines = selectedLines
67+
self.documentURL = documentURL
68+
self.projectURL = projectURL
69+
self.relativePath = relativePath
70+
self.language = language
71+
}
72+
73+
public func code(in range: CursorRange) -> String {
74+
return EditorInformation.code(in: selectedLines, inside: range).code
75+
}
76+
77+
public static func lines(in code: [String], containing range: CursorRange) -> [String] {
78+
let startIndex = min(max(0, range.start.line), code.endIndex - 1)
79+
let endIndex = min(max(startIndex, range.end.line), code.endIndex - 1)
80+
let selectedLines = code[startIndex...endIndex]
81+
return Array(selectedLines)
82+
}
83+
84+
public static func code(
85+
in code: [String],
86+
inside range: CursorRange
87+
) -> (code: String, lines: [String]) {
88+
let rangeLines = lines(in: code, containing: range)
89+
var selectedContent = rangeLines
90+
if !selectedContent.isEmpty {
91+
selectedContent[0] = String(selectedContent[0].dropFirst(range.start.character))
92+
selectedContent[selectedContent.endIndex - 1] = String(
93+
selectedContent[selectedContent.endIndex - 1].dropLast(
94+
selectedContent[selectedContent.endIndex - 1].count - range.end.character
95+
)
96+
)
97+
}
98+
return (selectedContent.joined(), rangeLines)
99+
}
100+
}
101+

0 commit comments

Comments
 (0)