Skip to content

Commit 17525ad

Browse files
Add Bold and Italics Support To Themes (#289)
### Description - Adds bold and italics support to the editor theme type. > [!WARNING] > This is a API-breaking change, merging this will require a minor version update when releasing. ### Related Issues - closes #283 ### Checklist - [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md) - [x] The issues this PR addresses are related to each other - [x] My changes generate no new warnings - [x] My code builds and runs on my machine - [x] My changes are all related to the related issue above - [x] I documented my code ### Screenshots Update example app default theme with bold and italics on strings, italics on comments, and bold on keywords. https://github.com/user-attachments/assets/136331a2-ddcd-4fe8-a5e7-536055cf6249
1 parent 2810b76 commit 17525ad

File tree

6 files changed

+105
-97
lines changed

6 files changed

+105
-97
lines changed

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample/Extensions/EditorTheme+Default.swift

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,22 @@ import CodeEditSourceEditor
1212
extension EditorTheme {
1313
static var standard: EditorTheme {
1414
EditorTheme(
15-
text: .init(hex: "000000"),
16-
insertionPoint: .init(hex: "000000"),
17-
invisibles: .init(hex: "D6D6D6"),
18-
background: .init(hex: "FFFFFF"),
19-
lineHighlight: .init(hex: "ECF5FF"),
20-
selection: .init(hex: "B2D7FF"),
21-
keywords: .init(hex: "9B2393"),
22-
commands: .init(hex: "326D74"),
23-
types: .init(hex: "0B4F79"),
24-
attributes: .init(hex: "815F03"),
25-
variables: .init(hex: "0F68A0"),
26-
values: .init(hex: "6C36A9"),
27-
numbers: .init(hex: "1C00CF"),
28-
strings: .init(hex: "C41A16"),
29-
characters: .init(hex: "1C00CF"),
30-
comments: .init(hex: "267507")
15+
text: Attribute(color: NSColor(hex: "000000")),
16+
insertionPoint: NSColor(hex: "000000"),
17+
invisibles: Attribute(color: NSColor(hex: "D6D6D6")),
18+
background: NSColor(hex: "FFFFFF"),
19+
lineHighlight: NSColor(hex: "ECF5FF"),
20+
selection: NSColor(hex: "B2D7FF"),
21+
keywords: Attribute(color: NSColor(hex: "9B2393"), bold: true),
22+
commands: Attribute(color: NSColor(hex: "326D74")),
23+
types: Attribute(color: NSColor(hex: "0B4F79")),
24+
attributes: Attribute(color: NSColor(hex: "815F03")),
25+
variables: Attribute(color: NSColor(hex: "0F68A0")),
26+
values: Attribute(color: NSColor(hex: "6C36A9")),
27+
numbers: Attribute(color: NSColor(hex: "1C00CF")),
28+
strings: Attribute(color: NSColor(hex: "C41A16"), bold: true, italic: true),
29+
characters: Attribute(color: NSColor(hex: "1C00CF")),
30+
comments: Attribute(color: NSColor(hex: "267507"), italic: true)
3131
)
3232
}
3333
}

Sources/CodeEditSourceEditor/Controller/TextViewController+Highlighter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extension TextViewController {
2929
extension TextViewController: ThemeAttributesProviding {
3030
public func attributesFor(_ capture: CaptureName?) -> [NSAttributedString.Key: Any] {
3131
[
32-
.font: font,
32+
.font: theme.fontFor(for: capture, from: font),
3333
.foregroundColor: theme.colorFor(capture),
3434
.kern: textView.kern
3535
]

Sources/CodeEditSourceEditor/Controller/TextViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ public class TextViewController: NSViewController {
262262
self.textView = TextView(
263263
string: string,
264264
font: font,
265-
textColor: theme.text,
265+
textColor: theme.text.color,
266266
lineHeightMultiplier: lineHeightMultiple,
267267
wrapLines: wrapLines,
268268
isEditable: isEditable,

Sources/CodeEditSourceEditor/Theme/EditorTheme.swift

Lines changed: 74 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,58 @@
77

88
import SwiftUI
99

10-
/// A collection of `NSColor` used for syntax higlighting
11-
public struct EditorTheme {
10+
/// A collection of attributes used for syntax highlighting and other colors for the editor.
11+
///
12+
/// Attributes of a theme that do not apply to text (background, line highlight) are a single `NSColor` for simplicity.
13+
/// All other attributes use the ``EditorTheme/Attribute`` type to store
14+
public struct EditorTheme: Equatable {
15+
/// Represents attributes that can be applied to style text.
16+
public struct Attribute: Equatable, Hashable, Sendable {
17+
public let color: NSColor
18+
public let bold: Bool
19+
public let italic: Bool
1220

13-
public var text: NSColor
21+
public init(color: NSColor, bold: Bool = false, italic: Bool = false) {
22+
self.color = color
23+
self.bold = bold
24+
self.italic = italic
25+
}
26+
}
27+
28+
public var text: Attribute
1429
public var insertionPoint: NSColor
15-
public var invisibles: NSColor
30+
public var invisibles: Attribute
1631
public var background: NSColor
1732
public var lineHighlight: NSColor
1833
public var selection: NSColor
19-
public var keywords: NSColor
20-
public var commands: NSColor
21-
public var types: NSColor
22-
public var attributes: NSColor
23-
public var variables: NSColor
24-
public var values: NSColor
25-
public var numbers: NSColor
26-
public var strings: NSColor
27-
public var characters: NSColor
28-
public var comments: NSColor
34+
public var keywords: Attribute
35+
public var commands: Attribute
36+
public var types: Attribute
37+
public var attributes: Attribute
38+
public var variables: Attribute
39+
public var values: Attribute
40+
public var numbers: Attribute
41+
public var strings: Attribute
42+
public var characters: Attribute
43+
public var comments: Attribute
2944

3045
public init(
31-
text: NSColor,
46+
text: Attribute,
3247
insertionPoint: NSColor,
33-
invisibles: NSColor,
48+
invisibles: Attribute,
3449
background: NSColor,
3550
lineHighlight: NSColor,
3651
selection: NSColor,
37-
keywords: NSColor,
38-
commands: NSColor,
39-
types: NSColor,
40-
attributes: NSColor,
41-
variables: NSColor,
42-
values: NSColor,
43-
numbers: NSColor,
44-
strings: NSColor,
45-
characters: NSColor,
46-
comments: NSColor
52+
keywords: Attribute,
53+
commands: Attribute,
54+
types: Attribute,
55+
attributes: Attribute,
56+
variables: Attribute,
57+
values: Attribute,
58+
numbers: Attribute,
59+
strings: Attribute,
60+
characters: Attribute,
61+
comments: Attribute
4762
) {
4863
self.text = text
4964
self.insertionPoint = insertionPoint
@@ -63,10 +78,10 @@ public struct EditorTheme {
6378
self.comments = comments
6479
}
6580

66-
/// Get the color from ``theme`` for the specified capture name.
67-
/// - Parameter capture: The capture name
68-
/// - Returns: A `NSColor`
69-
func colorFor(_ capture: CaptureName?) -> NSColor {
81+
/// Maps a capture type to the attributes for that capture determined by the theme.
82+
/// - Parameter capture: The capture to map to.
83+
/// - Returns: Theme attributes for the capture.
84+
private func mapCapture(_ capture: CaptureName?) -> Attribute {
7085
switch capture {
7186
case .include, .constructor, .keyword, .boolean, .variableBuiltin,
7287
.keywordReturn, .keywordFunction, .repeat, .conditional, .tag:
@@ -82,25 +97,35 @@ public struct EditorTheme {
8297
default: return text
8398
}
8499
}
85-
}
86100

87-
extension EditorTheme: Equatable {
88-
public static func == (lhs: EditorTheme, rhs: EditorTheme) -> Bool {
89-
return lhs.text == rhs.text &&
90-
lhs.insertionPoint == rhs.insertionPoint &&
91-
lhs.invisibles == rhs.invisibles &&
92-
lhs.background == rhs.background &&
93-
lhs.lineHighlight == rhs.lineHighlight &&
94-
lhs.selection == rhs.selection &&
95-
lhs.keywords == rhs.keywords &&
96-
lhs.commands == rhs.commands &&
97-
lhs.types == rhs.types &&
98-
lhs.attributes == rhs.attributes &&
99-
lhs.variables == rhs.variables &&
100-
lhs.values == rhs.values &&
101-
lhs.numbers == rhs.numbers &&
102-
lhs.strings == rhs.strings &&
103-
lhs.characters == rhs.characters &&
104-
lhs.comments == rhs.comments
101+
/// Get the color from ``theme`` for the specified capture name.
102+
/// - Parameter capture: The capture name
103+
/// - Returns: A `NSColor`
104+
func colorFor(_ capture: CaptureName?) -> NSColor {
105+
return mapCapture(capture).color
106+
}
107+
108+
/// Returns the correct font with attributes (bold and italics) for a given capture name.
109+
/// - Parameters:
110+
/// - capture: The capture name.
111+
/// - font: The font to add attributes to.
112+
/// - Returns: A new font that has the correct attributes for the capture.
113+
func fontFor(for capture: CaptureName?, from font: NSFont) -> NSFont {
114+
let attributes = mapCapture(capture)
115+
guard attributes.bold || attributes.italic else {
116+
return font
117+
}
118+
119+
var font = font
120+
121+
if attributes.bold {
122+
font = NSFontManager.shared.convert(font, toHaveTrait: .boldFontMask)
123+
}
124+
125+
if attributes.italic {
126+
font = NSFontManager.shared.convert(font, toHaveTrait: .italicFontMask)
127+
}
128+
129+
return font
105130
}
106131
}

Tests/CodeEditSourceEditorTests/Mock.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,22 +69,22 @@ enum Mock {
6969

7070
static func theme() -> EditorTheme {
7171
EditorTheme(
72-
text: .textColor,
72+
text: EditorTheme.Attribute(color: .textColor),
7373
insertionPoint: .textColor,
74-
invisibles: .gray,
74+
invisibles: EditorTheme.Attribute(color: .gray),
7575
background: .textBackgroundColor,
7676
lineHighlight: .highlightColor,
7777
selection: .selectedTextColor,
78-
keywords: .systemPink,
79-
commands: .systemBlue,
80-
types: .systemMint,
81-
attributes: .systemTeal,
82-
variables: .systemCyan,
83-
values: .systemOrange,
84-
numbers: .systemYellow,
85-
strings: .systemRed,
86-
characters: .systemRed,
87-
comments: .systemGreen
78+
keywords: EditorTheme.Attribute(color: .systemPink),
79+
commands: EditorTheme.Attribute(color: .systemBlue),
80+
types: EditorTheme.Attribute(color: .systemMint),
81+
attributes: EditorTheme.Attribute(color: .systemTeal),
82+
variables: EditorTheme.Attribute(color: .systemCyan),
83+
values: EditorTheme.Attribute(color: .systemOrange),
84+
numbers: EditorTheme.Attribute(color: .systemYellow),
85+
strings: EditorTheme.Attribute(color: .systemRed),
86+
characters: EditorTheme.Attribute(color: .systemRed),
87+
comments: EditorTheme.Attribute(color: .systemGreen)
8888
)
8989
}
9090

Tests/CodeEditSourceEditorTests/TextViewControllerTests.swift

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,7 @@ final class TextViewControllerTests: XCTestCase {
1212
var theme: EditorTheme!
1313

1414
override func setUpWithError() throws {
15-
theme = EditorTheme(
16-
text: .textColor,
17-
insertionPoint: .textColor,
18-
invisibles: .gray,
19-
background: .textBackgroundColor,
20-
lineHighlight: .highlightColor,
21-
selection: .selectedTextColor,
22-
keywords: .systemPink,
23-
commands: .systemBlue,
24-
types: .systemMint,
25-
attributes: .systemTeal,
26-
variables: .systemCyan,
27-
values: .systemOrange,
28-
numbers: .systemYellow,
29-
strings: .systemRed,
30-
characters: .systemRed,
31-
comments: .systemGreen
32-
)
15+
theme = Mock.theme()
3316
controller = TextViewController(
3417
string: "",
3518
language: .default,

0 commit comments

Comments
 (0)