Skip to content

Commit

Permalink
feat: new code editor
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcoCarnevali committed Mar 22, 2022
1 parent 70f5d71 commit e2f6052
Show file tree
Hide file tree
Showing 18 changed files with 824 additions and 119 deletions.
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
3 changes: 3 additions & 0 deletions CodeEdit/Documents/WorkspaceCodeFileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ struct WorkspaceCodeFileView: View {

@ViewBuilder var body: some View {
if let item = workspace.openFileItems.first(where: { file in
if file.id == workspace.selectedId {
print("Item loaded is: ", file.url)
}
return file.id == workspace.selectedId
}) {
if let codeFile = workspace.openedCodeFiles[item] {
Expand Down
2 changes: 1 addition & 1 deletion CodeEdit/Documents/WorkspaceDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate {
openedCodeFiles[item] = codeFile
}
selectedId = item.id

Swift.print("Opening file for item: ", item.url)
self.windowControllers.first?.window?.subtitle = item.url.lastPathComponent
} catch let err {
Swift.print(err)
Expand Down
9 changes: 6 additions & 3 deletions CodeEdit/Quick Open/QuickOpenPreviewView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import SwiftUI
import WorkspaceClient
import CodeFile
import CodeEditor

struct QuickOpenPreviewView: View {
var item: WorkspaceClient.FileItem
Expand All @@ -18,8 +17,12 @@ struct QuickOpenPreviewView: View {

var body: some View {
VStack {
if loaded {
ThemedCodeView($content, language: .init(url: item.url), editable: false)
if let codeFile = try? CodeFileDocument(
for: item.url,
withContentsOf: item.url,
ofType: "public.source-code"
), loaded {
CodeFileView(codeFile: codeFile, editable: false)
} else if let error = error {
Text(error)
} else {
Expand Down
16 changes: 8 additions & 8 deletions CodeEdit/Settings/GeneralSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@

import SwiftUI
import CodeFile
import CodeEditor

// MARK: - View

struct GeneralSettingsView: View {
@AppStorage(Appearances.storageKey) var appearance: Appearances = .default
@AppStorage(ReopenBehavior.storageKey) var reopenBehavior: ReopenBehavior = .default
@AppStorage(FileIconStyle.storageKey) var fileIconStyle: FileIconStyle = .default
@AppStorage(CodeEditorTheme.storageKey) var editorTheme: CodeEditor.ThemeName = .atelierSavannaAuto
@AppStorage(FileIconStyle.storageKey) var fileIconStyle: FileIconStyle = .default
@AppStorage(CodeFileView.Theme.storageKey) var editorTheme: CodeFileView.Theme = .atelierSavannaAuto

var body: some View {
Form {
Picker("Appearance".localized(), selection: $appearance) {
Expand Down Expand Up @@ -50,18 +50,18 @@ struct GeneralSettingsView: View {

Picker("Editor Theme".localized(), selection: $editorTheme) {
Text("Atelier Savanna (Auto)")
.tag(CodeEditor.ThemeName.atelierSavannaAuto)
.tag(CodeFileView.Theme.atelierSavannaAuto)
Text("Atelier Savanna Dark")
.tag(CodeEditor.ThemeName.atelierSavannaDark)
.tag(CodeFileView.Theme.atelierSavannaDark)
Text("Atelier Savanna Light")
.tag(CodeEditor.ThemeName.atelierSavannaLight)
.tag(CodeFileView.Theme.atelierSavannaLight)
// TODO: Pojoaque does not seem to work (does not change from previous selection)
// Text("Pojoaque")
// .tag(CodeEditor.ThemeName.pojoaque)
Text("Agate")
.tag(CodeEditor.ThemeName.agate)
.tag(CodeFileView.Theme.agate)
Text("Ocean")
.tag(CodeEditor.ThemeName.ocean)
.tag(CodeFileView.Theme.ocean)
}
}
.padding()
Expand Down
8 changes: 5 additions & 3 deletions CodeEdit/SideBar/SideBarItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ struct SideBarItem: View {

func sidebarFileItem(_ item: WorkspaceClient.FileItem) -> some View {
NavigationLink {
WorkspaceCodeFileView(windowController: windowController,
workspace: workspace)
.onAppear { workspace.openFile(item: item) }
WorkspaceCodeFileView(
windowController: windowController,
workspace: workspace
)
.onAppear { workspace.openFile(item: item) }
} label: {
Label(item.url.lastPathComponent, systemImage: item.systemImage)
.accentColor(iconStyle == .color ? item.iconColor : .secondary)
Expand Down
6 changes: 4 additions & 2 deletions CodeEdit/WorkspaceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ struct WorkspaceView: View {
SideBar(workspace: workspace, windowController: windowController)
.frame(minWidth: 250)

WorkspaceCodeFileView(windowController: windowController,
workspace: workspace)
WorkspaceCodeFileView(
windowController: windowController,
workspace: workspace
)
} else {
EmptyView()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1330"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CodeFile"
BuildableName = "CodeFile"
BlueprintName = "CodeFile"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CodeFile"
BuildableName = "CodeFile"
BlueprintName = "CodeFile"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
17 changes: 0 additions & 17 deletions CodeEditModules/Modules/CodeFile/src/CodeEditor+AppStorage.swift

This file was deleted.

148 changes: 148 additions & 0 deletions CodeEditModules/Modules/CodeFile/src/CodeEditor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
//
// CodeEditor.swift
// CodeEdit
//
// Created by Marco Carnevali on 19/03/22.
//

import Foundation
import AppKit
import SwiftUI
import Highlightr
import Combine

struct CodeEditor: NSViewRepresentable {
@State private var isCurrentlyUpdatingView: ReferenceTypeBool = .init(value: false)
private var content: Binding<String>
private let language: Language?
private let theme: Binding<CodeFileView.Theme>
private let highlightr = Highlightr()

init(
content: Binding<String>,
language: Language?,
theme: Binding<CodeFileView.Theme>
) {
self.content = content
self.language = language
self.theme = theme
highlightr?.setTheme(to: theme.wrappedValue.rawValue)
}

func makeNSView(context: Context) -> NSScrollView {
let scrollView = NSScrollView()
let textView = CodeEditorTextView(
textContainer: buildTextStorage(
language: language,
scrollView: scrollView
)
)
textView.autoresizingMask = .width
textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
textView.minSize = NSSize(width: 0, height: scrollView.contentSize.height)
textView.delegate = context.coordinator

scrollView.drawsBackground = true
scrollView.borderType = .noBorder
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalRuler = false
scrollView.autoresizingMask = [.width, .height]

scrollView.documentView = textView
scrollView.verticalRulerView = LineGutter(
scrollView: scrollView,
width: 60,
font: .systemFont(ofSize: 10),
textColor: .labelColor,
backgroundColor: .windowBackgroundColor
)
scrollView.rulersVisible = true

updateTextView(textView)
return scrollView
}

func updateNSView(_ scrollView: NSScrollView, context: Context) {
if let textView = scrollView.documentView as? CodeEditorTextView {
updateTextView(textView)
}
}

final class Coordinator: NSObject, NSTextViewDelegate {
private var content: Binding<String>
init(content: Binding<String>) {
self.content = content
}

func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else {
return
}
content.wrappedValue = textView.string
}

}

func makeCoordinator() -> Coordinator {
Coordinator(content: content)
}

private func updateTextView(_ textView: NSTextView) {
guard !isCurrentlyUpdatingView.value else {
return
}

isCurrentlyUpdatingView.value = true

defer {
isCurrentlyUpdatingView.value = false
}

highlightr?.setTheme(to: theme.wrappedValue.rawValue)

if content.wrappedValue != textView.string {
if let textStorage = textView.textStorage as? CodeAttributedString {
textStorage.language = language?.rawValue
textStorage.replaceCharacters(
in: NSRange(location: 0, length: textStorage.length),
with: content.wrappedValue
)
} else {
textView.string = content.wrappedValue
}
}
}

private func buildTextStorage(language: Language?, scrollView: NSScrollView) -> NSTextContainer {
// highlightr wrapper that enables real-time highlighting
let textStorage: CodeAttributedString
if let highlightr = highlightr {
textStorage = CodeAttributedString(highlightr: highlightr)
} else {
textStorage = CodeAttributedString()
}
textStorage.language = language?.rawValue
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(containerSize: scrollView.frame.size)
textContainer.widthTracksTextView = true
textContainer.containerSize = NSSize(
width: scrollView.contentSize.width,
height: .greatestFiniteMagnitude
)
layoutManager.addTextContainer(textContainer)
return textContainer
}
}

extension CodeEditor {
// A wrapper around a `Bool` that enables updating
// the wrapped value during `View` renders.
private class ReferenceTypeBool {
var value: Bool

init(value: Bool) {
self.value = value
}
}
}
Loading

0 comments on commit e2f6052

Please sign in to comment.