Skip to content

Commit

Permalink
Added support for more code editor themes
Browse files Browse the repository at this point in the history
  • Loading branch information
nanashili committed Mar 19, 2022
1 parent 5287e33 commit 8139217
Show file tree
Hide file tree
Showing 10 changed files with 1,035 additions and 15 deletions.
9 changes: 9 additions & 0 deletions CodeEdit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
04660F6427E3ACAF00477777 /* Appearances.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04660F6327E3ACAF00477777 /* Appearances.swift */; };
04660F6627E3ACEF00477777 /* ReopenBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04660F6527E3ACEF00477777 /* ReopenBehavior.swift */; };
04660F6A27E51E5C00477777 /* CodeEditWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04660F6927E51E5C00477777 /* CodeEditWindowController.swift */; };
2074890327E5FFD60039CF4A /* CodeEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 2074890227E5FFD60039CF4A /* CodeEditor */; };
286620A527E4AB6900E18C2B /* BreadcrumbsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286620A427E4AB6900E18C2B /* BreadcrumbsComponent.swift */; };
2875A46D27E3BE5B007805F8 /* BreadcrumbsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2875A46C27E3BE5B007805F8 /* BreadcrumbsView.swift */; };
287776E727E3413200D46668 /* SideBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287776E627E3413200D46668 /* SideBar.swift */; };
Expand Down Expand Up @@ -70,6 +71,7 @@
04ADA0CA27E5D41F00BF00B2 /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/Localizable.strings; sourceTree = "<group>"; };
04F2BF0E27DBB28E0024EAB1 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
04F2BF1127DBB3C10024EAB1 /* GeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsView.swift; sourceTree = "<group>"; };
2074890127E5FEF30039CF4A /* CodeEditor */ = {isa = PBXFileReference; lastKnownFileType = folder; name = CodeEditor; path = CodeEditModules/Modules/CodeEditor; sourceTree = "<group>"; };
2805D9E827E5ED180032BC56 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
286620A427E4AB6900E18C2B /* BreadcrumbsComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsComponent.swift; sourceTree = "<group>"; };
2875A46C27E3BE5B007805F8 /* BreadcrumbsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -102,6 +104,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
2074890327E5FFD60039CF4A /* CodeEditor in Frameworks */,
5C403B8F27E20F8000788241 /* WorkspaceClient in Frameworks */,
D70F5E2C27E4E8CF004EE4B9 /* WelcomeModule in Frameworks */,
5CF38A5E27E48E6C0096A0F7 /* CodeFile in Frameworks */,
Expand Down Expand Up @@ -203,6 +206,7 @@
5C403B8D27E20F8000788241 /* Frameworks */ = {
isa = PBXGroup;
children = (
2074890127E5FEF30039CF4A /* CodeEditor */,
);
name = Frameworks;
sourceTree = "<group>";
Expand Down Expand Up @@ -297,6 +301,7 @@
5C403B8E27E20F8000788241 /* WorkspaceClient */,
5CF38A5D27E48E6C0096A0F7 /* CodeFile */,
D70F5E2B27E4E8CF004EE4B9 /* WelcomeModule */,
2074890227E5FFD60039CF4A /* CodeEditor */,
);
productName = CodeEdit;
productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */;
Expand Down Expand Up @@ -827,6 +832,10 @@
/* End XCConfigurationList section */

