Skip to content

Commit 52adc05

Browse files
feat: new code editor
1 parent 70f5d71 commit 52adc05

File tree

15 files changed

+577
-118
lines changed

15 files changed

+577
-118
lines changed

CodeEdit.xcworkspace/xcshareddata/swiftpm/Package.resolved

-9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
11
{
22
"object": {
33
"pins": [
4-
{
5-
"package": "CodeEditor",
6-
"repositoryURL": "https://github.com/ZeeZide/CodeEditor.git",
7-
"state": {
8-
"branch": null,
9-
"revision": "5856fac22b0a2174dbdea212784567c8c9cd1129",
10-
"version": "1.2.0"
11-
}
12-
},
134
{
145
"package": "Highlightr",
156
"repositoryURL": "https://github.com/raspu/Highlightr",

CodeEdit/Quick Open/QuickOpenPreviewView.swift

+7-3
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ struct QuickOpenPreviewView: View {
1515
@State var content: String = ""
1616
@State var loaded = false
1717
@State var error: String?
18-
18+
1919
var body: some View {
2020
VStack {
21-
if loaded {
22-
ThemedCodeView($content, language: .init(url: item.url), editable: false)
21+
if let codeFile = try? CodeFileDocument(
22+
for: item.url,
23+
withContentsOf: item.url,
24+
ofType: "public.source-code"
25+
), loaded {
26+
CodeFileView(codeFile: codeFile, editable: false)
2327
} else if let error = error {
2428
Text(error)
2529
} else {

CodeEdit/Settings/GeneralSettingsView.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77

88
import SwiftUI
99
import CodeFile
10-
import CodeEditor
1110

1211
// MARK: - View
1312

1413
struct GeneralSettingsView: View {
1514
@AppStorage(Appearances.storageKey) var appearance: Appearances = .default
1615
@AppStorage(ReopenBehavior.storageKey) var reopenBehavior: ReopenBehavior = .default
17-
@AppStorage(FileIconStyle.storageKey) var fileIconStyle: FileIconStyle = .default
18-
@AppStorage(CodeEditorTheme.storageKey) var editorTheme: CodeEditor.ThemeName = .atelierSavannaAuto
16+
@AppStorage(FileIconStyle.storageKey) var fileIconStyle: FileIconStyle = .default
17+
@AppStorage(CodeFileView.Theme.storageKey) var editorTheme: CodeFileView.Theme = .atelierSavannaAuto
18+
1919
var body: some View {
2020
Form {
2121
Picker("Appearance".localized(), selection: $appearance) {
@@ -50,18 +50,18 @@ struct GeneralSettingsView: View {
5050

5151
Picker("Editor Theme".localized(), selection: $editorTheme) {
5252
Text("Atelier Savanna (Auto)")
53-
.tag(CodeEditor.ThemeName.atelierSavannaAuto)
53+
.tag(CodeFileView.Theme.atelierSavannaAuto)
5454
Text("Atelier Savanna Dark")
55-
.tag(CodeEditor.ThemeName.atelierSavannaDark)
55+
.tag(CodeFileView.Theme.atelierSavannaDark)
5656
Text("Atelier Savanna Light")
57-
.tag(CodeEditor.ThemeName.atelierSavannaLight)
57+
.tag(CodeFileView.Theme.atelierSavannaLight)
5858
// TODO: Pojoaque does not seem to work (does not change from previous selection)
5959
// Text("Pojoaque")
6060
// .tag(CodeEditor.ThemeName.pojoaque)
6161
Text("Agate")
62-
.tag(CodeEditor.ThemeName.agate)
62+
.tag(CodeFileView.Theme.agate)
6363
Text("Ocean")
64-
.tag(CodeEditor.ThemeName.ocean)
64+
.tag(CodeFileView.Theme.ocean)
6565
}
6666
}
6767
.padding()

CodeEdit/SideBar/SideBarItem.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ struct SideBarItem: View {
2929

3030
func sidebarFileItem(_ item: WorkspaceClient.FileItem) -> some View {
3131
NavigationLink {
32-
WorkspaceCodeFileView(windowController: windowController,
33-
workspace: workspace)
32+
WorkspaceCodeFileView(
33+
windowController: windowController,
34+
workspace: workspace
35+
)
3436
.onAppear { workspace.openFile(item: item) }
3537
} label: {
3638
Label(item.url.lastPathComponent, systemImage: item.systemImage)

CodeEdit/WorkspaceView.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ struct WorkspaceView: View {
3030
SideBar(workspace: workspace, windowController: windowController)
3131
.frame(minWidth: 250)
3232

33-
WorkspaceCodeFileView(windowController: windowController,
34-
workspace: workspace)
33+
WorkspaceCodeFileView(
34+
windowController: windowController,
35+
workspace: workspace
36+
)
3537
} else {
3638
EmptyView()
3739
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1330"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "CodeFile"
18+
BuildableName = "CodeFile"
19+
BlueprintName = "CodeFile"
20+
ReferencedContainer = "container:">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
</BuildActionEntries>
24+
</BuildAction>
25+
<TestAction
26+
buildConfiguration = "Debug"
27+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
shouldUseLaunchSchemeArgsEnv = "YES">
30+
<Testables>
31+
</Testables>
32+
</TestAction>
33+
<LaunchAction
34+
buildConfiguration = "Debug"
35+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
36+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
37+
launchStyle = "0"
38+
useCustomWorkingDirectory = "NO"
39+
ignoresPersistentStateOnLaunch = "NO"
40+
debugDocumentVersioning = "YES"
41+
debugServiceExtension = "internal"
42+
allowLocationSimulation = "YES">
43+
</LaunchAction>
44+
<ProfileAction
45+
buildConfiguration = "Release"
46+
shouldUseLaunchSchemeArgsEnv = "YES"
47+
savedToolIdentifier = ""
48+
useCustomWorkingDirectory = "NO"
49+
debugDocumentVersioning = "YES">
50+
<MacroExpansion>
51+
<BuildableReference
52+
BuildableIdentifier = "primary"
53+
BlueprintIdentifier = "CodeFile"
54+
BuildableName = "CodeFile"
55+
BlueprintName = "CodeFile"
56+
ReferencedContainer = "container:">
57+
</BuildableReference>
58+
</MacroExpansion>
59+
</ProfileAction>
60+
<AnalyzeAction
61+
buildConfiguration = "Debug">
62+
</AnalyzeAction>
63+
<ArchiveAction
64+
buildConfiguration = "Release"
65+
revealArchiveInOrganizer = "YES">
66+
</ArchiveAction>
67+
</Scheme>

CodeEditModules/Modules/CodeFile/src/CodeEditor+AppStorage.swift

-17
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
//
2+
// CodeEditor.swift
3+
// CodeEdit
4+
//
5+
// Created by Marco Carnevali on 19/03/22.
6+
//
7+
8+
import Foundation
9+
import AppKit
10+
import SwiftUI
11+
import Highlightr
12+
import Combine
13+
14+
struct CodeEditor: NSViewRepresentable {
15+
@ObservedObject private var codeFile: CodeFileDocument
16+
private var lineGutter: LineGutter?
17+
18+
private let highlightr = Highlightr()
19+
private var view = NSView()
20+
private let theme: CodeFileView.Theme
21+
22+
private var textView: CodeEditorTextView? {
23+
let scrollView = view.subviews.first as? NSScrollView
24+
return scrollView?.documentView as? CodeEditorTextView
25+
}
26+
27+
private lazy var scrollView: NSScrollView = {
28+
let scrollView = NSScrollView()
29+
scrollView.drawsBackground = true
30+
scrollView.borderType = .noBorder
31+
scrollView.hasVerticalScroller = true
32+
scrollView.hasHorizontalRuler = false
33+
scrollView.autoresizingMask = [.width, .height]
34+
scrollView.translatesAutoresizingMaskIntoConstraints = false
35+
return scrollView
36+
}()
37+
38+
init(
39+
codeFile: CodeFileDocument,
40+
theme: CodeFileView.Theme
41+
) {
42+
self.codeFile = codeFile
43+
self.theme = theme
44+
45+
let contentSize = scrollView.contentSize
46+
let textStorage = NSTextStorage()
47+
let layoutManager = NSLayoutManager()
48+
textStorage.addLayoutManager(layoutManager)
49+
let textContainer = NSTextContainer(containerSize: scrollView.frame.size)
50+
textContainer.widthTracksTextView = true
51+
textContainer.containerSize = NSSize(
52+
width: contentSize.width,
53+
height: .greatestFiniteMagnitude
54+
)
55+
layoutManager.addTextContainer(textContainer)
56+
let textView = CodeEditorTextView(frame: .zero, textContainer: textContainer)
57+
textView.autoresizingMask = .width
58+
textView.drawsBackground = true
59+
textView.isEditable = true
60+
textView.isHorizontallyResizable = false
61+
textView.isVerticallyResizable = true
62+
textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
63+
textView.minSize = NSSize(width: 0, height: contentSize.height)
64+
textView.allowsUndo = true
65+
66+
scrollView.documentView = textView
67+
scrollView.translatesAutoresizingMaskIntoConstraints = false
68+
view.addSubview(scrollView)
69+
NSLayoutConstraint.activate([
70+
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
71+
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
72+
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
73+
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
74+
])
75+
let lineGutter = LineGutter(
76+
scrollView: scrollView,
77+
width: 20,
78+
font: .systemFont(ofSize: 10),
79+
textColor: .labelColor,
80+
backgroundColor: .windowBackgroundColor
81+
)
82+
scrollView.verticalRulerView = lineGutter
83+
self.lineGutter = lineGutter
84+
scrollView.rulersVisible = true
85+
highlightr?.setTheme(to: theme.rawValue)
86+
}
87+
88+
var themeBackgroundColor: NSColor? {
89+
highlightr?.theme.themeBackgroundColor as? NSColor
90+
}
91+
92+
func makeNSView(context: Context) -> some NSView {
93+
guard let highlightr = highlightr,
94+
let highlightedCode = highlightr.highlight(codeFile.content)
95+
else {
96+
return view
97+
}
98+
textView?.textStorage?.setAttributedString(highlightedCode)
99+
return view
100+
}
101+
102+
func updateNSView(_ nsView: NSViewType, context: Context) {
103+
}
104+
105+
class Coordinator {
106+
private let highlightr: Highlightr?
107+
private var cancellables = Set<AnyCancellable>()
108+
109+
init(highlightr: Highlightr?) {
110+
self.highlightr = highlightr
111+
observeTextChange()
112+
}
113+
114+
deinit {
115+
cancellables.forEach { $0.cancel() }
116+
}
117+
118+
private func observeTextChange() {
119+
NotificationCenter.default
120+
.publisher(for: NSText.didChangeNotification)
121+
// we debounce so that performances while typing are increased
122+
.debounce(for: .seconds(1.5), scheduler: RunLoop.main)
123+
.compactMap { notification in notification.object as? NSTextView }
124+
.sink { [weak self] textView in
125+
if let code = self?.highlightr?.highlight(textView.string) {
126+
let cursor = textView.selectedRange()
127+
textView.textStorage?.setAttributedString(code)
128+
textView.setSelectedRange(cursor)
129+
}
130+
}
131+
.store(in: &cancellables)
132+
}
133+
}
134+
135+
func makeCoordinator() -> Coordinator {
136+
Coordinator(highlightr: highlightr)
137+
}
138+
}

CodeEditModules/Modules/CodeFile/src/CodeFile.swift

-21
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
//
77

88
import AppKit
9-
import CodeEditor
109
import Foundation
1110
import SwiftUI
1211

@@ -25,14 +24,6 @@ public final class CodeFileDocument: NSDocument, ObservableObject {
2524
return true
2625
}
2726

28-
public func fileLanguage() -> CodeEditor.Language {
29-
if let fileURL = fileURL {
30-
return .init(url: fileURL)
31-
} else {
32-
return .markdown
33-
}
34-
}
35-
3627
override public func makeWindowControllers() {
3728
// Returns the Storyboard that contains your Document window.
3829
let contentView = CodeFileView(codeFile: self)
@@ -57,15 +48,3 @@ public final class CodeFileDocument: NSDocument, ObservableObject {
5748
self.content = content
5849
}
5950
}
60-
61-
public extension CodeEditor.Language {
62-
init(url: URL) {
63-
var value = url.pathExtension
64-
switch value {
65-
case "js": value = "javascript"
66-
case "sh": value = "shell"
67-
default: break
68-
}
69-
self.init(rawValue: value)
70-
}
71-
}

0 commit comments

Comments
 (0)