Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce Text Artifacts, Fix Layout Bug, Public Undo Manager #23

Merged
merged 2 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,20 @@ let package = Package(
name: "CodeEditTextView",
dependencies: [
"TextStory",
.product(name: "Collections", package: "swift-collections")
.product(name: "Collections", package: "swift-collections"),
"CodeEditTextViewObjC"
],
plugins: [
.plugin(name: "SwiftLint", package: "SwiftLintPlugin")
]
),

// ObjC addons
.target(
name: "CodeEditTextViewObjC",
publicHeadersPath: "include"
),

// Tests for the text view
.testTarget(
name: "CodeEditTextViewTests",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,12 @@ public class TextLayoutManager: NSObject {

/// Lays out all visible lines
func layoutLines() { // swiftlint:disable:this function_body_length
guard let visibleRect = delegate?.visibleRect, !isInTransaction, let textStorage else { return }
guard layoutView?.superview != nil,
let visibleRect = delegate?.visibleRect,
!isInTransaction,
let textStorage else {
return
}
CATransaction.begin()
let minY = max(visibleRect.minY, 0)
let maxY = max(visibleRect.maxY, 0)
Expand Down
17 changes: 14 additions & 3 deletions Sources/CodeEditTextView/TextLine/LineFragmentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import AppKit
import CodeEditTextViewObjC

/// Displays a line fragment.
final class LineFragmentView: NSView {
Expand All @@ -23,7 +24,6 @@ final class LineFragmentView: NSView {
override func prepareForReuse() {
super.prepareForReuse()
lineFragment = nil

}

/// Set a new line fragment for this view, updating view size.
Expand All @@ -39,13 +39,24 @@ final class LineFragmentView: NSView {
return
}
context.saveGState()
context.setAllowsFontSmoothing(true)
context.setShouldSmoothFonts(true)

context.setAllowsAntialiasing(true)
context.setShouldAntialias(true)
context.setAllowsFontSmoothing(false)
context.setShouldSmoothFonts(false)
context.setAllowsFontSubpixelPositioning(true)
context.setShouldSubpixelPositionFonts(true)
context.setAllowsFontSubpixelQuantization(true)
context.setShouldSubpixelQuantizeFonts(true)

ContextSetHiddenSmoothingStyle(context, 16)

context.textMatrix = .init(scaleX: 1, y: -1)
context.textPosition = CGPoint(
x: 0,
y: lineFragment.height - lineFragment.descent + (lineFragment.heightDifference/2)
).pixelAligned

CTLineDraw(lineFragment.ctLine, context)
context.restoreGState()
}
Expand Down
18 changes: 15 additions & 3 deletions Sources/CodeEditTextView/TextView/TextView+ReplaceCharacters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ extension TextView {
NotificationCenter.default.post(name: Self.textWillChangeNotification, object: self)
layoutManager.beginTransaction()
textStorage.beginEditing()
// Can't insert an ssempty string into an empty range. One must be not empty

var shouldEndGrouping = false
if !(_undoManager?.isGrouping ?? false) {
_undoManager?.beginGrouping()
shouldEndGrouping = true
}

// Can't insert an empty string into an empty range. One must be not empty
for range in ranges.sorted(by: { $0.location > $1.location }) where
(delegate?.textView(self, shouldReplaceContentsIn: range, with: string) ?? true)
&& (!range.isEmpty || !string.isEmpty) {
(!range.isEmpty || !string.isEmpty) &&
(delegate?.textView(self, shouldReplaceContentsIn: range, with: string) ?? true) {
delegate?.textView(self, willReplaceContentsIn: range, with: string)

layoutManager.willReplaceCharactersInRange(range: range, with: string)
Expand All @@ -38,6 +45,11 @@ extension TextView {

delegate?.textView(self, didReplaceContentsIn: range, with: string)
}

if shouldEndGrouping {
_undoManager?.endGrouping()
}

layoutManager.endTransaction()
textStorage.endEditing()
selectionManager.notifyAfterEdit()
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodeEditTextView/TextView/TextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ public class TextView: NSView, NSTextContent {
(" " as NSString).size(withAttributes: [.font: font]).width
}

var _undoManager: CEUndoManager?
internal(set) public var _undoManager: CEUndoManager?
@objc dynamic open var allowsUndo: Bool

var scrollView: NSScrollView? {
Expand Down
20 changes: 14 additions & 6 deletions Sources/CodeEditTextView/Utils/CEUndoManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ public class CEUndoManager {
return
}
isUndoing = true
NotificationCenter.default.post(name: .NSUndoManagerWillUndoChange, object: self.manager)
for mutation in item.mutations.reversed() {
NotificationCenter.default.post(name: .NSUndoManagerWillUndoChange, object: self.manager)
textView.insertText(mutation.inverse.string, replacementRange: mutation.inverse.range)
NotificationCenter.default.post(name: .NSUndoManagerDidUndoChange, object: self.manager)
textView.replaceCharacters(in: mutation.inverse.range, with: mutation.inverse.string)
}
NotificationCenter.default.post(name: .NSUndoManagerDidUndoChange, object: self.manager)
redoStack.append(item)
isUndoing = false
}
Expand All @@ -111,11 +111,11 @@ public class CEUndoManager {
return
}
isRedoing = true
NotificationCenter.default.post(name: .NSUndoManagerWillRedoChange, object: self.manager)
for mutation in item.mutations {
NotificationCenter.default.post(name: .NSUndoManagerWillRedoChange, object: self.manager)
textView.insertText(mutation.mutation.string, replacementRange: mutation.mutation.range)
NotificationCenter.default.post(name: .NSUndoManagerDidRedoChange, object: self.manager)
textView.replaceCharacters(in: mutation.mutation.range, with: mutation.mutation.string)
}
NotificationCenter.default.post(name: .NSUndoManagerDidRedoChange, object: self.manager)
undoStack.append(item)
isRedoing = false
}
Expand Down Expand Up @@ -159,11 +159,19 @@ public class CEUndoManager {

/// Groups all incoming mutations.
public func beginGrouping() {
guard !isGrouping else {
assertionFailure("UndoManager already in a group. Call `endGrouping` before this can be called.")
return
}
isGrouping = true
}

/// Stops grouping all incoming mutations.
public func endGrouping() {
guard isGrouping else {
assertionFailure("UndoManager not in a group. Call `beginGrouping` before this can be called.")
return
}
isGrouping = false
}

Expand Down
15 changes: 15 additions & 0 deletions Sources/CodeEditTextViewObjC/CGContextHidden.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// CGContextHidden.m
// CodeEditTextViewObjC
//
// Created by Khan Winter on 2/12/24.
//

#import <Cocoa/Cocoa.h>
#import "CGContextHidden.h"

extern void CGContextSetFontSmoothingStyle(CGContextRef, int);

void ContextSetHiddenSmoothingStyle(CGContextRef context, int style) {
CGContextSetFontSmoothingStyle(context, style);
}
15 changes: 15 additions & 0 deletions Sources/CodeEditTextViewObjC/include/CGContextHidden.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// CGContextHidden.h
// CodeEditTextViewObjC
//
// Created by Khan Winter on 2/12/24.
//

#ifndef CGContextHidden_h
#define CGContextHidden_h

#import <Cocoa/Cocoa.h>

void ContextSetHiddenSmoothingStyle(CGContextRef context, int style);

#endif /* CGContextHidden_h */
3 changes: 3 additions & 0 deletions Sources/CodeEditTextViewObjC/include/module.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module CodeEditTextViewObjC {
header "CGContextHidden.h"
}
Loading