Skip to content

Commit e2f6052

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

File tree

18 files changed

+824
-119
lines changed

18 files changed

+824
-119
lines changed

CodeEdit.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 0 additions & 9 deletions
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/Documents/WorkspaceCodeFileView.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ struct WorkspaceCodeFileView: View {
1616

1717
@ViewBuilder var body: some View {
1818
if let item = workspace.openFileItems.first(where: { file in
19+
if file.id == workspace.selectedId {
20+
print("Item loaded is: ", file.url)
21+
}
1922
return file.id == workspace.selectedId
2023
}) {
2124
if let codeFile = workspace.openedCodeFiles[item] {

CodeEdit/Documents/WorkspaceDocument.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate {
8686
openedCodeFiles[item] = codeFile
8787
}
8888
selectedId = item.id
89-
89+
Swift.print("Opening file for item: ", item.url)
9090
self.windowControllers.first?.window?.subtitle = item.url.lastPathComponent
9191
} catch let err {
9292
Swift.print(err)

CodeEdit/Quick Open/QuickOpenPreviewView.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import SwiftUI
99
import WorkspaceClient
1010
import CodeFile
11-
import CodeEditor
1211

1312
struct QuickOpenPreviewView: View {
1413
var item: WorkspaceClient.FileItem
@@ -18,8 +17,12 @@ struct QuickOpenPreviewView: View {
1817

1918
var body: some View {
2019
VStack {
21-
if loaded {
22-
ThemedCodeView($content, language: .init(url: item.url), editable: false)
20+
if let codeFile = try? CodeFileDocument(
21+
for: item.url,
22+
withContentsOf: item.url,
23+
ofType: "public.source-code"
24+
), loaded {
25+
CodeFileView(codeFile: codeFile, editable: false)
2326
} else if let error = error {
2427
Text(error)
2528
} else {

CodeEdit/Settings/GeneralSettingsView.swift

Lines changed: 8 additions & 8 deletions
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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ struct SideBarItem: View {
2929

3030
func sidebarFileItem(_ item: WorkspaceClient.FileItem) -> some View {
3131
NavigationLink {
32-
WorkspaceCodeFileView(windowController: windowController,
33-
workspace: workspace)
34-
.onAppear { workspace.openFile(item: item) }
32+
WorkspaceCodeFileView(
33+
windowController: windowController,
34+
workspace: workspace
35+
)
36+
.onAppear { workspace.openFile(item: item) }
3537
} label: {
3638
Label(item.url.lastPathComponent, systemImage: item.systemImage)
3739
.accentColor(iconStyle == .color ? item.iconColor : .secondary)

CodeEdit/WorkspaceView.swift

Lines changed: 4 additions & 2 deletions
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
}
Lines changed: 67 additions & 0 deletions
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

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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+
@State private var isCurrentlyUpdatingView: ReferenceTypeBool = .init(value: false)
16+
private var content: Binding<String>
17+
private let language: Language?
18+
private let theme: Binding<CodeFileView.Theme>
19+
private let highlightr = Highlightr()
20+
21+
init(
22+
content: Binding<String>,
23+
language: Language?,
24+
theme: Binding<CodeFileView.Theme>
25+
) {
26+
self.content = content
27+
self.language = language
28+
self.theme = theme
29+
highlightr?.setTheme(to: theme.wrappedValue.rawValue)
30+
}
31+
32+
func makeNSView(context: Context) -> NSScrollView {
33+
let scrollView = NSScrollView()
34+
let textView = CodeEditorTextView(
35+
textContainer: buildTextStorage(
36+
language: language,
37+
scrollView: scrollView
38+
)
39+
)
40+
textView.autoresizingMask = .width
41+
textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
42+
textView.minSize = NSSize(width: 0, height: scrollView.contentSize.height)
43+
textView.delegate = context.coordinator
44+
45+
scrollView.drawsBackground = true
46+
scrollView.borderType = .noBorder
47+
scrollView.hasVerticalScroller = true
48+
scrollView.hasHorizontalRuler = false
49+
scrollView.autoresizingMask = [.width, .height]
50+
51+
scrollView.documentView = textView
52+
scrollView.verticalRulerView = LineGutter(
53+
scrollView: scrollView,
54+
width: 60,
55+
font: .systemFont(ofSize: 10),
56+
textColor: .labelColor,
57+
backgroundColor: .windowBackgroundColor
58+
)
59+
scrollView.rulersVisible = true
60+
61+
updateTextView(textView)
62+
return scrollView
63+
}
64+
65+
func updateNSView(_ scrollView: NSScrollView, context: Context) {
66+
if let textView = scrollView.documentView as? CodeEditorTextView {
67+
updateTextView(textView)
68+
}
69+
}
70+
71+
final class Coordinator: NSObject, NSTextViewDelegate {
72+
private var content: Binding<String>
73+
init(content: Binding<String>) {
74+
self.content = content
75+
}
76+
77+
func textDidChange(_ notification: Notification) {
78+
guard let textView = notification.object as? NSTextView else {
79+
return
80+
}
81+
content.wrappedValue = textView.string
82+
}
83+
84+
}
85+
86+
func makeCoordinator() -> Coordinator {
87+
Coordinator(content: content)
88+
}
89+
90+
private func updateTextView(_ textView: NSTextView) {
91+
guard !isCurrentlyUpdatingView.value else {
92+
return
93+
}
94+
95+
isCurrentlyUpdatingView.value = true
96+
97+
defer {
98+
isCurrentlyUpdatingView.value = false
99+
}
100+
101+
highlightr?.setTheme(to: theme.wrappedValue.rawValue)
102+
103+
if content.wrappedValue != textView.string {
104+
if let textStorage = textView.textStorage as? CodeAttributedString {
105+
textStorage.language = language?.rawValue
106+
textStorage.replaceCharacters(
107+
in: NSRange(location: 0, length: textStorage.length),
108+
with: content.wrappedValue
109+
)
110+
} else {
111+
textView.string = content.wrappedValue
112+
}
113+
}
114+
}
115+
116+
private func buildTextStorage(language: Language?, scrollView: NSScrollView) -> NSTextContainer {
117+
// highlightr wrapper that enables real-time highlighting
118+
let textStorage: CodeAttributedString
119+
if let highlightr = highlightr {
120+
textStorage = CodeAttributedString(highlightr: highlightr)
121+
} else {
122+
textStorage = CodeAttributedString()
123+
}
124+
textStorage.language = language?.rawValue
125+
let layoutManager = NSLayoutManager()
126+
textStorage.addLayoutManager(layoutManager)
127+
let textContainer = NSTextContainer(containerSize: scrollView.frame.size)
128+
textContainer.widthTracksTextView = true
129+
textContainer.containerSize = NSSize(
130+
width: scrollView.contentSize.width,
131+
height: .greatestFiniteMagnitude
132+
)
133+
layoutManager.addTextContainer(textContainer)
134+
return textContainer
135+
}
136+
}
137+
138+
extension CodeEditor {
139+
// A wrapper around a `Bool` that enables updating
140+
// the wrapped value during `View` renders.
141+
private class ReferenceTypeBool {
142+
var value: Bool
143+
144+
init(value: Bool) {
145+
self.value = value
146+
}
147+
}
148+
}

0 commit comments

Comments
 (0)