Skip to content

Commit 74e0b6b

Browse files
committed
Update text extraction from code to use uft-16 view
1 parent 9634ad0 commit 74e0b6b

3 files changed

Lines changed: 178 additions & 52 deletions

File tree

Tool/Sources/SuggestionModel/EditorInformation.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,10 @@ public struct EditorInformation {
8686

8787
public static func lines(in code: [String], containing range: CursorRange) -> [String] {
8888
guard !code.isEmpty else { return [] }
89+
guard range.start.line <= range.end.line else { return [] }
8990
let startIndex = min(max(0, range.start.line), code.endIndex - 1)
9091
let endIndex = min(max(startIndex, range.end.line), code.endIndex - 1)
92+
guard startIndex <= endIndex else { return [] }
9193
let selectedLines = code[startIndex...endIndex]
9294
return Array(selectedLines)
9395
}
@@ -97,18 +99,26 @@ public struct EditorInformation {
9799
inside range: CursorRange,
98100
ignoreColumns: Bool = false
99101
) -> (code: String, lines: [String]) {
102+
guard range.start <= range.end else { return ("", []) }
103+
100104
let rangeLines = lines(in: code, containing: range)
101105
if ignoreColumns {
102106
return (rangeLines.joined(), rangeLines)
103107
}
104108
var content = rangeLines
105109
if !content.isEmpty {
106-
let dropLastCount = max(0, content[content.endIndex - 1].count - range.end.character)
110+
let dropLastCount = max(
111+
0,
112+
content[content.endIndex - 1].utf16.count - range.end.character
113+
)
107114
content[content.endIndex - 1] = String(
115+
content[content.endIndex - 1].utf16.dropLast(dropLastCount)
116+
) ?? String(
108117
content[content.endIndex - 1].dropLast(dropLastCount)
109118
)
110119
let dropFirstCount = max(0, range.start.character)
111-
content[0] = String(content[0].dropFirst(dropFirstCount))
120+
content[0] = String(content[0].utf16.dropFirst(dropFirstCount))
121+
?? String(content[0].dropFirst(dropFirstCount))
112122
}
113123
return (content.joined(), rangeLines)
114124
}

Tool/Sources/XPCShared/Models.swift

Lines changed: 9 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import SuggestionModel
21
import Foundation
2+
import SuggestionModel
33

44
public struct EditorContent: Codable {
55
public struct Selection: Codable {
@@ -60,53 +60,12 @@ public struct UpdatedContent: Codable {
6060
}
6161

6262
func selectedCode(in selection: EditorContent.Selection, for lines: [String]) -> String {
63-
let startPosition = selection.start
64-
let endPosition = CursorPosition(
65-
line: selection.end.line,
66-
character: selection.end.character - 1
67-
)
68-
69-
guard startPosition.line >= 0, startPosition.line < lines.count else { return "" }
70-
guard startPosition.character >= 0,
71-
startPosition.character < lines[startPosition.line].count else { return "" }
72-
guard endPosition.line >= 0,
73-
endPosition.line < lines.count
74-
|| (endPosition.line == lines.count && endPosition.character == -1)
75-
else { return "" }
76-
guard endPosition.line >= startPosition.line else { return "" }
77-
guard endPosition.character >= -1 else { return "" }
78-
79-
if endPosition.line < lines.endIndex {
80-
guard endPosition.character < lines[endPosition.line].count else { return "" }
81-
}
82-
83-
var code = ""
84-
if startPosition.line == endPosition.line {
85-
guard endPosition.character >= startPosition.character else { return "" }
86-
let line = lines[startPosition.line]
87-
let startIndex = line.index(line.startIndex, offsetBy: startPosition.character)
88-
let endIndex = line.index(line.startIndex, offsetBy: endPosition.character)
89-
code = String(line[startIndex...endIndex])
90-
} else {
91-
let startLine = lines[startPosition.line]
92-
let startIndex = startLine.index(
93-
startLine.startIndex,
94-
offsetBy: startPosition.character
95-
)
96-
code += String(startLine[startIndex...])
97-
98-
if startPosition.line + 1 < endPosition.line {
99-
for line in lines[startPosition.line + 1...endPosition.line - 1] {
100-
code += line
101-
}
102-
}
103-
104-
if endPosition.character >= 0, endPosition.line < lines.endIndex {
105-
let endLine = lines[endPosition.line]
106-
let endIndex = endLine.index(endLine.startIndex, offsetBy: endPosition.character)
107-
code += String(endLine[...endIndex])
108-
}
109-
}
110-
111-
return code
63+
return EditorInformation.code(
64+
in: lines,
65+
inside: .init(
66+
start: .init(line: selection.start.line, character: selection.start.character),
67+
end: .init(line: selection.end.line, character: selection.end.character)
68+
),
69+
ignoreColumns: false
70+
).code
11271
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import Foundation
2+
import XCTest
3+
@testable import SuggestionModel
4+
5+
final class TextExtrationFromCodeTests: XCTestCase {
6+
func test_empty_selection() {
7+
let selection = CursorRange(
8+
start: CursorPosition(line: 0, character: 0),
9+
end: CursorPosition(line: 0, character: 0)
10+
)
11+
let lines = ["let foo = 1\n", "let bar = 2\n"]
12+
let result = EditorInformation.code(
13+
in: lines,
14+
inside: selection,
15+
ignoreColumns: false
16+
)
17+
XCTAssertEqual(result.code, "")
18+
XCTAssertEqual(result.lines, ["let foo = 1\n"])
19+
}
20+
21+
func test_single_line_selection() {
22+
let selection = CursorRange(
23+
start: CursorPosition(line: 0, character: 4),
24+
end: CursorPosition(line: 0, character: 10)
25+
)
26+
let lines = ["let foo = 1\n", "let bar = 2\n"]
27+
let result = EditorInformation.code(
28+
in: lines,
29+
inside: selection,
30+
ignoreColumns: false
31+
)
32+
XCTAssertEqual(result.code, "foo = ")
33+
XCTAssertEqual(result.lines, ["let foo = 1\n"])
34+
}
35+
36+
func test_single_line_selection_with_emoji() {
37+
let selection = CursorRange(
38+
start: CursorPosition(line: 0, character: 4),
39+
end: CursorPosition(line: 0, character: 10)
40+
)
41+
let lines = ["let 🎆🎆o = 1\n", "let bar = 2\n"]
42+
let result = EditorInformation.code(
43+
in: lines,
44+
inside: selection,
45+
ignoreColumns: false
46+
)
47+
XCTAssertEqual(result.code, "🎆🎆o ")
48+
XCTAssertEqual(result.lines, ["let 🎆🎆o = 1\n"])
49+
}
50+
51+
func test_single_line_selection_cutting_emoji() {
52+
// undefined behavior
53+
54+
let selection = CursorRange(
55+
start: CursorPosition(line: 0, character: 5),
56+
end: CursorPosition(line: 0, character: 10)
57+
)
58+
let lines = ["let 🎆🎆o = 1\n", "let bar = 2\n"]
59+
let result = EditorInformation.code(
60+
in: lines,
61+
inside: selection,
62+
ignoreColumns: false
63+
)
64+
XCTAssertEqual(result.lines, ["let 🎆🎆o = 1\n"])
65+
}
66+
67+
func test_single_line_selection_at_line_end() {
68+
let selection = CursorRange(
69+
start: CursorPosition(line: 0, character: 8),
70+
end: CursorPosition(line: 0, character: 11)
71+
)
72+
let lines = ["let foo = 1\n", "let bar = 2\n"]
73+
let result = EditorInformation.code(
74+
in: lines,
75+
inside: selection,
76+
ignoreColumns: false
77+
)
78+
XCTAssertEqual(result.code, "= 1")
79+
XCTAssertEqual(result.lines, ["let foo = 1\n"])
80+
}
81+
82+
func test_multi_line_selection() {
83+
let selection = CursorRange(
84+
start: CursorPosition(line: 0, character: 4),
85+
end: CursorPosition(line: 1, character: 11)
86+
)
87+
let lines = ["let foo = 1\n", "let bar = 2\n", "let baz = 3\n"]
88+
let result = EditorInformation.code(
89+
in: lines,
90+
inside: selection,
91+
ignoreColumns: false
92+
)
93+
XCTAssertEqual(result.code, "foo = 1\nlet bar = 2")
94+
XCTAssertEqual(result.lines, ["let foo = 1\n", "let bar = 2\n"])
95+
}
96+
97+
func test_multi_line_selection_with_emoji() {
98+
let selection = CursorRange(
99+
start: CursorPosition(line: 0, character: 4),
100+
end: CursorPosition(line: 1, character: 11)
101+
)
102+
let lines = ["🎆🎆 foo = 1\n", "let bar = 2\n", "let baz = 3\n"]
103+
let result = EditorInformation.code(
104+
in: lines,
105+
inside: selection,
106+
ignoreColumns: false
107+
)
108+
XCTAssertEqual(result.code, " foo = 1\nlet bar = 2")
109+
XCTAssertEqual(result.lines, ["🎆🎆 foo = 1\n", "let bar = 2\n"])
110+
}
111+
112+
func test_invalid_selection() {
113+
let selection = CursorRange(
114+
start: CursorPosition(line: 1, character: 4),
115+
end: CursorPosition(line: 0, character: 10)
116+
)
117+
let lines = ["let foo = 1", "let bar = 2"]
118+
let result = EditorInformation.code(
119+
in: lines,
120+
inside: selection,
121+
ignoreColumns: false
122+
)
123+
XCTAssertEqual(result.code, "")
124+
XCTAssertEqual(result.lines, [])
125+
}
126+
127+
func test_single_line_selection_ignoring_column() {
128+
let selection = CursorRange(
129+
start: CursorPosition(line: 0, character: 4),
130+
end: CursorPosition(line: 0, character: 10)
131+
)
132+
let lines = ["let foo = 1\n", "let bar = 2\n"]
133+
let result = EditorInformation.code(
134+
in: lines,
135+
inside: selection,
136+
ignoreColumns: true
137+
)
138+
XCTAssertEqual(result.code, "let foo = 1\n")
139+
XCTAssertEqual(result.lines, ["let foo = 1\n"])
140+
}
141+
142+
func test_multi_line_selection_ignoring_column() {
143+
let selection = CursorRange(
144+
start: CursorPosition(line: 0, character: 4),
145+
end: CursorPosition(line: 1, character: 11)
146+
)
147+
let lines = ["let foo = 1\n", "let bar = 2\n", "let baz = 3\n"]
148+
let result = EditorInformation.code(
149+
in: lines,
150+
inside: selection,
151+
ignoreColumns: true
152+
)
153+
XCTAssertEqual(result.code, "let foo = 1\nlet bar = 2\n")
154+
XCTAssertEqual(result.lines, ["let foo = 1\n", "let bar = 2\n"])
155+
}
156+
}
157+

0 commit comments

Comments
 (0)