Skip to content

Commit

Permalink
Add Bold and Italics Support To Themes (#289)
Browse files Browse the repository at this point in the history
### 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
  • Loading branch information
thecoolwinter authored Jan 11, 2025
1 parent 2810b76 commit 17525ad
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ import CodeEditSourceEditor
extension EditorTheme {
static var standard: EditorTheme {
EditorTheme(
text: .init(hex: "000000"),
insertionPoint: .init(hex: "000000"),
invisibles: .init(hex: "D6D6D6"),
background: .init(hex: "FFFFFF"),
lineHighlight: .init(hex: "ECF5FF"),
selection: .init(hex: "B2D7FF"),
keywords: .init(hex: "9B2393"),
commands: .init(hex: "326D74"),
types: .init(hex: "0B4F79"),
attributes: .init(hex: "815F03"),
variables: .init(hex: "0F68A0"),
values: .init(hex: "6C36A9"),
numbers: .init(hex: "1C00CF"),
strings: .init(hex: "C41A16"),
characters: .init(hex: "1C00CF"),
comments: .init(hex: "267507")
text: Attribute(color: NSColor(hex: "000000")),
insertionPoint: NSColor(hex: "000000"),
invisibles: Attribute(color: NSColor(hex: "D6D6D6")),
background: NSColor(hex: "FFFFFF"),
lineHighlight: NSColor(hex: "ECF5FF"),
selection: NSColor(hex: "B2D7FF"),
keywords: Attribute(color: NSColor(hex: "9B2393"), bold: true),
commands: Attribute(color: NSColor(hex: "326D74")),
types: Attribute(color: NSColor(hex: "0B4F79")),
attributes: Attribute(color: NSColor(hex: "815F03")),
variables: Attribute(color: NSColor(hex: "0F68A0")),
values: Attribute(color: NSColor(hex: "6C36A9")),
numbers: Attribute(color: NSColor(hex: "1C00CF")),
strings: Attribute(color: NSColor(hex: "C41A16"), bold: true, italic: true),
characters: Attribute(color: NSColor(hex: "1C00CF")),
comments: Attribute(color: NSColor(hex: "267507"), italic: true)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extension TextViewController {
extension TextViewController: ThemeAttributesProviding {
public func attributesFor(_ capture: CaptureName?) -> [NSAttributedString.Key: Any] {
[
.font: font,
.font: theme.fontFor(for: capture, from: font),
.foregroundColor: theme.colorFor(capture),
.kern: textView.kern
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ public class TextViewController: NSViewController {
self.textView = TextView(
string: string,
font: font,
textColor: theme.text,
textColor: theme.text.color,
lineHeightMultiplier: lineHeightMultiple,
wrapLines: wrapLines,
isEditable: isEditable,
Expand Down
123 changes: 74 additions & 49 deletions Sources/CodeEditSourceEditor/Theme/EditorTheme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,58 @@

import SwiftUI

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

public var text: NSColor
public init(color: NSColor, bold: Bool = false, italic: Bool = false) {
self.color = color
self.bold = bold
self.italic = italic
}
}

public var text: Attribute
public var insertionPoint: NSColor
public var invisibles: NSColor
public var invisibles: Attribute
public var background: NSColor
public var lineHighlight: NSColor
public var selection: NSColor
public var keywords: NSColor
public var commands: NSColor
public var types: NSColor
public var attributes: NSColor
public var variables: NSColor
public var values: NSColor
public var numbers: NSColor
public var strings: NSColor
public var characters: NSColor
public var comments: NSColor
public var keywords: Attribute
public var commands: Attribute
public var types: Attribute
public var attributes: Attribute
public var variables: Attribute
public var values: Attribute
public var numbers: Attribute
public var strings: Attribute
public var characters: Attribute
public var comments: Attribute

public init(
text: NSColor,
text: Attribute,
insertionPoint: NSColor,
invisibles: NSColor,
invisibles: Attribute,
background: NSColor,
lineHighlight: NSColor,
selection: NSColor,
keywords: NSColor,
commands: NSColor,
types: NSColor,
attributes: NSColor,
variables: NSColor,
values: NSColor,
numbers: NSColor,
strings: NSColor,
characters: NSColor,
comments: NSColor
keywords: Attribute,
commands: Attribute,
types: Attribute,
attributes: Attribute,
variables: Attribute,
values: Attribute,
numbers: Attribute,
strings: Attribute,
characters: Attribute,
comments: Attribute
) {
self.text = text
self.insertionPoint = insertionPoint
Expand All @@ -63,10 +78,10 @@ public struct EditorTheme {
self.comments = comments
}

/// Get the color from ``theme`` for the specified capture name.
/// - Parameter capture: The capture name
/// - Returns: A `NSColor`
func colorFor(_ capture: CaptureName?) -> NSColor {
/// Maps a capture type to the attributes for that capture determined by the theme.
/// - Parameter capture: The capture to map to.
/// - Returns: Theme attributes for the capture.
private func mapCapture(_ capture: CaptureName?) -> Attribute {
switch capture {
case .include, .constructor, .keyword, .boolean, .variableBuiltin,
.keywordReturn, .keywordFunction, .repeat, .conditional, .tag:
Expand All @@ -82,25 +97,35 @@ public struct EditorTheme {
default: return text
}
}
}

extension EditorTheme: Equatable {
public static func == (lhs: EditorTheme, rhs: EditorTheme) -> Bool {
return lhs.text == rhs.text &&
lhs.insertionPoint == rhs.insertionPoint &&
lhs.invisibles == rhs.invisibles &&
lhs.background == rhs.background &&
lhs.lineHighlight == rhs.lineHighlight &&
lhs.selection == rhs.selection &&
lhs.keywords == rhs.keywords &&
lhs.commands == rhs.commands &&
lhs.types == rhs.types &&
lhs.attributes == rhs.attributes &&
lhs.variables == rhs.variables &&
lhs.values == rhs.values &&
lhs.numbers == rhs.numbers &&
lhs.strings == rhs.strings &&
lhs.characters == rhs.characters &&
lhs.comments == rhs.comments
/// Get the color from ``theme`` for the specified capture name.
/// - Parameter capture: The capture name
/// - Returns: A `NSColor`
func colorFor(_ capture: CaptureName?) -> NSColor {
return mapCapture(capture).color
}

/// Returns the correct font with attributes (bold and italics) for a given capture name.
/// - Parameters:
/// - capture: The capture name.
/// - font: The font to add attributes to.
/// - Returns: A new font that has the correct attributes for the capture.
func fontFor(for capture: CaptureName?, from font: NSFont) -> NSFont {
let attributes = mapCapture(capture)
guard attributes.bold || attributes.italic else {
return font
}

var font = font

if attributes.bold {
font = NSFontManager.shared.convert(font, toHaveTrait: .boldFontMask)
}

if attributes.italic {
font = NSFontManager.shared.convert(font, toHaveTrait: .italicFontMask)
}

return font
}
}
24 changes: 12 additions & 12 deletions Tests/CodeEditSourceEditorTests/Mock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,22 @@ enum Mock {

static func theme() -> EditorTheme {
EditorTheme(
text: .textColor,
text: EditorTheme.Attribute(color: .textColor),
insertionPoint: .textColor,
invisibles: .gray,
invisibles: EditorTheme.Attribute(color: .gray),
background: .textBackgroundColor,
lineHighlight: .highlightColor,
selection: .selectedTextColor,
keywords: .systemPink,
commands: .systemBlue,
types: .systemMint,
attributes: .systemTeal,
variables: .systemCyan,
values: .systemOrange,
numbers: .systemYellow,
strings: .systemRed,
characters: .systemRed,
comments: .systemGreen
keywords: EditorTheme.Attribute(color: .systemPink),
commands: EditorTheme.Attribute(color: .systemBlue),
types: EditorTheme.Attribute(color: .systemMint),
attributes: EditorTheme.Attribute(color: .systemTeal),
variables: EditorTheme.Attribute(color: .systemCyan),
values: EditorTheme.Attribute(color: .systemOrange),
numbers: EditorTheme.Attribute(color: .systemYellow),
strings: EditorTheme.Attribute(color: .systemRed),
characters: EditorTheme.Attribute(color: .systemRed),
comments: EditorTheme.Attribute(color: .systemGreen)
)
}

Expand Down
19 changes: 1 addition & 18 deletions Tests/CodeEditSourceEditorTests/TextViewControllerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,7 @@ final class TextViewControllerTests: XCTestCase {
var theme: EditorTheme!

override func setUpWithError() throws {
theme = EditorTheme(
text: .textColor,
insertionPoint: .textColor,
invisibles: .gray,
background: .textBackgroundColor,
lineHighlight: .highlightColor,
selection: .selectedTextColor,
keywords: .systemPink,
commands: .systemBlue,
types: .systemMint,
attributes: .systemTeal,
variables: .systemCyan,
values: .systemOrange,
numbers: .systemYellow,
strings: .systemRed,
characters: .systemRed,
comments: .systemGreen
)
theme = Mock.theme()
controller = TextViewController(
string: "",
language: .default,
Expand Down

0 comments on commit 17525ad

Please sign in to comment.