Skip to content

Commit

Permalink
Merge branch 'develop' of https://github.com/ls1intum/Themis into fea…
Browse files Browse the repository at this point in the history
…ture/correction-rounds
  • Loading branch information
terlan98 committed Nov 5, 2023
2 parents 62ee5f1 + 82bba75 commit 08ac8d1
Show file tree
Hide file tree
Showing 37 changed files with 687 additions and 257 deletions.
76 changes: 57 additions & 19 deletions CodeEditor/Sources/CodeEditor/UXCodeTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ final class UXCodeTextView: UXTextView, HighlightDelegate, UIScrollViewDelegate

private var firstPoint: CGPoint?
private var secondPoint: CGPoint?
private var previousDragSelection: Range<Int>?

var pencilOnly = false {
didSet {
Expand All @@ -79,7 +80,6 @@ final class UXCodeTextView: UXTextView, HighlightDelegate, UIScrollViewDelegate
}

var selectionGranularity = UITextGranularity.character
var canSelectionIncludeHighlightedRanges = true

var feedbackMode = true

Expand Down Expand Up @@ -289,13 +289,15 @@ final class UXCodeTextView: UXTextView, HighlightDelegate, UIScrollViewDelegate
guard let highlightr = highlightr,
highlightr.setTheme(to: (newTheme ?? themeName).rawValue),
let theme = highlightr.theme else { return false }

if theme.codeFont !== self.font {
theme.codeFont = theme.codeFont?.withSize(newSize)
theme.boldCodeFont = theme.boldCodeFont?.withSize(newSize)
theme.italicCodeFont = theme.italicCodeFont?.withSize(newSize)

self.font = theme.codeFont
}

if let font = theme.codeFont, font !== self.font { self.font = font }
guard theme.codeFont?.pointSize != newSize else { return true }

theme.codeFont = theme.codeFont?.withSize(newSize)
theme.boldCodeFont = theme.boldCodeFont?.withSize(newSize)
theme.italicCodeFont = theme.italicCodeFont?.withSize(newSize)
if let newTheme {
themeName = newTheme
}
Expand Down Expand Up @@ -453,7 +455,7 @@ final class UXCodeTextView: UXTextView, HighlightDelegate, UIScrollViewDelegate
}
}
}

