diff --git a/Src/views/VwSelection.cpp b/Src/views/VwSelection.cpp
index e5eb3f1c17..4fa25937b3 100644
--- a/Src/views/VwSelection.cpp
+++ b/Src/views/VwSelection.cpp
@@ -5417,6 +5417,72 @@ bool CheckForParaBreak(const wchar * pchw, int ich, int cch, int * pichNext)
return false;
}
+///
+/// Check if the selected text (the paste target) is a complete single paragraph.
+///
+///
+/// True: If the selection contains exactly one complete paragraph, which means that the selection starts
+/// at the beginning of a paragraph and ends at the end of the same paragraph.
+/// False: If the selection contains text from more than one paragraph or only contains part of a paragraph.
+///
+bool VwTextSelection::SelectedTextIsSingleParagraph()
+{
+ VwParagraphBox* notUsed;
+ int targetMin;
+ int targetLim; // One past the last selected character, relative to the last paragraph.
+ GetFirstAndLast(¬Used, ¬Used, &targetMin, &targetLim);
+
+ if (!m_pvpboxEnd &&
+ targetMin == 0 &&
+ m_pvpbox->Source()->CStrings() == 1)
+ {
+ ITsString* firstStr = NULL;
+ m_pvpbox->Source()->StringAtIndex(0, &firstStr);
+ int firstStrLen = 0;
+ CheckHr(firstStr->get_Length(&firstStrLen));
+
+ if (targetLim == firstStrLen)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+///
+/// Check if the replacement text is a single paragraph.
+///
+/// The replacement text.
+///
+/// True: If the replacement text contains exactly one paragraph, which means that the text ends with
+/// a '\n' and contains no other '\n' characters.
+/// False: If the replacement text contains more than one '\n' or if the '\n' is not at the end.
+///
+bool VwTextSelection::ReplacementTextIsSingleParagraph(ITsString* replacementTstr)
+{
+ int replacementCharCount;
+ SmartBstr replacementBstr;
+ CheckHr(replacementTstr->get_Length(&replacementCharCount));
+ CheckHr(replacementTstr->get_Text(&replacementBstr));
+ const wchar* replacementText = replacementBstr.Chars();
+ for (int ii = 0; ii < replacementCharCount; ii++)
+ {
+ if (replacementText[ii] == '\n')
+ {
+ // The last character is a '\n' and there are no other '\n' characters in the string.
+ if (ii == replacementCharCount - 1)
+ {
+ return true;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ return false;
+}
+
/*----------------------------------------------------------------------------------------------
Replace what is selected with a TsString.
If the string contains newlines, the properties associated with the Newline become
@@ -5449,6 +5515,21 @@ STDMETHODIMP VwTextSelection::ReplaceWithTsString(ITsString * ptss)
CheckHr(ptss->get_NormalizedForm(knmNFD, &qtssNorm));
ptss = qtssNorm;
+ // If the selected text (the paste target) is a complete single paragraph, and the replacement
+ // text is a complete single paragraph (has exactly one terminal paragraph marker with no interior
+ // paragraph breaks) then trim that marker so that we do character replacement instead of paragraph
+ // replacement. Paragraph replacement fails in this case: LT-20857 (and many others).
+ if (SelectedTextIsSingleParagraph() && ReplacementTextIsSingleParagraph(ptss))
+ {
+ int replacementCharCount;
+ CheckHr(ptss->get_Length(&replacementCharCount));
+
+ // Remove the trailing '\n'.
+ ITsStringPtr trimmedReplacementText;
+ MakeSubString(ptss, 0, replacementCharCount - 1, &trimmedReplacementText);
+ ptss = trimmedReplacementText;
+ }
+
// default: only position changed; set to 2 if para changes
VwSelChangeType nHowChanged = ksctSamePara;
try
diff --git a/Src/views/VwSelection.h b/Src/views/VwSelection.h
index bd701d06e9..2161f47dc4 100644
--- a/Src/views/VwSelection.h
+++ b/Src/views/VwSelection.h
@@ -541,6 +541,8 @@ class VwTextSelection : public VwSelection
IVwViewConstructor * pvvcEdit, int fragEdit, ITsStrBldr * ptsb, bool * pfOk);
virtual bool RuinedByDeleting(VwBox * pbox, VwBox * pboxReplacement=0);
void MakeSubString(ITsString * ptss, int ichMin, int ichLim, ITsString ** pptssSub);
+ bool SelectedTextIsSingleParagraph();
+ bool ReplacementTextIsSingleParagraph(ITsString* ptss);
void SetInsertionProps(ITsTextProps * pttp)
{