Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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