func didHighlight(_ range: NSRange, success: Bool) {
if !text.isEmpty {
for hRange in highlightedRanges {
Expand Down Expand Up @@ -522,22 +524,31 @@ final class UXCodeTextView: UXTextView, HighlightDelegate, UIScrollViewDelegate

/// Validates the `dragSelection` value
private func isSelectionValid() -> Bool {
guard !canSelectionIncludeHighlightedRanges else { return true }
guard let dragSelection else { return false }

// Check for empty selection
// Warning: This also prevents selecting an empty line, which is possible in the web client
if let selectedRangeAsNSRange = Range(dragSelection.toNSRange(), in: self.text),
string[selectedRangeAsNSRange].trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return false
}

// Check for overlap
let highlightedIntRanges = highlightedRanges.compactMap({ Range($0.range) })
for range in highlightedIntRanges {
if self.dragSelection?.overlaps(range) == true {
if dragSelection.overlaps(range) == true {
return false
}
}

return true
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard feedbackMode else { return }
guard let touch = touches.first else { return }
if pencilOnly && touch.type == .pencil || !pencilOnly {
self.previousDragSelection = nil
self.firstPoint = touch.location(in: self)
setNeedsDisplay()
}
Expand All @@ -548,7 +559,9 @@ final class UXCodeTextView: UXTextView, HighlightDelegate, UIScrollViewDelegate
guard let touch = touches.first else { return }
if pencilOnly && touch.type == .pencil || !pencilOnly {
self.secondPoint = touch.location(in: self)
self.dragSelection = getSelectionFromTouch()
let calculatedSelection = getSelectionFromTouch()
self.dragSelection = calculatedSelection ?? previousDragSelection
previousDragSelection = calculatedSelection ?? previousDragSelection
setNeedsDisplay()
}
}
Expand All @@ -564,6 +577,31 @@ final class UXCodeTextView: UXTextView, HighlightDelegate, UIScrollViewDelegate

coordinator?.setDragSelection(dragSelection)
}

// This override disables the context menu, but still enables links (tappable highlights)
// https://stackoverflow.com/a/49428307/7074664
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UIPanGestureRecognizer {
// required for compatibility with isScrollEnabled
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer,
tapGestureRecognizer.numberOfTapsRequired == 1 {
// required for compatibility with links
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
// allowing smallDelayRecognizer for links
// https://stackoverflow.com/questions/46143868/xcode-9-uitextview-links-no-longer-clickable
if let longPressGestureRecognizer = gestureRecognizer as? UILongPressGestureRecognizer,
// comparison value is used to distinguish between 0.12 (smallDelayRecognizer) and 0.5 (textSelectionForce and textLoupe)
longPressGestureRecognizer.minimumPressDuration < 0.325 {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
// preventing selection from loupe/magnifier (_UITextSelectionForceGesture), multi tap, tap and a half, etc.
gestureRecognizer.isEnabled = false
return false
}

}

protocol UXCodeTextViewDelegate: UXTextViewDelegate {
Expand All @@ -576,15 +614,15 @@ protocol UXCodeTextViewDelegate: UXTextViewDelegate {
// MARK: - Range functions

extension UXTextView {
/// Tries to find a range enclosing either the given position, it's left neighbor, or it's right neighbor
/// Tries to find a range enclosing either the given position, it's left neighbors, or it's right neighbors
func rangeEnclosingApproximatePosition(_ position: UITextPosition, with granularity: UITextGranularity, inDirection direction: UITextDirection) -> UITextRange? {
let leftPosition = self.position(from: position, offset: -1)
let rightPosition = self.position(from: position, offset: 1)
let positionsToCheck = [position, leftPosition, rightPosition].compactMap({ $0 })

for position in positionsToCheck {
if let range = tokenizer.rangeEnclosingPosition(position, with: granularity, inDirection: direction) {
return range
// We check the position itself, followed by the left and right neighbors
for offset in [0, -1, -2, -3, 1, 2, 3] {
if let newPosition = self.position(from: position, offset: offset) {
if let range = tokenizer.rangeEnclosingPosition(newPosition, with: granularity, inDirection: direction) {
return range
}
}
}

Expand Down
29 changes: 11 additions & 18 deletions CodeEditor/Sources/CodeEditor/UXCodeTextViewRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,6 @@ public struct UXCodeTextViewRepresentable: UXViewRepresentable {
#error("Unsupported OS")
#endif

public func textView(_ textView: UITextView, editMenuForTextIn range: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? {
var additionalActions: [UIMenuElement] = []
if range.length > 0 && self.parent.editorBindings.flags.contains(.feedbackMode) {
let feedbackAction = UIAction(title: "Feedback") { _ in
self.parent.editorBindings.showAddFeedback.wrappedValue.toggle()
}
additionalActions.append(feedbackAction)
}
return UIMenu(children: additionalActions + suggestedActions)
}

public func textView(_ textView: UITextView, shouldInteractWith URL: URL,
in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if interaction == .invokeDefaultAction,
Expand All @@ -170,6 +159,10 @@ public struct UXCodeTextViewRepresentable: UXViewRepresentable {
guard !parent.isCurrentlyUpdatingView.value else {
return
}

// to prevent interference with inline highlight selection
textView.selectedTextRange = nil

// avoid empty references for inline highlights
if textView.selectedRange.length > 0 {
parent.editorBindings.selectedSection.wrappedValue = textView.selectedRange
Expand Down Expand Up @@ -201,8 +194,9 @@ public struct UXCodeTextViewRepresentable: UXViewRepresentable {
defer {
isCurrentlyUpdatingView.value = false
}
if editorBindings.themeName.rawValue != textView.themeName.rawValue {
textView.applyNewTheme(editorBindings.themeName)
if editorBindings.themeName.rawValue != textView.themeName.rawValue,
let fontSize = editorBindings.fontSize?.wrappedValue {
textView.applyNewTheme(editorBindings.themeName, andFontSize: fontSize)
}

textView.language = editorBindings.language
Expand All @@ -216,6 +210,7 @@ public struct UXCodeTextViewRepresentable: UXViewRepresentable {
// reset contentoffset when switching files as it is not stored and content heights of files vary
deletedHighlights.removeAll()
textView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)

if let textStorage = textView.codeTextStorage {
textStorage.replaceCharacters(
in: NSRange(location: 0, length: textStorage.length),
Expand All @@ -224,16 +219,14 @@ public struct UXCodeTextViewRepresentable: UXViewRepresentable {
assertionFailure("no text storage?")
textView.string = editorBindings.source.wrappedValue
}
if editorBindings.flags.contains(.feedbackMode) {
textView.feedbackSuggestions = editorBindings.feedbackSuggestions
textView.updateLightBulbs()
}

textView.feedbackSuggestions = editorBindings.feedbackSuggestions
textView.updateLightBulbs()
}
textView.setNeedsDisplay()
textView.pencilOnly = editorBindings.pencilOnly.wrappedValue
textView.dragSelection = self.editorBindings.dragSelection?.wrappedValue
textView.selectionGranularity = editorBindings.selectionGranularity
textView.canSelectionIncludeHighlightedRanges = editorBindings.canSelectionIncludeHighlightedRanges
textView.font = editorBindings.font ?? textView.font

if let binding = editorBindings.fontSize {
Expand Down
3 changes: 0 additions & 3 deletions CodeEditor/Sources/CodeEditor/Utils/EditorBindings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public struct EditorBindings {
public let indentStyle: CodeEditor.IndentStyle
public var highlightedRanges: [HighlightedRange]
public let selectionGranularity: UITextGranularity
public let canSelectionIncludeHighlightedRanges: Bool
public var dragSelection: Binding<Range<Int>?>?
public var showAddFeedback: Binding<Bool>
public var showEditFeedback: Binding<Bool>
Expand All @@ -37,7 +36,6 @@ public struct EditorBindings {
indentStyle: CodeEditor.IndentStyle = .system,
highlightedRanges: [HighlightedRange],
selectionGranularity: UITextGranularity = .character,
canSelectionIncludeHighlightedRanges: Bool = true,
dragSelection: Binding<Range<Int>?>? = nil,
showAddFeedback: Binding<Bool>,
showEditFeedback: Binding<Bool>,
Expand All @@ -61,7 +59,6 @@ public struct EditorBindings {
self.indentStyle = indentStyle
self.highlightedRanges = highlightedRanges
self.selectionGranularity = selectionGranularity
self.canSelectionIncludeHighlightedRanges = canSelectionIncludeHighlightedRanges
self.dragSelection = dragSelection
self.showAddFeedback = showAddFeedback
self.showEditFeedback = showEditFeedback
Expand Down
Loading

0 comments on commit 08ac8d1

Please sign in to comment.