Splice AI text result into the selected span

This commit is contained in:
John Preston
2026-07-01 23:10:45 +04:00
parent 5337e5fb5b
commit 4ed5eff3f5
3 changed files with 173 additions and 0 deletions
@@ -7588,6 +7588,81 @@ State::ActiveTextBlockActionResult State::applyActiveTextBlockAction(
});
}
State::ActiveTextBlockActionResult
State::replaceActiveTextSelectionWithRichPage(
std::shared_ptr<const RichPage> page,
ActiveTextInsertContext context) {
return applyCheckedMutation(ActiveTextBlockActionResult{
.result = ApplyResult::Failed,
}, [page = std::move(page), context = std::move(context)](
State &candidate) mutable {
ActiveTextBlockActionResult result{
.result = ApplyResult::Failed,
};
const auto failed = [&] {
return CheckedMutationResult<ActiveTextBlockActionResult>{
.result = result,
};
};
if (!page || page->blocks.empty()) {
return failed();
}
const auto descriptor = candidate.textNode(
candidate._activeTextOrdinal);
if (!descriptor || (descriptor->leaf.kind == LeafKind::MathFormula)) {
return failed();
}
const auto singleParagraph = (page->blocks.size() == 1)
&& (page->blocks.front().kind == BlockKind::Paragraph)
&& page->blocks.front().blocks.empty()
&& (descriptor->leaf.kind != LeafKind::TableCellText);
if (singleParagraph) {
const auto &inner = page->blocks.front().text.text;
const auto selectionFrom = int(context.before.text.size());
const auto selectionTo = selectionFrom + int(inner.text.size());
const auto applied = candidate.applyActiveTextUnchecked(JoinText(
context.before,
inner,
context.after));
if (applied == ApplyResult::Failed) {
return failed();
}
const auto updated = candidate.textNode(
candidate._activeTextOrdinal);
return CheckedMutationResult<ActiveTextBlockActionResult>{
.apply = true,
.result = {
.result = ApplyResult::Changed,
.destinationLeaf = (updated
? updated->leaf
: descriptor->leaf),
.selectionFrom = selectionFrom,
.selectionTo = selectionTo,
},
};
}
auto blocks = page->blocks;
const auto applied = candidate.insertBlocksAfterActiveUnchecked(
std::move(blocks),
context);
if (!applied) {
return failed();
}
const auto updated = candidate.textNode(candidate._activeTextOrdinal);
return CheckedMutationResult<ActiveTextBlockActionResult>{
.apply = true,
.result = {
.result = ApplyResult::Changed,
.destinationLeaf = (updated
? updated->leaf
: descriptor->leaf),
.selectionFrom = 0,
.selectionTo = 0,
},
};
});
}
bool State::insertBlockAfterActive(
InsertAction action,
std::optional<ActiveTextInsertContext> context) {
@@ -412,6 +412,10 @@ public:
[[nodiscard]] ActiveTextBlockActionResult applyActiveTextBlockAction(
InsertAction action,
ActiveTextInsertContext context);
[[nodiscard]] ActiveTextBlockActionResult
replaceActiveTextSelectionWithRichPage(
std::shared_ptr<const RichPage> page,
ActiveTextInsertContext context);
[[nodiscard]] bool insertPreparedBlockAfterActive(RichPage::Block block);
[[nodiscard]] bool insertPreparedBlocksAfterActive(
std::vector<RichPage::Block> blocks,
@@ -3678,6 +3678,100 @@ void Widget::replaceCurrentSelectionWithRichPage(
return;
}
}
auto data = ClipboardBlockData();
data.blocks = page->blocks;
pasteStructuredClipboardData(ClipboardData(std::move(data)));
return;
}
if (auto context = activeTextInsertContext()) {
recordMutationTransaction([&] {
const auto restoreLeaf = _fieldLeaf;
const auto restoreStyleKey = _activeFieldStyleKey;
const auto restoreMode = _fieldMode;
const auto restoreSelection
= captureHistoryViewState().leafSelection;
_pendingOrdinal = -1;
_pendingCursorOffset = 0;
hideInlineField();
clearInlineFieldEditSession(true);
auto restore = true;
const auto restoreInlineField = gsl::finally([&] {
if (!restore) {
return;
}
if (restoreLeaf && restoreStyleKey) {
if (auto revived = reviveRetainedLeafField(
_historyIndex,
*restoreLeaf,
restoreMode,
*restoreStyleKey)) {
_field = std::move(revived);
_activeFieldStyleKey = restoreStyleKey;
_fieldMode = restoreMode;
_fieldLeaf = *restoreLeaf;
refreshInlineFieldPlaceholder();
_fieldUndoAvailable = _field->isUndoAvailable();
_fieldRedoAvailable = _field->isRedoAvailable();
clearFieldUndoRedoNoopState();
}
}
if (!_fieldLeaf && restoreSelection) {
const auto ordinal = _state->textOrdinalForLeafPath(
restoreSelection->leaf);
if (ordinal >= 0) {
activateTextOrdinal(
ordinal,
restoreSelection->anchorOffset,
restoreSelection->cursorOffset);
return;
}
}
_field->show();
syncInlineFieldGeometry();
updateInlineFieldHeightOverride();
syncArticleVisibleTopBottom();
revealActiveInlineField();
_field->raise();
_field->setFocusFast();
notifyToolbarStateChanged();
});
const auto applied = _state->replaceActiveTextSelectionWithRichPage(
page,
*context);
if (applied.result != ApplyResult::Changed) {
showLastLimitToast();
return MutationTransactionResult{
.committed = ApplyResult::Unchanged,
};
}
restore = false;
refreshPreparedContent();
auto restored = false;
if (applied.destinationLeaf) {
const auto ordinal = _state->textOrdinalForLeafPath(
*applied.destinationLeaf);
if (ordinal >= 0) {
activateTextOrdinal(
ordinal,
applied.selectionFrom,
applied.selectionTo);
restored = true;
}
}
if (!restored) {
const auto ordinal = _state->activeTextOrdinal();
if (ordinal >= 0 && ordinal < _state->textNodeCount()) {
activateTextOrdinal(ordinal, 0);
} else {
activateInitialNode();
}
}
return MutationTransactionResult{
.committed = ApplyResult::Unchanged,
.changed = true,
};
});
return;
}
auto data = ClipboardBlockData();
data.blocks = page->blocks;