Skip to content

Commit 5f07119

Browse files
committed
Add syntax highlighting to suggestion panel
1 parent 79fb2f1 commit 5f07119

File tree

5 files changed

+101
-16
lines changed

5 files changed

+101
-16
lines changed

Core/Sources/Service/SuggestionCommandHandler/WindowBaseCommandHandler.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import CopilotModel
2+
import CopilotService
23
import Environment
34
import Foundation
45
import os.log
@@ -55,6 +56,7 @@ struct WindowBaseCommandHandler: SuggestionCommandHandler {
5556
presenter.presentSuggestion(
5657
suggestion,
5758
lines: editor.lines,
59+
language: filespace.language,
5860
fileURL: fileURL,
5961
currentSuggestionIndex: filespace.suggestionIndex,
6062
suggestionCount: filespace.suggestions.count
@@ -83,6 +85,7 @@ struct WindowBaseCommandHandler: SuggestionCommandHandler {
8385
presenter.presentSuggestion(
8486
suggestion,
8587
lines: editor.lines,
88+
language: filespace.language,
8689
fileURL: fileURL,
8790
currentSuggestionIndex: filespace.suggestionIndex,
8891
suggestionCount: filespace.suggestions.count
@@ -111,6 +114,7 @@ struct WindowBaseCommandHandler: SuggestionCommandHandler {
111114
presenter.presentSuggestion(
112115
suggestion,
113116
lines: editor.lines,
117+
language: filespace.language,
114118
fileURL: fileURL,
115119
currentSuggestionIndex: filespace.suggestionIndex,
116120
suggestionCount: filespace.suggestions.count

Core/Sources/Service/SuggestionPresenter/PresentInWindowSuggestionPresenter.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ struct PresentInWindowSuggestionPresenter {
55
func presentSuggestion(
66
_ suggestion: CopilotCompletion,
77
lines: [String],
8+
language: String,
89
fileURL: URL,
910
currentSuggestionIndex: Int,
1011
suggestionCount: Int
@@ -13,6 +14,7 @@ struct PresentInWindowSuggestionPresenter {
1314
let controller = GraphicalUserInterfaceController.shared.suggestionWidget
1415
controller.suggestCode(
1516
suggestion.text,
17+
language: language,
1618
startLineIndex: suggestion.position.line,
1719
fileURL: fileURL,
1820
currentSuggestionIndex: currentSuggestionIndex,

Core/Sources/SuggestionWidget/SuggestionPanelView.swift

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import SwiftUI
44
@MainActor
55
final class SuggestionPanelViewModel: ObservableObject {
66
@Published var startLineIndex: Int
7-
@Published var suggestion: [String]
7+
@Published var suggestion: [NSAttributedString]
88
@Published var isPanelDisplayed: Bool
99
@Published var suggestionCount: Int
1010
@Published var currentSuggestionIndex: Int
@@ -16,7 +16,7 @@ final class SuggestionPanelViewModel: ObservableObject {
1616

1717
public init(
1818
startLineIndex: Int = 0,
19-
suggestion: [String] = [],
19+
suggestion: [NSAttributedString] = [],
2020
isPanelDisplayed: Bool = false,
2121
suggestionCount: Int = 0,
2222
currentSuggestionIndex: Int = 0,
@@ -41,6 +41,7 @@ struct SuggestionPanelView: View {
4141
@ObservedObject var viewModel: SuggestionPanelViewModel
4242
@State var isHovering: Bool = false
4343
@State var codeHeight: Double = 0
44+
let backgroundColor = #colorLiteral(red: 0.1580096483, green: 0.1730263829, blue: 0.2026666105, alpha: 1)
4445

4546
var body: some View {
4647
VStack {
@@ -53,7 +54,7 @@ struct SuggestionPanelView: View {
5354
ScrollView {
5455
CodeBlock(viewModel: viewModel)
5556
}
56-
.background(Color(red: 31 / 255, green: 31 / 255, blue: 36 / 255))
57+
.background(Color(nsColor: backgroundColor))
5758

5859
ToolBar(viewModel: viewModel)
5960
}
@@ -106,7 +107,7 @@ struct CodeBlock: View {
106107
ForEach(0..<viewModel.suggestion.count, id: \.self) { index in
107108
Text("\(index + viewModel.startLineIndex + 1)")
108109
.foregroundColor(Color.white.opacity(0.6))
109-
Text(viewModel.suggestion[index])
110+
Text(AttributedString(viewModel.suggestion[index]))
110111
.frame(maxWidth: .infinity, alignment: .leading)
111112
.multilineTextAlignment(.leading)
112113
.lineSpacing(4)
@@ -210,16 +211,17 @@ struct SuggestionPanelView_Preview: PreviewProvider {
210211
SuggestionPanelView(viewModel: .init(
211212
startLineIndex: 8,
212213
suggestion:
213-
"""
214-
LazyVGrid(columns: [GridItem(.fixed(30)), GridItem(.flexible())]) {
214+
highlighted(
215+
code: """
216+
LazyVGrid(columns: [GridItem(.fixed(30)), GridItem(.flexible())]) {
215217
ForEach(0..<viewModel.suggestion.count, id: \\.self) { index in // lkjaskldjalksjdlkasjdlkajslkdjas
216218
Text(viewModel.suggestion[index])
217219
.frame(maxWidth: .infinity, alignment: .leading)
218220
.multilineTextAlignment(.leading)
219221
}
220-
Spacer()
221-
}
222-
""".split(separator: "\n").map(String.init),
222+
""",
223+
language: "swift"
224+
),
223225
isPanelDisplayed: true
224226
))
225227
.frame(width: 450, height: 400)

Core/Sources/SuggestionWidget/SuggestionWidgetController.swift

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import ActiveApplicationMonitor
22
import AppKit
33
import AXNotificationStream
44
import Environment
5+
import Highlightr
6+
import Splash
57
import SwiftUI
68
import XPCShared
79

@@ -90,7 +92,13 @@ public final class SuggestionWidgetController {
9092
}
9193

9294
enum Suggestion {
93-
case code([String], startLineIndex: Int, currentSuggestionIndex: Int, suggestionCount: Int)
95+
case code(
96+
String,
97+
language: String,
98+
startLineIndex: Int,
99+
currentSuggestionIndex: Int,
100+
suggestionCount: Int
101+
)
94102
}
95103

96104
public nonisolated init() {
@@ -132,19 +140,21 @@ public final class SuggestionWidgetController {
132140

133141
public func suggestCode(
134142
_ code: String,
143+
language: String,
135144
startLineIndex: Int,
136145
fileURL: URL,
137146
currentSuggestionIndex: Int,
138147
suggestionCount: Int
139148
) {
140149
withAnimation(.easeInOut(duration: 0.2)) {
141-
suggestionPanelViewModel.suggestion = code.split(separator: "\n").map(String.init)
150+
suggestionPanelViewModel.suggestion = highlighted(code: code, language: language)
142151
suggestionPanelViewModel.startLineIndex = startLineIndex
143152
suggestionPanelViewModel.isPanelDisplayed = true
144153
suggestionPanelViewModel.currentSuggestionIndex = currentSuggestionIndex
145154
suggestionPanelViewModel.suggestionCount = suggestionCount
146155
suggestionForFiles[fileURL] = .code(
147-
suggestionPanelViewModel.suggestion,
156+
code,
157+
language: language,
148158
startLineIndex: startLineIndex,
149159
currentSuggestionIndex: currentSuggestionIndex,
150160
suggestionCount: suggestionCount
@@ -194,8 +204,17 @@ public final class SuggestionWidgetController {
194204
continue
195205
}
196206
switch suggestion {
197-
case let .code(code, startLineIndex, currentSuggestionIndex, suggestionCount):
198-
suggestionPanelViewModel.suggestion = code
207+
case let .code(
208+
code,
209+
language,
210+
startLineIndex,
211+
currentSuggestionIndex,
212+
suggestionCount
213+
):
214+
suggestionPanelViewModel.suggestion = highlighted(
215+
code: code,
216+
language: language
217+
)
199218
suggestionPanelViewModel.startLineIndex = startLineIndex
200219
suggestionPanelViewModel.currentSuggestionIndex = currentSuggestionIndex
201220
suggestionPanelViewModel.suggestionCount = suggestionCount
@@ -247,7 +266,10 @@ public final class SuggestionWidgetController {
247266
if foundSize, foundPosition, let screen, let firstScreen {
248267
let proposedAnchorFrameOnTheRightSide = CGRect(
249268
x: frame.maxX - Style.widgetPadding - Style.widgetWidth,
250-
y: max(firstScreen.frame.height - frame.maxY + Style.widgetPadding, 4 + screen.frame.minY),
269+
y: max(
270+
firstScreen.frame.height - frame.maxY + Style.widgetPadding,
271+
4 + screen.frame.minY
272+
),
251273
width: Style.widgetWidth,
252274
height: Style.widgetHeight
253275
)
@@ -312,3 +334,58 @@ public final class SuggestionWidgetController {
312334
hide()
313335
}
314336
}
337+
338+
func highlighted(code: String, language: String) -> [NSAttributedString] {
339+
switch language {
340+
case "swift":
341+
let plainTextColor = #colorLiteral(red: 0.6509803922, green: 0.6980392157, blue: 0.7529411765, alpha: 1)
342+
let highlighter =
343+
SyntaxHighlighter(
344+
format: AttributedStringOutputFormat(theme: .init(
345+
font: .init(size: 14),
346+
plainTextColor: plainTextColor,
347+
tokenColors: [
348+
.keyword: #colorLiteral(red: 0.8258609176, green: 0.5708742738, blue: 0.8922662139, alpha: 1),
349+
.string: #colorLiteral(red: 0.6253595352, green: 0.7963448763, blue: 0.5427476764, alpha: 1),
350+
.type: #colorLiteral(red: 0.9221783876, green: 0.7978314757, blue: 0.5575165749, alpha: 1),
351+
.call: #colorLiteral(red: 0.4466812611, green: 0.742190659, blue: 0.9515134692, alpha: 1),
352+
.number: #colorLiteral(red: 0.8620631099, green: 0.6468816996, blue: 0.4395158887, alpha: 1),
353+
.comment: #colorLiteral(red: 0.4233166873, green: 0.4612616301, blue: 0.5093258619, alpha: 1),
354+
.property: #colorLiteral(red: 0.906378448, green: 0.5044228435, blue: 0.5263597369, alpha: 1),
355+
.dotAccess: #colorLiteral(red: 0.906378448, green: 0.5044228435, blue: 0.5263597369, alpha: 1),
356+
.preprocessing: #colorLiteral(red: 0.3776347041, green: 0.8792117238, blue: 0.4709561467, alpha: 1),
357+
]
358+
))
359+
)
360+
let formatted = NSMutableAttributedString(attributedString: highlighter.highlight(code))
361+
formatted.addAttributes(
362+
[.font: NSFont.monospacedSystemFont(ofSize: 13, weight: .regular)],
363+
range: NSRange(location: 0, length: formatted.length)
364+
)
365+
return splitAttributedString(formatted)
366+
default:
367+
guard let highlighter = Highlightr() else {
368+
return splitAttributedString(NSAttributedString(string: code))
369+
}
370+
highlighter.setTheme(to: "atom-one-dark")
371+
highlighter.theme.setCodeFont(.monospacedSystemFont(ofSize: 13, weight: .regular))
372+
guard let formatted = highlighter.highlight(code, as: "swift") else {
373+
return splitAttributedString(NSAttributedString(string: code))
374+
}
375+
return splitAttributedString(formatted)
376+
}
377+
}
378+
379+
func splitAttributedString(_ inputString: NSAttributedString) -> [NSAttributedString] {
380+
let input = inputString.string
381+
let separatedInput = input.components(separatedBy: "\n")
382+
var output = [NSAttributedString]()
383+
var start = 0
384+
for sub in separatedInput {
385+
let range = NSMakeRange(start, sub.utf16.count)
386+
let attributedString = inputString.attributedSubstring(from: range)
387+
output.append(attributedString)
388+
start += range.length + 1
389+
}
390+
return output
391+
}

Core/Sources/SuggestionWidget/WidgetView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ struct WidgetView_Preview: PreviewProvider {
109109

110110
WidgetView(
111111
viewModel: .init(isProcessing: false),
112-
panelViewModel: .init(suggestion: ["Hello"]),
112+
panelViewModel: .init(suggestion: [.init(string: "Hello")]),
113113
isHovering: false
114114
)
115115
}

0 commit comments

Comments
 (0)