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

Fix Reference Cycle #255

Merged
merged 4 commits into from
Jul 4, 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
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,15 @@ extension TextViewController {
}
.store(in: &cancellables)

NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
guard self.view.window?.firstResponder == self.textView else { return event }
// let charactersIgnoringModifiers = event.charactersIgnoringModifiers
if let localEventMonitor = self.localEvenMonitor {
NSEvent.removeMonitor(localEventMonitor)
}
self.localEvenMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
guard self?.view.window?.firstResponder == self?.textView else { return event }
let commandKey = NSEvent.ModifierFlags.command.rawValue
let modifierFlags = event.modifierFlags.intersection(.deviceIndependentFlagsMask).rawValue
if modifierFlags == commandKey && event.charactersIgnoringModifiers == "/" {
self.commandSlashCalled()
self?.commandSlashCalled()
return nil
} else {
return event
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// TextViewController+StyleViews.swift
// CodeEditSourceEditor
//
// Created by Khan Winter on 7/3/24.
//

import AppKit

extension TextViewController {
package func generateParagraphStyle() -> NSMutableParagraphStyle {
// swiftlint:disable:next force_cast
let paragraph = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
paragraph.tabStops.removeAll()
paragraph.defaultTabInterval = CGFloat(tabWidth) * fontCharWidth
return paragraph
}

/// Style the text view.
package func styleTextView() {
textView.selectionManager.selectionBackgroundColor = theme.selection
textView.selectionManager.selectedLineBackgroundColor = getThemeBackground()
textView.selectionManager.highlightSelectedLine = isEditable
textView.selectionManager.insertionPointColor = theme.insertionPoint
paragraphStyle = generateParagraphStyle()
textView.typingAttributes = attributesFor(nil)
}

/// Finds the preferred use theme background.
/// - Returns: The background color to use.
private func getThemeBackground() -> NSColor {
if useThemeBackground {
return theme.lineHighlight
}

if systemAppearance == .darkAqua {
return NSColor.quaternaryLabelColor
}

return NSColor.selectedTextBackgroundColor.withSystemEffect(.disabled)
}

/// Style the gutter view.
package func styleGutterView() {
gutterView.frame.origin.y = -scrollView.contentInsets.top
gutterView.selectedLineColor = useThemeBackground ? theme.lineHighlight : systemAppearance == .darkAqua
? NSColor.quaternaryLabelColor
: NSColor.selectedTextBackgroundColor.withSystemEffect(.disabled)
gutterView.highlightSelectedLines = isEditable
gutterView.font = font.rulerFont
gutterView.backgroundColor = useThemeBackground ? theme.background : .textBackgroundColor
if self.isEditable == false {
gutterView.selectedLineTextColor = nil
gutterView.selectedLineColor = .clear
}
}

/// Style the scroll view.
package func styleScrollView() {
guard let scrollView = view as? NSScrollView else { return }
scrollView.drawsBackground = useThemeBackground
scrollView.backgroundColor = useThemeBackground ? theme.background : .clear
if let contentInsets {
scrollView.automaticallyAdjustsContentInsets = false
scrollView.contentInsets = contentInsets
} else {
scrollView.automaticallyAdjustsContentInsets = true
}
scrollView.contentInsets.bottom = (contentInsets?.bottom ?? 0) + bottomContentInsets
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ extension TextViewController {
/// Method called when CMD + / key sequence recognized, comments cursor's current line of code
public func commandSlashCalled() {
guard let cursorPosition = cursorPositions.first else {
print("There is no cursor \(#function)")
return
}
// Many languages require a character sequence at the beginning of the line to comment the line.
Expand All @@ -33,41 +32,31 @@ extension TextViewController {

/// Toggles comment string at the beginning of a specified line (lineNumber is 1-indexed)
private func toggleCharsAtBeginningOfLine(chars: String, lineNumber: Int) {
guard let lineInfo = textView.layoutManager.textLineForIndex(lineNumber - 1) else {
print("There are no characters/lineInfo \(#function)")
return
}
guard let lineString = textView.textStorage.substring(from: lineInfo.range) else {
print("There are no characters/lineString \(#function)")
guard let lineInfo = textView.layoutManager.textLineForIndex(lineNumber - 1),
let lineString = textView.textStorage.substring(from: lineInfo.range) else {
return
}
let firstNonWhiteSpaceCharIndex = lineString.firstIndex(where: {!$0.isWhitespace}) ?? lineString.startIndex
let numWhitespaceChars = lineString.distance(from: lineString.startIndex, to: firstNonWhiteSpaceCharIndex)
let firstCharsInLine = lineString.suffix(from: firstNonWhiteSpaceCharIndex).prefix(chars.count)
// toggle comment off
if firstCharsInLine == chars {
textView.replaceCharacters(in: NSRange(
location: lineInfo.range.location + numWhitespaceChars,
length: chars.count
), with: "")
}
// toggle comment on
else {
textView.replaceCharacters(in: NSRange(
location: lineInfo.range.location + numWhitespaceChars,
length: 0
), with: chars)
textView.replaceCharacters(
in: NSRange(location: lineInfo.range.location + numWhitespaceChars, length: chars.count),
with: ""
)
} else {
// toggle comment on
textView.replaceCharacters(
in: NSRange(location: lineInfo.range.location + numWhitespaceChars, length: 0),
with: chars
)
}
}

/// Toggles a specific string of characters at the end of a specified line. (lineNumber is 1-indexed)
private func toggleCharsAtEndOfLine(chars: String, lineNumber: Int) {
guard let lineInfo = textView.layoutManager.textLineForIndex(lineNumber - 1) else {
print("There are no characters/lineInfo \(#function)")
return
}
guard let lineString = textView.textStorage.substring(from: lineInfo.range) else {
print("There are no characters/lineString \(#function)")
guard let lineInfo = textView.layoutManager.textLineForIndex(lineNumber - 1), !lineInfo.range.isEmpty else {
return
}
let lineLastCharIndex = lineInfo.range.location + lineInfo.range.length - 1
Expand All @@ -79,13 +68,12 @@ extension TextViewController {
let lastCharsInLine = textView.textStorage.substring(from: closeCommentRange)
// toggle comment off
if lastCharsInLine == chars {
textView.replaceCharacters(in: NSRange(
location: lineLastCharIndex - closeCommentLength,
length: closeCommentLength
), with: "")
}
// toggle comment on
else {
textView.replaceCharacters(
in: NSRange(location: lineLastCharIndex - closeCommentLength, length: closeCommentLength),
with: ""
)
} else {
// toggle comment on
textView.replaceCharacters(in: NSRange(location: lineLastCharIndex, length: 0), with: chars)
}
}
Expand Down
72 changes: 8 additions & 64 deletions Sources/CodeEditSourceEditor/Controller/TextViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class TextViewController: NSViewController {
internal var highlightLayers: [CALayer] = []
internal var systemAppearance: NSAppearance.Name?

package var localEvenMonitor: Any?
package var isPostingCursorNotification: Bool = false

/// The string contents.
Expand Down Expand Up @@ -172,15 +173,15 @@ public class TextViewController: NSViewController {
/// This will be `nil` if another highlighter provider is passed to the source editor.
internal(set) public var treeSitterClient: TreeSitterClient?

private var fontCharWidth: CGFloat { (" " as NSString).size(withAttributes: [.font: font]).width }
package var fontCharWidth: CGFloat { (" " as NSString).size(withAttributes: [.font: font]).width }

/// Filters used when applying edits..
internal var textFilters: [TextFormation.Filter] = []

internal var cancellables = Set<AnyCancellable>()

/// ScrollView's bottom inset using as editor overscroll
private var bottomContentInsets: CGFloat {
package var bottomContentInsets: CGFloat {
let height = view.frame.height
var inset = editorOverscroll * height

Expand Down Expand Up @@ -270,15 +271,7 @@ public class TextViewController: NSViewController {
// MARK: Paragraph Style

/// A default `NSParagraphStyle` with a set `lineHeight`
internal lazy var paragraphStyle: NSMutableParagraphStyle = generateParagraphStyle()

private func generateParagraphStyle() -> NSMutableParagraphStyle {
// swiftlint:disable:next force_cast
let paragraph = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
paragraph.tabStops.removeAll()
paragraph.defaultTabInterval = CGFloat(tabWidth) * fontCharWidth
return paragraph
}
package lazy var paragraphStyle: NSMutableParagraphStyle = generateParagraphStyle()

// MARK: - Reload UI

Expand All @@ -293,59 +286,6 @@ public class TextViewController: NSViewController {
highlighter?.invalidate()
}

/// Style the text view.
package func styleTextView() {
textView.selectionManager.selectionBackgroundColor = theme.selection
textView.selectionManager.selectedLineBackgroundColor = getThemeBackground()
textView.selectionManager.highlightSelectedLine = isEditable
textView.selectionManager.insertionPointColor = theme.insertionPoint
paragraphStyle = generateParagraphStyle()
textView.typingAttributes = attributesFor(nil)
}

/// Finds the preferred use theme background.
/// - Returns: The background color to use.
private func getThemeBackground() -> NSColor {
if useThemeBackground {
return theme.lineHighlight
}

if systemAppearance == .darkAqua {
return NSColor.quaternaryLabelColor
}

return NSColor.selectedTextBackgroundColor.withSystemEffect(.disabled)
}

/// Style the gutter view.
package func styleGutterView() {
gutterView.frame.origin.y = -scrollView.contentInsets.top
gutterView.selectedLineColor = useThemeBackground ? theme.lineHighlight : systemAppearance == .darkAqua
? NSColor.quaternaryLabelColor
: NSColor.selectedTextBackgroundColor.withSystemEffect(.disabled)
gutterView.highlightSelectedLines = isEditable
gutterView.font = font.rulerFont
gutterView.backgroundColor = useThemeBackground ? theme.background : .textBackgroundColor
if self.isEditable == false {
gutterView.selectedLineTextColor = nil
gutterView.selectedLineColor = .clear
}
}

/// Style the scroll view.
package func styleScrollView() {
guard let scrollView = view as? NSScrollView else { return }
scrollView.drawsBackground = useThemeBackground
scrollView.backgroundColor = useThemeBackground ? theme.background : .clear
if let contentInsets {
scrollView.automaticallyAdjustsContentInsets = false
scrollView.contentInsets = contentInsets
} else {
scrollView.automaticallyAdjustsContentInsets = true
}
scrollView.contentInsets.bottom = (contentInsets?.bottom ?? 0) + bottomContentInsets
}

deinit {
if let highlighter {
textView.removeStorageDelegate(highlighter)
Expand All @@ -354,6 +294,10 @@ public class TextViewController: NSViewController {
highlightProvider = nil
NotificationCenter.default.removeObserver(self)
cancellables.forEach { $0.cancel() }
if let localEvenMonitor {
NSEvent.removeMonitor(localEvenMonitor)
}
localEvenMonitor = nil
}
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/CodeEditSourceEditor/Gutter/GutterView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -213,5 +213,7 @@ public class GutterView: NSView {

deinit {
NotificationCenter.default.removeObserver(self)
delegate = nil
textView = nil
}
}
11 changes: 4 additions & 7 deletions Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Highlighter: NSObject {
private var theme: EditorTheme

/// The object providing attributes for captures.
private weak var attributeProvider: ThemeAttributesProviding!
private weak var attributeProvider: ThemeAttributesProviding?

/// The current language of the editor.
private var language: CodeLanguage
Expand Down Expand Up @@ -113,7 +113,7 @@ class Highlighter: NSObject {
// Remove all current highlights. Makes the language setting feel snappier and tells the user we're doing
// something immediately.
textView.textStorage.setAttributes(
attributeProvider.attributesFor(nil),
attributeProvider?.attributesFor(nil) ?? [:],
range: NSRange(location: 0, length: textView.textStorage.length)
)
textView.layoutManager.invalidateLayoutForRect(textView.visibleRect)
Expand All @@ -133,6 +133,7 @@ class Highlighter: NSObject {
}

deinit {
NotificationCenter.default.removeObserver(self)
self.attributeProvider = nil
self.textView = nil
self.highlightProvider = nil
Expand Down Expand Up @@ -234,10 +235,7 @@ private extension Highlighter {
// they need to be changed back.
for ignoredRange in ignoredIndexes.rangeView
where textView?.documentRange.upperBound ?? 0 > ignoredRange.upperBound {
textView?.textStorage.setAttributes(
attributeProvider.attributesFor(nil),
range: NSRange(ignoredRange)
)
textView?.textStorage.setAttributes(attributeProvider.attributesFor(nil), range: NSRange(ignoredRange))
}

textView?.textStorage.endEditing()
Expand All @@ -262,7 +260,6 @@ private extension Highlighter {
length: min(rangeChunkLimit, range.upperBound - range.lowerBound)
)
}

}

// MARK: - Visible Content Updates
Expand Down
Loading