From 4ed5eff3f59fb65d71b944ea1b4ca5b22d8bc91a Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 1 Jul 2026 23:10:45 +0400 Subject: [PATCH] Splice AI text result into the selected span --- .../SourceFiles/iv/editor/iv_editor_state.cpp | 75 +++++++++++++++ .../SourceFiles/iv/editor/iv_editor_state.h | 4 + .../iv/editor/iv_editor_widget.cpp | 94 +++++++++++++++++++ 3 files changed, 173 insertions(+) diff --git a/Telegram/SourceFiles/iv/editor/iv_editor_state.cpp b/Telegram/SourceFiles/iv/editor/iv_editor_state.cpp index ae20befd09..56ebc79c40 100644 --- a/Telegram/SourceFiles/iv/editor/iv_editor_state.cpp +++ b/Telegram/SourceFiles/iv/editor/iv_editor_state.cpp @@ -7588,6 +7588,81 @@ State::ActiveTextBlockActionResult State::applyActiveTextBlockAction( }); } +State::ActiveTextBlockActionResult +State::replaceActiveTextSelectionWithRichPage( + std::shared_ptr 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{ + .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{ + .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{ + .apply = true, + .result = { + .result = ApplyResult::Changed, + .destinationLeaf = (updated + ? updated->leaf + : descriptor->leaf), + .selectionFrom = 0, + .selectionTo = 0, + }, + }; + }); +} + bool State::insertBlockAfterActive( InsertAction action, std::optional context) { diff --git a/Telegram/SourceFiles/iv/editor/iv_editor_state.h b/Telegram/SourceFiles/iv/editor/iv_editor_state.h index 940b91f27f..91de5054c9 100644 --- a/Telegram/SourceFiles/iv/editor/iv_editor_state.h +++ b/Telegram/SourceFiles/iv/editor/iv_editor_state.h @@ -412,6 +412,10 @@ public: [[nodiscard]] ActiveTextBlockActionResult applyActiveTextBlockAction( InsertAction action, ActiveTextInsertContext context); + [[nodiscard]] ActiveTextBlockActionResult + replaceActiveTextSelectionWithRichPage( + std::shared_ptr page, + ActiveTextInsertContext context); [[nodiscard]] bool insertPreparedBlockAfterActive(RichPage::Block block); [[nodiscard]] bool insertPreparedBlocksAfterActive( std::vector blocks, diff --git a/Telegram/SourceFiles/iv/editor/iv_editor_widget.cpp b/Telegram/SourceFiles/iv/editor/iv_editor_widget.cpp index e2fa7e5e35..dcaf2ebaca 100644 --- a/Telegram/SourceFiles/iv/editor/iv_editor_widget.cpp +++ b/Telegram/SourceFiles/iv/editor/iv_editor_widget.cpp @@ -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;