Skip to content
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 @@ -379,6 +379,26 @@ class EnrichedMarkdownTextInputView(
text?.let { blockStore.normalizeToLineBounds(it) }
}

private inline fun replaceTextInRange(
start: Int,
end: Int,
newText: String,
postAdjust: (Editable) -> Unit = {},
) {
val editable = text ?: return
isProcessingTextChange = true
try {
editable.replace(start, end, newText)
adjustStoresForEdit(start, end - start, newText.length)
postAdjust(editable)
lastProcessedText = editable.toString()
applyFormattingAndEmit()
eventEmitter.emitChangeText()
} finally {
isProcessingTextChange = false
}
}

fun applyFormatting() {
val editable = text ?: return
formatter.applyFormatting(editable, formattingStore.allRanges)
Expand Down Expand Up @@ -581,11 +601,7 @@ class EnrichedMarkdownTextInputView(
val selStart = selectionStart.coerceIn(0, editable.length)
val selEnd = selectionEnd.coerceIn(selStart, editable.length)

isProcessingTextChange = true
try {
editable.replace(selStart, selEnd, parsed.plainText)
adjustStoresForEdit(selStart, selEnd - selStart, parsed.plainText.length)

replaceTextInRange(selStart, selEnd, parsed.plainText) { editable ->
for (range in parsed.formattingRanges) {
formattingStore.addRange(
FormattingRange(range.type, range.start + selStart, range.end + selStart, range.url),
Expand All @@ -594,15 +610,8 @@ class EnrichedMarkdownTextInputView(
for (block in parsed.blockRanges) {
blockStore.setBlock(block.type, block.level, block.start + selStart, block.end + selStart, editable)
}

val currentText = editable.toString()
lastProcessedText = currentText
setSelection(selStart + parsed.plainText.length)
applyFormattingAndEmit()
detectorPipeline.processTextChange(editable, currentText, selStart, parsed.plainText.length)
eventEmitter.emitChangeText()
} finally {
isProcessingTextChange = false
detectorPipeline.processTextChange(editable, editable.toString(), selStart, parsed.plainText.length)
}
}

Expand Down Expand Up @@ -712,24 +721,14 @@ class EnrichedMarkdownTextInputView(
displayText: String,
url: String,
) {
val editable = text ?: return
val selStart = selectionStart
val selEnd = selectionEnd
val linkEnd = selStart + displayText.length

isProcessingTextChange = true
try {
editable.replace(selStart, selEnd, displayText)
adjustStoresForEdit(selStart, selEnd - selStart, displayText.length)
replaceTextInRange(selStart, selEnd, displayText) { editable ->
autoLinkDetector.clearAutoLinkInRange(editable, selStart, linkEnd)
formattingStore.addRange(FormattingRange(StyleType.LINK, selStart, linkEnd, sanitizeLinkUrl(url)))
lastProcessedText = editable.toString()

setSelection(linkEnd)
applyFormattingAndEmit()
eventEmitter.emitChangeText()
} finally {
isProcessingTextChange = false
}
}

Expand All @@ -749,41 +748,23 @@ class EnrichedMarkdownTextInputView(
val replacement = if (shouldAppendSpace) "$displayText " else displayText
val linkEnd = start + displayText.length

isProcessingTextChange = true
try {
editable.replace(start, end, replacement)
adjustStoresForEdit(start, end - start, replacement.length)
autoLinkDetector.clearAutoLinkInRange(editable, start, linkEnd)
replaceTextInRange(start, end, replacement) { ed ->
autoLinkDetector.clearAutoLinkInRange(ed, start, linkEnd)
formattingStore.addRange(FormattingRange(StyleType.LINK, start, linkEnd, sanitizedUrl))
lastProcessedText = editable.toString()

clearActiveMention(emit = true, indicatorOverride = indicator)
setSelection(start + replacement.length)
applyFormattingAndEmit()
eventEmitter.emitChangeText()
} finally {
isProcessingTextChange = false
}
}

fun startMention(indicator: String) {
if (indicator.isEmpty() || indicator !in mentionIndicators) return
val editable = text ?: return
val selStart = selectionStart
val selEnd = selectionEnd

isProcessingTextChange = true
try {
editable.replace(selStart, selEnd, indicator)
adjustStoresForEdit(selStart, selEnd - selStart, indicator.length)
lastProcessedText = editable.toString()
replaceTextInRange(selStart, selEnd, indicator) {
setSelection(selStart + indicator.length)
applyFormattingAndEmit()
eventEmitter.emitChangeText()
updateActiveMention()
} finally {
isProcessingTextChange = false
}
updateActiveMention()
}

fun removeLinkAtCursor() {
Expand All @@ -794,25 +775,17 @@ class EnrichedMarkdownTextInputView(
}

fun deleteLinkBeforeCursor(): Boolean {
val editable = text ?: return false
val cursorStart = selectionStart
val cursorEnd = selectionEnd
if (cursorStart != cursorEnd || cursorStart <= 0) return false
if (text == null) return false

val linkRange = formattingStore.rangeOfType(StyleType.LINK, cursorStart - 1) ?: return false

isProcessingTextChange = true
try {
editable.delete(linkRange.start, linkRange.end)
adjustStoresForEdit(linkRange.start, linkRange.length, 0)
lastProcessedText = editable.toString()
replaceTextInRange(linkRange.start, linkRange.end, "") { editable ->
setSelection(linkRange.start.coerceAtMost(editable.length))
applyFormattingAndEmit()
eventEmitter.emitChangeText()
clearActiveMention()
} finally {
isProcessingTextChange = false
}
clearActiveMention()
return true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -607,11 +607,9 @@ - (void)replaceTextInRange:(NSRange)selection
ENRMReplaceTextInRange(_textView, text, selection);
_isApplyingFormatting = NO;

NSString *plainText = ENRMGetPlainText(_textView);
[_formattingStore adjustForEditAtLocation:editLocation deletedLength:selection.length insertedLength:text.length];
[_blockStore adjustForEditAtLocation:editLocation deletedLength:selection.length insertedLength:text.length];
[self pruneOrphanedHeadingBlocks];
[_blockStore normalizeToLineBoundsInText:plainText];
NSString *plainText = [self adjustStoresForEditAtLocation:editLocation
deletedLength:selection.length
insertedLength:text.length];

for (ENRMFormattingRange *range in ranges) {
NSRange shifted = NSMakeRange(range.range.location + editLocation, range.range.length);
Expand All @@ -628,8 +626,7 @@ - (void)replaceTextInRange:(NSRange)selection

[self applyFormatting];

[_detectorPipeline processTextChange:ENRMGetPlainText(_textView)
modificationRange:NSMakeRange(editLocation, text.length)];
[_detectorPipeline processTextChange:plainText modificationRange:NSMakeRange(editLocation, text.length)];

[self updatePlaceholderVisibility];
[self emitOnChangeText];
Expand Down Expand Up @@ -956,6 +953,18 @@ - (void)syncTypingAttributesWithCursorBlock
_textView.typingAttributes = attrs;
}

- (NSString *)adjustStoresForEditAtLocation:(NSUInteger)editLocation
deletedLength:(NSUInteger)deletedLength
insertedLength:(NSUInteger)insertedLength
{
[_formattingStore adjustForEditAtLocation:editLocation deletedLength:deletedLength insertedLength:insertedLength];
[_blockStore adjustForEditAtLocation:editLocation deletedLength:deletedLength insertedLength:insertedLength];
[self pruneOrphanedHeadingBlocks];
NSString *plainText = ENRMGetPlainText(_textView);
[_blockStore normalizeToLineBoundsInText:plainText];
return plainText;
}

/// Reverts to a plain paragraph any heading no longer anchored at a line start
/// (e.g. Backspace merged its line into the previous one). Must run BEFORE
/// normalizeToLineBoundsInText: so a merged range is judged on its unsnapped
Expand Down Expand Up @@ -1671,17 +1680,12 @@ - (void)handleTextChanged
}
}

[_formattingStore adjustForEditAtLocation:editLocation deletedLength:deletedLength insertedLength:insertedLength];
[_blockStore adjustForEditAtLocation:editLocation deletedLength:deletedLength insertedLength:insertedLength];
[self pruneOrphanedHeadingBlocks];
[_blockStore normalizeToLineBoundsInText:ENRMGetPlainText(_textView)];
NSString *plainText = [self adjustStoresForEditAtLocation:editLocation
deletedLength:deletedLength
insertedLength:insertedLength];

if (insertedLength > 0) {
NSRange insertedRange = NSMakeRange(editLocation, insertedLength);

// Skip applying pending styles when the insertion is only line breaks —
// a phantom range over a bare newline corrupts isStyleActive() at the boundary.
NSString *plainText = ENRMGetPlainText(_textView);
NSUInteger insertedEnd = NSMaxRange(insertedRange);
BOOL insertedHasGlyphContent = NO;
if (insertedEnd <= plainText.length) {
Expand Down
Loading