/* Begin XCSwiftPackageProductDependency section */
2074890227E5FFD60039CF4A /* CodeEditor */ = {
isa = XCSwiftPackageProductDependency;
productName = CodeEditor;
};
5C403B8E27E20F8000788241 /* WorkspaceClient */ = {
isa = XCSwiftPackageProductDependency;
productName = WorkspaceClient;
Expand Down
9 changes: 0 additions & 9 deletions CodeEdit.xcworkspace/xcshareddata/swiftpm/Package.resolved
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
{
"object": {
"pins": [
{
"package": "CodeEditor",
"repositoryURL": "https://github.com/ZeeZide/CodeEditor.git",
"state": {
"branch": null,
"revision": "5856fac22b0a2174dbdea212784567c8c9cd1129",
"version": "1.2.0"
}
},
{
"package": "Highlightr",
"repositoryURL": "https://github.com/raspu/Highlightr",
Expand Down
8 changes: 8 additions & 0 deletions CodeEdit/Settings/GeneralSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ struct GeneralSettingsView: View {
.tag(CodeEditor.ThemeName.agate)
Text("Ocean")
.tag(CodeEditor.ThemeName.ocean)
Text("Xcode")
.tag(CodeEditor.ThemeName.xcode)
Text("Github")
.tag(CodeEditor.ThemeName.github)
Text("Google Code")
.tag(CodeEditor.ThemeName.googlecode)
Text("Visual Studio")
.tag(CodeEditor.ThemeName.vs)
}
}
.padding()
Expand Down
309 changes: 309 additions & 0 deletions CodeEditModules/Modules/CodeEditor/CodeEditor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
//
// CodeEditor.swift
// CodeEditor
//
// Created by Helge Heß.
// Copyright © 2021 ZeeZide GmbH. All rights reserved.
//

import SwiftUI
import Highlightr

/**
* An simple code editor (or viewer) with highlighting for SwiftUI (iOS and
* macOS).
*
* To use the code editor as a Viewer, simply pass the source code
*
* struct ContentView: View {
*
* var body: some View {
* CodeEditor(source: "let a = 42")
* }
* }
*
* If it should act as an actual editor, pass in a `Binding`:
*
* struct ContentView: View {
*
* @State private var source = "let a = 42\n"
*
* var body: some View {
* CodeEditor(source: $source, language: .swift, theme: .ocean)
* }
* }
*
* ### Languages and Themes
*
* Highlight.js supports more than 180 languages and over 80 different themes.
*
* The available languages and themes can be accessed using:
*
* CodeEditor.availableLanguages
* CodeEditor.availableThemes
*
* They can be used in a SwiftUI `Picker` like so:
*
* @State var source = "let it = be"
* @State var language = CodeEditor.Language.swift
*
* Picker("Language", selection: $language) {
* ForEach(CodeEditor.availableLanguages) { language in
* Text("\(language.rawValue.capitalized)")
* .tag(language)
* }
* }
*
* CodeEditor(source: $source, language: language)
*
* Note: The `CodeEditor` doesn't do automatic theme changes if the appearance
* changes.
*
* ### Smart Indent and Open/Close Pairing
*
* Inspired by [NTYSmartTextView](https://github.com/naoty/NTYSmartTextView),
* `CodeEditor` now also supports (on macOS):
* - smarter indents (preserving the indent of the previous line)
* - soft indents (insert a configurable amount of spaces if the user presses tabs)
* - auto character pairing, e.g. when entering `{`, the matching `}` will be auto-added
*
* To enable smart indents, add the `smartIndent` flag, e.g.:
*
* CodeEditor(source: $source, language: language,
* flags: [ .selectable, .editable, .smartIndent ])
*
* It is enabled for editors by default.
*
* To configure soft indents, use the `indentStyle` parameter, e.g.
*
* CodeEditor(source: $source, language: language,
* indentStyle: .softTab(width: 2))
*
* It defaults to tabs, as per system settings.
*
* Auto character pairing is automatic based on the language. E.g. there is a set of
* defaults for C like languages (e.g. Swift), Python or XML. The defaults can be overridden
* using the respective static variable in `CodeEditor`,
* or the desired pairing can be set explicitly:
*
* CodeEditor(source: $source, language: language,
* autoPairs: [ "{": "}", "<": ">", "'": "'" ])
*
*
* ### Font Sizing
*
* On macOS the editor supports sizing of the font (using Cmd +/Cmd - and the
* font panel).
* To enable sizing commands, the WindowScene needs to have the proper commands
* applied, e.g.:
*
* WindowGroup {
* ContentView()
* }
* .commands {
* TextFormattingCommands()
* }
*
* To persist the binding, the `fontSize` binding is available.
*
* ### Highlightr and Shaper
*
* Based on the excellent [Highlightr](https://github.com/raspu/Highlightr).
* This means that it is using JavaScriptCore as the actual driver. As
* Highlightr says:
*
* > It will never be as fast as a native solution, but it's fast enough to be
* > used on a real time editor.
*
* The editor is similar to (but not exactly the same) the one used by
* [SVG Shaper for SwiftUI](https://zeezide.de/en/products/svgshaper/),
* for its SVG and Swift editor parts.
*/
public struct CodeEditor: View {

/// Returns the available themes in the associated Highlightr package.
public static var availableThemes =
Highlightr()?.availableThemes().map(ThemeName.init).sorted() ?? []

/// Returns the available languages in the associated Highlightr package.
public static var availableLanguages =
Highlightr()?.supportedLanguages().map(Language.init).sorted() ?? []


/**
* Flags available for `CodeEditor`, currently just:
* - `.editable`
* - `.selectable`
*/
@frozen public struct Flags: OptionSet {
public let rawValue : UInt8
@inlinable public init(rawValue: UInt8) { self.rawValue = rawValue }

/// `.editable` requires that the `source` of the `CodeEditor` is a
/// `Binding`.
public static let editable = Flags(rawValue: 1 << 0)

/// Whether the displayed content should be selectable by the user.
public static let selectable = Flags(rawValue: 1 << 1)

/// If the user starts a newline, the editor automagically adds the same
/// whitespace as on the previous line.
public static let smartIndent = Flags(rawValue: 1 << 2)

public static let defaultViewerFlags : Flags = [ .selectable ]
public static let defaultEditorFlags : Flags =
[ .selectable, .editable, .smartIndent ]
}

@frozen public enum IndentStyle: Equatable {
case system
case softTab(width: Int)
}

/**
* Default auto pairing mappings for languages.
*/
public static var defaultAutoPairs : [ Language : [ String : String ] ] = [
.c: cStyleAutoPairs, .cpp: cStyleAutoPairs, .objectivec: cStyleAutoPairs,
.swift: cStyleAutoPairs,
.java: cStyleAutoPairs, .javascript: cStyleAutoPairs,
.xml: xmlStyleAutoPairs,
.python: [ "(": ")", "[": "]", "\"": "\"", "'": "'", "`": "`" ]
]
public static var cStyleAutoPairs = [
"(": ")", "[": "]", "{": "}", "\"": "\"", "'": "'", "`": "`"
]
public static var xmlStyleAutoPairs = [ "<": ">", "\"": "\"", "'": "'" ]


/**
* Configures a CodeEditor View with the given parameters.
*
* - Parameters:
* - source: A binding to a String that holds the source code to be
* edited (or displayed).
* - language: Optionally set a language (e.g. `.swift`), otherwise
* Highlight.js will attempt to detect the language.
* - theme: The name of the theme to use, defaults to "pojoaque".
* - fontSize: On macOS this Binding can be used to persist the size of
* the font in use. At runtime this is combined with the
* theme to produce the full font information. (optional)
* - flags: Configure whether the text is editable and/or selectable
* (defaults to both).
* - indentStyle: Optionally insert a configurable amount of spaces if the
* user hits "tab".
* - autoPairs: A mapping of open/close characters, where the close
* characters are automatically injected when the user enters
* the opening character. For example: `[ "{": "}" ]` would
* automatically insert the closing "}" if the user enters
* "{". If no value is given, the default mapping for the
* language is used.
* - inset: The editor can be inset in the scroll view. Defaults to
* 8/8.
*/
public init(source : Binding<String>,
language : Language? = nil,
theme : ThemeName = .default,
fontSize : Binding<CGFloat>? = nil,
flags : Flags = .defaultEditorFlags,
indentStyle : IndentStyle = .system,
autoPairs : [ String : String ]? = nil,
inset : CGSize? = nil)
{
self.source = source
self.fontSize = fontSize
self.language = language
self.themeName = theme
self.flags = flags
self.indentStyle = indentStyle
self.inset = inset ?? CGSize(width: 8, height: 8)
self.autoPairs = autoPairs
?? language.flatMap({ CodeEditor.defaultAutoPairs[$0] })
?? [:]
}

/**
* Configures a read-only CodeEditor View with the given parameters.
*
* - Parameters:
* - source: A String that holds the source code to be displayed.
* - language: Optionally set a language (e.g. `.swift`), otherwise
* Highlight.js will attempt to detect the language.
* - theme: The name of the theme to use, defaults to "pojoaque".
* - fontSize: On macOS this Binding can be used to persist the size of
* the font in use. At runtime this is combined with the
* theme to produce the full font information. (optional)
* - flags: Configure whether the text is selectable
* (defaults to both).
* - indentStyle: Optionally insert a configurable amount of spaces if the
* user hits "tab".
* - autoPairs: A mapping of open/close characters, where the close
* characters are automatically injected when the user enters
* the opening character. For example: `[ "{": "}" ]` would
* automatically insert the closing "}" if the user enters
* "{". If no value is given, the default mapping for the
* language is used.
* - inset: The editor can be inset in the scroll view. Defaults to
* 8/8.
*/
@inlinable
public init(source : String,
language : Language? = nil,
theme : ThemeName = .default,
fontSize : Binding<CGFloat>? = nil,
flags : Flags = .defaultViewerFlags,
indentStyle : IndentStyle = .system,
autoPairs : [ String : String ]? = nil,
inset : CGSize? = nil)
{
assert(!flags.contains(.editable), "Editing requires a Binding")
self.init(source : .constant(source),
language : language,
theme : theme,
fontSize : fontSize,
flags : flags.subtracting(.editable),
indentStyle : indentStyle,
autoPairs : autoPairs,
inset : inset)
}

private var source : Binding<String>
private var fontSize : Binding<CGFloat>?
private let language : Language?
private let themeName : ThemeName
private let flags : Flags
private let indentStyle : IndentStyle
private let autoPairs : [ String : String ]
private let inset : CGSize

public var body: some View {
UXCodeTextViewRepresentable(source : source,
language : language,
theme : themeName,
fontSize : fontSize,
flags : flags,
indentStyle : indentStyle,
autoPairs : autoPairs,
inset : inset)
}
}

struct CodeEditor_Previews: PreviewProvider {

static var previews: some View {

CodeEditor(source: "let a = 5")
.frame(width: 200, height: 100)

CodeEditor(source: "let a = 5", language: .swift, theme: .pojoaque)
.frame(width: 200, height: 100)

CodeEditor(source:
#"""
The quadratic formula is $-b \pm \sqrt{b^2 - 4ac} \over 2a$
\bye
"""#, language: .tex
)
.frame(width: 540, height: 200)
}
}
Loading

0 comments on commit 8139217

Please sign in to comment.