Skip to content

Commit

Permalink
Fix Editing Bugs, Pass Tests
Browse files Browse the repository at this point in the history
  • Loading branch information
thecoolwinter committed Oct 15, 2023
1 parent 181e7ff commit 5a11ada
Show file tree
Hide file tree
Showing 18 changed files with 493 additions and 456 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,15 @@ extension TextLayoutManager: NSTextStorageDelegate {
/// - insertedString: The string being inserted.
/// - location: The location the string is being inserted into.
private func applyLineInsert(_ insertedString: NSString, at location: Int) {
if LineEnding(line: insertedString as String) != nil {
if lineStorage.count == 0 && lineStorage.length == 0 {
// The text was completely empty before, insert.
lineStorage.insert(
line: TextLine(),
atIndex: location,
length: insertedString.length,
height: estimateLineHeight()
)
} else if LineEnding(line: insertedString as String) != nil {
// Need to split the line inserting into and create a new line with the split section of the line
guard let linePosition = lineStorage.getLine(atIndex: location) else { return }
let splitLocation = location + insertedString.length
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extension TextLayoutManager {
/// - Parameter index: The line to find.
/// - Returns: The text line position if any, `nil` if the index is out of bounds.
public func textLineForIndex(_ index: Int) -> TextLineStorage<TextLine>.TextLinePosition? {
guard index > 0 && index < lineStorage.count else { return nil }
guard index >= 0 && index < lineStorage.count else { return nil }
return lineStorage.getLine(atIndex: index)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,9 +486,11 @@ private extension TextLineStorage {
deltaHeight: CGFloat,
nodeAction: MetaFixupAction = .none
) {
guard node.parent != nil else { return }
guard node.parent != nil, root != nil else { return }
let rootRef = Unmanaged<Node<Data>>.passUnretained(root!)
var ref = Unmanaged<Node<Data>>.passUnretained(node)
while let node = ref._withUnsafeGuaranteedRef({ $0.parent }), ref.takeUnretainedValue() !== root {
while let node = ref._withUnsafeGuaranteedRef({ $0.parent }),
ref.takeUnretainedValue() !== rootRef.takeUnretainedValue() {
if node.left === ref.takeUnretainedValue() {
node.leftSubtreeOffset += delta
node.leftSubtreeHeight += deltaHeight
Expand Down
14 changes: 3 additions & 11 deletions Sources/CodeEditInputView/TextView/TextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class TextView: NSView, NSTextContent {
textStorage.string
}
set {
layoutManager.willReplaceCharactersInRange(range: documentRange, with: newValue)
textStorage.setAttributedString(NSAttributedString(string: newValue, attributes: typingAttributes))
}
}
Expand Down Expand Up @@ -95,12 +96,6 @@ public class TextView: NSView, NSTextContent {
layoutManager?.wrapLines = newValue
}
}
public var editorOverscroll: CGFloat {
didSet {
setNeedsDisplay()
updateFrameIfNeeded()
}
}

/// A multiplier that determines the amount of space between characters. `1.0` indicates no space,
/// `2.0` indicates one character of space between other characters.
Expand Down Expand Up @@ -188,7 +183,6 @@ public class TextView: NSView, NSTextContent {
textColor: NSColor,
lineHeight: CGFloat,
wrapLines: Bool,
editorOverscroll: CGFloat,
isEditable: Bool,
letterSpacing: Double,
delegate: TextViewDelegate,
Expand All @@ -197,8 +191,6 @@ public class TextView: NSView, NSTextContent {
self.delegate = delegate
self.textStorage = NSTextStorage(string: string)
self.storageDelegate = storageDelegate

self.editorOverscroll = editorOverscroll
self.isEditable = isEditable
self.letterSpacing = letterSpacing
self.allowsUndo = true
Expand Down Expand Up @@ -399,8 +391,8 @@ public class TextView: NSView, NSTextContent {

var didUpdate = false

if newHeight + editorOverscroll >= availableSize.height && frame.size.height != newHeight + editorOverscroll {
frame.size.height = newHeight + editorOverscroll
if newHeight >= availableSize.height && frame.size.height != newHeight {
frame.size.height = newHeight
// No need to update layout after height adjustment
}

Expand Down
12 changes: 6 additions & 6 deletions Sources/CodeEditInputView/TextView/TextViewDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
import Foundation

public protocol TextViewDelegate: AnyObject {
func textView(_ textView: TextView, willReplaceContentsIn range: NSRange, with: String)
func textView(_ textView: TextView, didReplaceContentsIn range: NSRange, with: String)
func textView(_ textView: TextView, shouldReplaceContentsIn range: NSRange, with: String) -> Bool
func textView(_ textView: TextView, willReplaceContentsIn range: NSRange, with string: String)
func textView(_ textView: TextView, didReplaceContentsIn range: NSRange, with string: String)
func textView(_ textView: TextView, shouldReplaceContentsIn range: NSRange, with string: String) -> Bool
}

public extension TextViewDelegate {
func textView(_ textView: TextView, willReplaceContentsIn range: NSRange, with: String) { }
func textView(_ textView: TextView, didReplaceContentsIn range: NSRange, with: String) { }
func textView(_ textView: TextView, shouldReplaceContentsIn range: NSRange, with: String) -> Bool { true }
func textView(_ textView: TextView, willReplaceContentsIn range: NSRange, with string: String) { }
func textView(_ textView: TextView, didReplaceContentsIn range: NSRange, with string: String) { }
func textView(_ textView: TextView, shouldReplaceContentsIn range: NSRange, with string: String) -> Bool { true }
}
2 changes: 1 addition & 1 deletion Sources/CodeEditTextView/CodeEditTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public struct CodeEditTextView: NSViewControllerRepresentable {
private var letterSpacing: Double
private var bracketPairHighlight: BracketPairHighlight?

public typealias NSViewControllerType = TextViewController // STTextViewController
public typealias NSViewControllerType = TextViewController

public func makeNSViewController(context: Context) -> TextViewController {
return TextViewController(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ extension TextViewController {
/// - Parameter position: The position to set. Lines and columns are 1-indexed.
func setCursorPosition(_ position: (Int, Int)) {
let (line, column) = position
guard line >= 0 && column >= 0 else { return }
guard line > 0 && column > 0 else { return }

_ = textView.becomeFirstResponder()

if textView.textStorage.length == 0 {
// If the file is blank, automatically place the cursor in the first index.
let range = NSRange(location: 0, length: 0)
_ = self.textView.becomeFirstResponder()
self.textView.selectionManager.setSelectedRange(range)
} else if line - 1 >= 0, let linePosition = textView.layoutManager.textLineForIndex(line - 1) {
} else if let linePosition = textView.layoutManager.textLineForIndex(line - 1) {
// If this is a valid line, set the new position
let index = max(
linePosition.range.lowerBound,
Expand Down
118 changes: 118 additions & 0 deletions Sources/CodeEditTextView/Controller/TextViewController+LoadView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//
// TextViewController+LoadView.swift
//
//
// Created by Khan Winter on 10/14/23.
//

import CodeEditInputView
import AppKit

extension TextViewController {
// swiftlint:disable:next function_body_length
override public func loadView() {
scrollView = NSScrollView()
textView = TextView(
string: string.wrappedValue,
font: font,
textColor: theme.text,
lineHeight: lineHeightMultiple,
wrapLines: wrapLines,
isEditable: isEditable,
letterSpacing: letterSpacing,
delegate: self,
storageDelegate: storageDelegate
)
textView.postsFrameChangedNotifications = true
textView.translatesAutoresizingMaskIntoConstraints = false
textView.selectionManager.insertionPointColor = theme.insertionPoint

scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.contentView.postsFrameChangedNotifications = true
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalScroller = true
scrollView.documentView = textView
scrollView.contentView.postsBoundsChangedNotifications = true
if let contentInsets {
scrollView.automaticallyAdjustsContentInsets = false
scrollView.contentInsets = contentInsets
}

gutterView = GutterView(
font: font.rulerFont,
textColor: .secondaryLabelColor,
textView: textView,
delegate: self
)
gutterView.frame.origin.y = -scrollView.contentInsets.top
gutterView.updateWidthIfNeeded()
scrollView.addFloatingSubview(
gutterView,
for: .horizontal
)

self.view = scrollView
setUpHighlighter()

NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])

// Layout on scroll change
NotificationCenter.default.addObserver(
forName: NSView.boundsDidChangeNotification,
object: scrollView.contentView,
queue: .main
) { [weak self] _ in
self?.textView.updatedViewport(self?.scrollView.documentVisibleRect ?? .zero)
self?.gutterView.needsDisplay = true
}

// Layout on frame change
NotificationCenter.default.addObserver(
forName: NSView.frameDidChangeNotification,
object: scrollView.contentView,
queue: .main
) { [weak self] _ in
self?.textView.updatedViewport(self?.scrollView.documentVisibleRect ?? .zero)
self?.gutterView.needsDisplay = true
if self?.bracketPairHighlight == .flash {
self?.removeHighlightLayers()
}
}

NotificationCenter.default.addObserver(
forName: NSView.frameDidChangeNotification,
object: textView,
queue: .main
) { [weak self] _ in
self?.gutterView.frame.size.height = (self?.textView.frame.height ?? 0) + 10
self?.gutterView.needsDisplay = true
}

NotificationCenter.default.addObserver(
forName: TextSelectionManager.selectionChangedNotification,
object: textView.selectionManager,
queue: .main
) { [weak self] _ in
self?.updateCursorPosition()
self?.highlightSelectionPairs()
}

textView.updateFrameIfNeeded()

NSApp.publisher(for: \.effectiveAppearance)
.receive(on: RunLoop.main)
.sink { [weak self] newValue in
guard let self = self else { return }

if self.systemAppearance != newValue.name {
self.systemAppearance = newValue.name
}
}
.store(in: &cancellables)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// TextViewController+TextViewDelegate.swift
//
//
// Created by Khan Winter on 10/14/23.
//

import Foundation
import CodeEditInputView
import TextStory

extension TextViewController: TextViewDelegate {
public func textView(_ textView: TextView, didReplaceContentsIn range: NSRange, with: String) {
gutterView.needsDisplay = true
}

public func textView(_ textView: TextView, shouldReplaceContentsIn range: NSRange, with string: String) -> Bool {
let mutation = TextMutation(
string: string,
range: range,
limit: textView.textStorage.length
)

return shouldApplyMutation(mutation, to: textView)
}
}
Loading

0 comments on commit 5a11ada

Please sign in to comment.