[Libreoffice-commits] core.git: Branch 'distro/collabora/cp-6.0' - 2 commits - editeng/source include/editeng sw/inc sw/source

Mike Kaganski (via logerrit) logerrit at kemper.freedesktop.org
Wed Oct 9 11:12:17 UTC 2019


 editeng/source/misc/svxacorr.cxx    |   57 ++++++++--
 include/editeng/svxacorr.hxx        |   10 +
 sw/inc/editsh.hxx                   |    6 -
 sw/source/core/edit/edws.cxx        |   26 +++-
 sw/source/uibase/dochdl/gloshdl.cxx |    5 
 sw/source/uibase/docvw/edtwin.cxx   |  202 +++++++++++++++++++++---------------
 sw/source/uibase/inc/edtwin.hxx     |    5 
 sw/source/uibase/inc/gloslst.hxx    |    3 
 sw/source/uibase/utlui/gloslst.cxx  |   63 +++++++++--
 9 files changed, 257 insertions(+), 120 deletions(-)

New commits:
commit 97e2e47e22f1e623d081376186d0b16499205097
Author:     Mike Kaganski <mike.kaganski at collabora.com>
AuthorDate: Mon Oct 7 17:46:36 2019 +0300
Commit:     Mike Kaganski <mike.kaganski at collabora.com>
CommitDate: Wed Oct 9 13:11:22 2019 +0200

    tdf#128009: Allow spaces in AutoText suggestions
    
    Currently autotext entries with long names starting with spaces, or
    containing spaces after first or second character, would never be
    suggested when SvxAutoCorrCfg::IsAutoTextTip() gives true (set in
    Tools -> AutoCorrect -> [x] Display remainder of name as suggestion
    while typing), because only a single word no less than 3 chars long
    left to cursor is considered a candidate for the name matching.
    
    This change allows to consider multiple chunks of text left to the
    cursor as the candidates for name matching. The chunks are 3-9
    characters long, may start only between words, and have spaces,
    including leading. Thus, AutoText entries with long names like
    "  Dr Foo" will now be suggested for an entry like "lorem  dr f".
    
    Change-Id: If91c957341a4f4b281acb0e4ada558706ea2f8c1
    Reviewed-on: https://gerrit.libreoffice.org/80392
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kaganski at collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/80495
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-on: https://gerrit.libreoffice.org/80530

diff --git a/editeng/source/misc/svxacorr.cxx b/editeng/source/misc/svxacorr.cxx
index bf52a5cf2a69..922f0b97973f 100644
--- a/editeng/source/misc/svxacorr.cxx
+++ b/editeng/source/misc/svxacorr.cxx
@@ -1526,12 +1526,12 @@ bool SvxAutoCorrect::AddWrtSttException( const OUString& rNew,
     return pLists && pLists->AddToWrdSttExceptList(rNew);
 }
 
-bool SvxAutoCorrect::GetPrevAutoCorrWord( SvxAutoCorrDoc const & rDoc,
-                                        const OUString& rTxt, sal_Int32 nPos,
-                                        OUString& rWord ) const
+OUString SvxAutoCorrect::GetPrevAutoCorrWord(SvxAutoCorrDoc const& rDoc, const OUString& rTxt,
+                                             sal_Int32 nPos)
 {
+    OUString sRet;
     if( !nPos )
-        return false;
+        return sRet;
 
     sal_Int32 nEnde = nPos;
 
@@ -1539,7 +1539,7 @@ bool SvxAutoCorrect::GetPrevAutoCorrWord( SvxAutoCorrDoc const & rDoc,
     if( ( nPos < rTxt.getLength() &&
         !IsWordDelim( rTxt[ nPos ])) ||
         IsWordDelim( rTxt[ --nPos ]))
-        return false;
+        return sRet;
 
     while( nPos && !IsWordDelim( rTxt[ --nPos ]))
         ;
@@ -1552,21 +1552,54 @@ bool SvxAutoCorrect::GetPrevAutoCorrWord( SvxAutoCorrDoc const & rDoc,
 
     while( lcl_IsInAsciiArr( sImplSttSkipChars, rTxt[ nCapLttrPos ]) )
         if( ++nCapLttrPos >= nEnde )
-            return false;
+            return sRet;
 
     if( 3 > nEnde - nCapLttrPos )
-        return false;
+        return sRet;
 
     const LanguageType eLang = GetDocLanguage( rDoc, nCapLttrPos );
 
-    SvxAutoCorrect* pThis = const_cast<SvxAutoCorrect*>(this);
-    CharClass& rCC = pThis->GetCharClass( eLang );
+    CharClass& rCC = GetCharClass( eLang );
 
     if( lcl_IsSymbolChar( rCC, rTxt, nCapLttrPos, nEnde ))
-        return false;
+        return sRet;
 
-    rWord = rTxt.copy( nCapLttrPos, nEnde - nCapLttrPos );
-    return true;
+    sRet = rTxt.copy( nCapLttrPos, nEnde - nCapLttrPos );
+    return sRet;
+}
+
+// static
+std::vector<OUString> SvxAutoCorrect::GetChunkForAutoText(const OUString& rTxt,
+                                                          const sal_Int32 nPos)
+{
+    constexpr sal_Int32 nMinLen = 3;
+    constexpr sal_Int32 nMaxLen = 9;
+    std::vector<OUString> aRes;
+    if (nPos >= nMinLen)
+    {
+        sal_Int32 nBegin = std::max<sal_Int32>(nPos - nMaxLen, 0);
+        // TODO: better detect word boundaries (not only whitespaces, but also e.g. punctuation)
+        if (nBegin > 0 && !IsWordDelim(rTxt[nBegin-1]))
+        {
+            while (nBegin + nMinLen <= nPos && !IsWordDelim(rTxt[nBegin]))
+                ++nBegin;
+        }
+        if (nBegin + nMinLen <= nPos)
+        {
+            OUString sRes = rTxt.copy(nBegin, nPos - nBegin);
+            aRes.push_back(sRes);
+            bool bLastStartedWithDelim = IsWordDelim(sRes[0]);
+            for (sal_Int32 i = 1; i <= sRes.getLength() - nMinLen; ++i)
+            {
+                bool bAdd = bLastStartedWithDelim;
+                bLastStartedWithDelim = IsWordDelim(sRes[i]);
+                bAdd = bAdd || bLastStartedWithDelim;
+                if (bAdd)
+                    aRes.push_back(sRes.copy(i));
+            }
+        }
+    }
+    return aRes;
 }
 
 bool SvxAutoCorrect::CreateLanguageFile( const LanguageTag& rLanguageTag, bool bNewFile )
diff --git a/include/editeng/svxacorr.hxx b/include/editeng/svxacorr.hxx
index 46c2b0e9dadf..69920089b50f 100644
--- a/include/editeng/svxacorr.hxx
+++ b/include/editeng/svxacorr.hxx
@@ -287,8 +287,13 @@ public:
 
     // Return for the autotext expansion the previous word,
     // AutoCorrect - corresponding algorithm
-    bool GetPrevAutoCorrWord( SvxAutoCorrDoc const & rDoc, const OUString& rTxt,
-                                sal_Int32 nPos, OUString& rWord ) const;
+    OUString GetPrevAutoCorrWord(SvxAutoCorrDoc const& rDoc, const OUString& rTxt, sal_Int32 nPos);
+
+    // Returns vector candidates for AutoText name match, starting with the longest string between
+    // 3 and 9 characters long, that is a chunk of text starting with a whitespace or with a word's
+    // first character, and ending at the current cursor position or empty string if no such string
+    // exists
+    static std::vector<OUString> GetChunkForAutoText(const OUString& rTxt, sal_Int32 nPos);
 
     // Search for the words in the replacement table.
     // rText - check in this text the words of the list
@@ -327,6 +332,7 @@ public:
     // Query/Set the current settings of AutoCorrect
     long GetFlags() const                       { return nFlags; }
     SvxSwAutoFormatFlags&   GetSwFlags()    { return aSwFlags;}
+    const SvxSwAutoFormatFlags& GetSwFlags() const { return aSwFlags; }
     bool IsAutoCorrFlag( long nFlag ) const
                                 { return (nFlags & nFlag) != 0; }
     void SetAutoCorrFlag( long nFlag, bool bOn = true );
diff --git a/sw/inc/editsh.hxx b/sw/inc/editsh.hxx
index 01eee9f968ad..59fedaeedaf8 100644
--- a/sw/inc/editsh.hxx
+++ b/sw/inc/editsh.hxx
@@ -824,7 +824,11 @@ public:
     /// Call AutoCorrect
     void AutoCorrect( SvxAutoCorrect& rACorr, bool bInsertMode,
                         sal_Unicode cChar );
-    bool GetPrevAutoCorrWord( SvxAutoCorrect const & rACorr, OUString& rWord );
+    OUString GetPrevAutoCorrWord(SvxAutoCorrect& rACorr);
+
+    // We consider no more than 9 characters before the cursor, and they must not start in the
+    // middle of a word (leading spaces are OK)
+    std::vector<OUString> GetChunkForAutoText();
 
     /// Set our styles according to the respective rules.
     void AutoFormat( const SvxSwAutoFormatFlags* pAFlags );
diff --git a/sw/source/core/edit/edws.cxx b/sw/source/core/edit/edws.cxx
index eae129280c10..ac8775d3d78c 100644
--- a/sw/source/core/edit/edws.cxx
+++ b/sw/source/core/edit/edws.cxx
@@ -276,23 +276,35 @@ void SwEditShell::SetNewDoc()
     GetDoc()->getIDocumentState().SetNewDoc(true);
 }
 
-bool SwEditShell::GetPrevAutoCorrWord( SvxAutoCorrect const & rACorr, OUString& rWord )
+OUString SwEditShell::GetPrevAutoCorrWord(SvxAutoCorrect& rACorr)
 {
     SET_CURR_SHELL( this );
 
-    bool bRet;
+    OUString sRet;
     SwPaM* pCursor = getShellCursor( true );
     const sal_Int32 nPos = pCursor->GetPoint()->nContent.GetIndex();
     SwTextNode* pTNd = pCursor->GetNode().GetTextNode();
     if( pTNd && nPos )
     {
         SwAutoCorrDoc aSwAutoCorrDoc( *this, *pCursor, 0 );
-        bRet = rACorr.GetPrevAutoCorrWord( aSwAutoCorrDoc,
-                                            pTNd->GetText(), nPos, rWord );
+        sRet = rACorr.GetPrevAutoCorrWord(aSwAutoCorrDoc, pTNd->GetText(), nPos);
     }
-    else
-        bRet = false;
-    return bRet;
+    return sRet;
+}
+
+std::vector<OUString> SwEditShell::GetChunkForAutoText()
+{
+    SET_CURR_SHELL(this);
+
+    std::vector<OUString> aRet;
+    SwPaM* pCursor = getShellCursor(true);
+    const sal_Int32 nPos = pCursor->GetPoint()->nContent.GetIndex();
+    SwTextNode* pTNd = pCursor->GetNode().GetTextNode();
+    if (pTNd && nPos)
+    {
+        aRet = SvxAutoCorrect::GetChunkForAutoText(pTNd->GetText(), nPos);
+    }
+    return aRet;
 }
 
 SwAutoCompleteWord& SwEditShell::GetAutoCompleteWords()
diff --git a/sw/source/uibase/docvw/edtwin.cxx b/sw/source/uibase/docvw/edtwin.cxx
index 3e605f318c70..9af4c7a15541 100644
--- a/sw/source/uibase/docvw/edtwin.cxx
+++ b/sw/source/uibase/docvw/edtwin.cxx
@@ -254,12 +254,11 @@ public:
 /// Assists with auto-completion of AutoComplete words and AutoText names.
 struct QuickHelpData
 {
-    /// Strings that at least partially match an input word.
-    std::vector<OUString> m_aHelpStrings;
+    /// Strings that at least partially match an input word, and match length.
+    std::vector<std::pair<OUString, sal_uInt16>> m_aHelpStrings;
     /// Index of the current help string.
     sal_uInt16 nCurArrPos;
-    /// Length of the input word associated with the help data.
-    sal_uInt16 nLen;
+    static constexpr sal_uInt16 nNoPos = std::numeric_limits<sal_uInt16>::max();
 
     /// Help data stores AutoText names rather than AutoComplete words.
     bool m_bIsAutoText;
@@ -277,10 +276,12 @@ struct QuickHelpData
 
     void Move( QuickHelpData& rCpy );
     void ClearContent();
-    void Start( SwWrtShell& rSh, sal_uInt16 nWrdLen );
+    void Start(SwWrtShell& rSh, bool bRestart);
     void Stop( SwWrtShell& rSh );
 
-    bool HasContent() const { return !m_aHelpStrings.empty() && 0 != nLen; }
+    bool HasContent() const { return !m_aHelpStrings.empty() && nCurArrPos != nNoPos; }
+    const OUString& CurStr() const { return m_aHelpStrings[nCurArrPos].first; }
+    sal_uInt16 CurLen() const { return m_aHelpStrings[nCurArrPos].second; }
 
     /// Next help string.
     void Next( bool bEndLess )
@@ -2588,7 +2589,7 @@ KEYINPUT_CHECKTABLE_INSDEL:
                 // replace the word or abbreviation with the auto text
                 rSh.StartUndo( SwUndoId::START );
 
-                OUString sFnd( aTmpQHD.m_aHelpStrings[ aTmpQHD.nCurArrPos ] );
+                OUString sFnd(aTmpQHD.CurStr());
                 if( aTmpQHD.m_bIsAutoText )
                 {
                     SwGlossaryList* pList = ::GetGlossaryList();
@@ -2597,7 +2598,7 @@ KEYINPUT_CHECKTABLE_INSDEL:
                     if(pList->GetShortName( sFnd, sShrtNm, sGroup))
                     {
                         rSh.SttSelect();
-                        rSh.ExtendSelection( false, aTmpQHD.nLen );
+                        rSh.ExtendSelection(false, aTmpQHD.CurLen());
                         SwGlossaryHdl* pGlosHdl = GetView().GetGlosHdl();
                         pGlosHdl->SetCurGroup(sGroup, true);
                         pGlosHdl->InsertGlossary( sShrtNm);
@@ -2606,7 +2607,7 @@ KEYINPUT_CHECKTABLE_INSDEL:
                 }
                 else
                 {
-                    sFnd = sFnd.copy( aTmpQHD.nLen );
+                    sFnd = sFnd.copy(aTmpQHD.CurLen());
                     rSh.Insert( sFnd );
                     m_pQuickHlpData->m_bAppendSpace = !pACorr ||
                             pACorr->GetSwFlags().bAutoCmpltAppendBlanc;
@@ -2617,7 +2618,7 @@ KEYINPUT_CHECKTABLE_INSDEL:
 
             case SwKeyState::NextPrevGlossary:
                 m_pQuickHlpData->Move( aTmpQHD );
-                m_pQuickHlpData->Start( rSh, USHRT_MAX );
+                m_pQuickHlpData->Start(rSh, false);
                 break;
 
             case SwKeyState::EditFormula:
@@ -2693,13 +2694,12 @@ KEYINPUT_CHECKTABLE_INSDEL:
         g_bFlushCharBuffer = bSave;
 
         // maybe show Tip-Help
-        OUString sWord;
-        if( bNormalChar && pACfg && pACorr &&
-            ( pACfg->IsAutoTextTip() ||
-              pACorr->GetSwFlags().bAutoCompleteWords ) &&
-            rSh.GetPrevAutoCorrWord( *pACorr, sWord ) )
+        if (bNormalChar)
         {
-            ShowAutoTextCorrectQuickHelp(sWord, pACfg, pACorr);
+            const bool bAutoTextShown
+                = pACfg->IsAutoTextTip() && ShowAutoText(rSh.GetChunkForAutoText());
+            if (!bAutoTextShown && pACorr && pACorr->GetSwFlags().bAutoCompleteWords)
+                ShowAutoCorrectQuickHelp(rSh.GetPrevAutoCorrWord(*pACorr), *pACorr);
         }
     }
 
@@ -5437,23 +5437,18 @@ void SwEditWin::Command( const CommandEvent& rCEvt )
                     rSh.SetExtTextInputData( *pData );
                 }
             }
-                uno::Reference< frame::XDispatchRecorder > xRecorder =
-                        m_rView.GetViewFrame()->GetBindings().GetRecorder();
-                if(!xRecorder.is())
+            uno::Reference< frame::XDispatchRecorder > xRecorder =
+                    m_rView.GetViewFrame()->GetBindings().GetRecorder();
+            if(!xRecorder.is())
+            {
+                SvxAutoCorrCfg& rACfg = SvxAutoCorrCfg::Get();
+                if (!rACfg.IsAutoTextTip() || !ShowAutoText(rSh.GetChunkForAutoText()))
                 {
-                    SvxAutoCorrCfg& rACfg = SvxAutoCorrCfg::Get();
                     SvxAutoCorrect* pACorr = rACfg.GetAutoCorrect();
-                    if( pACorr &&
-                        // If autocompletion required...
-                        ( rACfg.IsAutoTextTip() ||
-                          pACorr->GetSwFlags().bAutoCompleteWords ) &&
-                        // ... and extraction of last word from text input was successful...
-                        rSh.GetPrevAutoCorrWord( *pACorr, sWord ) )
-                    {
-                        // ... request for auto completion help to be shown.
-                        ShowAutoTextCorrectQuickHelp(sWord, &rACfg, pACorr, true);
-                    }
+                    if (pACorr && pACorr->GetSwFlags().bAutoCompleteWords)
+                        ShowAutoCorrectQuickHelp(rSh.GetPrevAutoCorrWord(*pACorr), *pACorr);
                 }
+            }
         }
     }
     break;
@@ -5929,7 +5924,6 @@ void QuickHelpData::Move( QuickHelpData& rCpy )
     m_aHelpStrings.swap( rCpy.m_aHelpStrings );
 
     m_bIsDisplayed = rCpy.m_bIsDisplayed;
-    nLen = rCpy.nLen;
     nCurArrPos = rCpy.nCurArrPos;
     m_bAppendSpace = rCpy.m_bAppendSpace;
     m_bIsTip = rCpy.m_bIsTip;
@@ -5938,7 +5932,7 @@ void QuickHelpData::Move( QuickHelpData& rCpy )
 
 void QuickHelpData::ClearContent()
 {
-    nLen = nCurArrPos = 0;
+    nCurArrPos = nNoPos;
     m_bIsDisplayed = m_bAppendSpace = false;
     nTipId = 0;
     m_aHelpStrings.clear();
@@ -5946,11 +5940,10 @@ void QuickHelpData::ClearContent()
     m_bIsAutoText = true;
 }
 
-void QuickHelpData::Start( SwWrtShell& rSh, sal_uInt16 nWrdLen )
+void QuickHelpData::Start(SwWrtShell& rSh, const bool bRestart)
 {
-    if( USHRT_MAX != nWrdLen )
+    if (bRestart)
     {
-        nLen = nWrdLen;
         nCurArrPos = 0;
     }
     m_bIsDisplayed = true;
@@ -5962,13 +5955,13 @@ void QuickHelpData::Start( SwWrtShell& rSh, sal_uInt16 nWrdLen )
                     rSh.GetCharRect().Pos() )));
         aPt.Y() -= 3;
         nTipId = Help::ShowPopover(&rWin, tools::Rectangle( aPt, Size( 1, 1 )),
-                        m_aHelpStrings[ nCurArrPos ],
+                        CurStr(),
                         QuickHelpFlags::Left | QuickHelpFlags::Bottom);
     }
     else
     {
-        OUString sStr( m_aHelpStrings[ nCurArrPos ] );
-        sStr = sStr.copy( nLen );
+        OUString sStr(CurStr());
+        sStr = sStr.copy(CurLen());
         sal_uInt16 nL = sStr.getLength();
         const ExtTextInputAttr nVal = ExtTextInputAttr::DottedUnderline |
                                 ExtTextInputAttr::Highlight;
@@ -6047,23 +6040,24 @@ void QuickHelpData::FillStrArr( SwWrtShell const & rSh, const OUString& rWord )
             if( rStr.getLength() > rWord.getLength() &&
                 rCC.lowercase( rStr, 0, rWord.getLength() ) == sWordLower )
             {
+                OUString sStr;
+
                 //fdo#61251 if it's an exact match, ensure unchanged replacement
                 //exists as a candidate
                 if (rStr.startsWith(rWord))
-                    m_aHelpStrings.push_back(rStr);
+                    m_aHelpStrings.emplace_back(rStr, rWord.getLength());
+                else
+                    sStr = rStr; // to be added if no case conversion is performed below
 
                 if ( aWordCase == CASE_LOWER )
-                    m_aHelpStrings.push_back( rCC.lowercase( rStr ) );
+                    sStr = rCC.lowercase(rStr);
                 else if ( aWordCase == CASE_SENTENCE )
-                {
-                    OUString sTmp = rCC.lowercase( rStr );
-                    sTmp = sTmp.replaceAt( 0, 1, OUString(rStr[0]) );
-                    m_aHelpStrings.push_back( sTmp );
-                }
+                    sStr = rCC.lowercase(rStr).replaceAt(0, 1, OUString(rStr[0]));
                 else if ( aWordCase == CASE_UPPER )
-                    m_aHelpStrings.push_back( rCC.uppercase( rStr ) );
-                else // CASE_OTHER - use retrieved capitalization
-                    m_aHelpStrings.push_back( rStr );
+                    sStr = rCC.uppercase(rStr);
+
+                if (!sStr.isEmpty())
+                    m_aHelpStrings.emplace_back(sStr, rWord.getLength());
             }
         }
         // Data for second loop iteration
@@ -6091,7 +6085,7 @@ void QuickHelpData::FillStrArr( SwWrtShell const & rSh, const OUString& rWord )
         // only for "201" or "2016-..." (to avoid unintentional text
         // insertion at line ending, for example typing "30 January 2016")
         if (rWord.getLength() != 4 && rStrToday.startsWith(rWord))
-            m_aHelpStrings.push_back(rStrToday);
+            m_aHelpStrings.emplace_back(rStrToday, rWord.getLength());
     }
 
     // Add matching words from AutoCompleteWord list
@@ -6108,22 +6102,25 @@ void QuickHelpData::FillStrArr( SwWrtShell const & rSh, const OUString& rWord )
             if (!rStrToday.isEmpty() && aCompletedString.startsWith(rWord))
                 continue;
 
+            OUString sStr;
+
             //fdo#61251 if it's an exact match, ensure unchanged replacement
             //exists as a candidate
             if (aCompletedString.startsWith(rWord))
-                m_aHelpStrings.push_back(aCompletedString);
-            if ( aWordCase == CASE_LOWER )
-                m_aHelpStrings.push_back( rCC.lowercase( aCompletedString ) );
-            else if ( aWordCase == CASE_SENTENCE )
-            {
-                OUString sTmp = rCC.lowercase( aCompletedString );
-                sTmp = sTmp.replaceAt( 0, 1, OUString(aCompletedString[0]) );
-                m_aHelpStrings.push_back( sTmp );
-            }
-            else if ( aWordCase == CASE_UPPER )
-                m_aHelpStrings.push_back( rCC.uppercase( aCompletedString ) );
-            else // CASE_OTHER - use retrieved capitalization
-                m_aHelpStrings.push_back( aCompletedString );
+                m_aHelpStrings.emplace_back(aCompletedString, rWord.getLength());
+            else
+                sStr = aCompletedString; // to be added if no case conversion is performed below
+
+            if (aWordCase == CASE_LOWER)
+                sStr = rCC.lowercase(aCompletedString);
+            else if (aWordCase == CASE_SENTENCE)
+                sStr = rCC.lowercase(aCompletedString)
+                           .replaceAt(0, 1, OUString(aCompletedString[0]));
+            else if (aWordCase == CASE_UPPER)
+                sStr = rCC.uppercase(aCompletedString);
+
+            if (!sStr.isEmpty())
+                m_aHelpStrings.emplace_back(aCompletedString, rWord.getLength());
         }
     }
 
@@ -6140,15 +6137,16 @@ public:
     {
     }
 
-    bool operator()(const OUString& s1, const OUString& s2) const
+    bool operator()(const std::pair<OUString, sal_uInt16>& s1,
+                    const std::pair<OUString, sal_uInt16>& s2) const
     {
-        int nRet = s1.compareToIgnoreAsciiCase(s2);
+        int nRet = s1.first.compareToIgnoreAsciiCase(s2.first);
         if (nRet == 0)
         {
             //fdo#61251 sort stuff that starts with the exact rOrigWord before
             //another ignore-case candidate
-            int n1StartsWithOrig = s1.startsWith(m_rOrigWord) ? 0 : 1;
-            int n2StartsWithOrig = s2.startsWith(m_rOrigWord) ? 0 : 1;
+            int n1StartsWithOrig = s1.first.startsWith(m_rOrigWord) ? 0 : 1;
+            int n2StartsWithOrig = s2.first.startsWith(m_rOrigWord) ? 0 : 1;
             return n1StartsWithOrig < n2StartsWithOrig;
         }
         return nRet < 0;
@@ -6157,9 +6155,10 @@ public:
 
 struct EqualIgnoreCaseAscii
 {
-    bool operator()(const OUString& s1, const OUString& s2) const
+    bool operator()(const std::pair<OUString, sal_uInt16>& s1,
+                    const std::pair<OUString, sal_uInt16>& s2) const
     {
-        return s1.equalsIgnoreAsciiCase(s2);
+        return s1.first.equalsIgnoreAsciiCase(s2.first);
     }
 };
 
@@ -6172,33 +6171,74 @@ void QuickHelpData::SortAndFilter(const OUString &rOrigWord)
                m_aHelpStrings.end(),
                CompareIgnoreCaseAsciiFavorExact(rOrigWord) );
 
-    std::vector<OUString>::iterator it = std::unique( m_aHelpStrings.begin(),
-                                                    m_aHelpStrings.end(),
-                                                    EqualIgnoreCaseAscii() );
+    const auto& it
+        = std::unique(m_aHelpStrings.begin(), m_aHelpStrings.end(), EqualIgnoreCaseAscii());
     m_aHelpStrings.erase( it, m_aHelpStrings.end() );
 
     nCurArrPos = 0;
 }
 
-void SwEditWin::ShowAutoTextCorrectQuickHelp(
-        const OUString& rWord, SvxAutoCorrCfg const * pACfg, SvxAutoCorrect* pACorr,
-        bool bFromIME )
+// For a given chunk of typed text between 3 and 9 characters long that may start at a word boundary
+// or in a whitespace and may include whitespaces, SwEditShell::GetChunkForAutoTextcreates a list of
+// possible candidates for long AutoText names. Let's say, we have typed text "lorem ipsum  dr f";
+// and the cursor is right after the "f". SwEditShell::GetChunkForAutoText would take "  dr f",
+// since it's the longest chunk to the left of the cursor no longer than 9 characters, not starting
+// in the middle of a word. Then it would create this list from it (in this order, longest first):
+//     "  dr f"
+//      " dr f"
+//       "dr f"
+// It cannot add "r f", because it starts in the middle of the word "dr"; also it cannot give " f",
+// because it's only 2 characters long.
+// Now the result of SwEditShell::GetChunkForAutoText is passed here to SwEditWin::ShowAutoText, and
+// then to SwGlossaryList::HasLongName, where all existing autotext entries' long names are tested
+// if they start with one of the list elements. The matches are sorted according the position of the
+// candidate that matched first, then alhpabetically inside the group of suggestions for a given
+// candidate. Say, if we have these AutoText entry long names:
+//    "Dr Frodo"
+//    "Dr Credo"
+//    "Or Bilbo"
+//    "dr foo"
+//    "  Dr Fuzz"
+//    " dr Faust"
+// the resulting list would be:
+//    "  Dr Fuzz" -> matches the first (longest) item in the candidates list
+//    " dr Faust" -> matches the second candidate item
+//    "Dr Foo" -> first item of the two matching the third candidate; alphabetically sorted
+//    "Dr Frodo" -> second item of the two matching the third candidate; alphabetically sorted
+// Each of the resulting suggestions knows the length of the candidate it replaces, so accepting the
+// first suggestion would replace 6 characters before cursor, while tabbing to and accepting the
+// last suggestion would replace only 4 characters to the left of cursor.
+bool SwEditWin::ShowAutoText(const std::vector<OUString>& rChunkCandidates)
 {
-    SwWrtShell& rSh = m_rView.GetWrtShell();
     m_pQuickHlpData->ClearContent();
-    if( pACfg->IsAutoTextTip() )
+    if (!rChunkCandidates.empty())
     {
         SwGlossaryList* pList = ::GetGlossaryList();
-        pList->HasLongName( rWord, &m_pQuickHlpData->m_aHelpStrings );
+        pList->HasLongName(rChunkCandidates, m_pQuickHlpData->m_aHelpStrings);
+    }
+
+    if (!m_pQuickHlpData->m_aHelpStrings.empty())
+    {
+        m_pQuickHlpData->Start(m_rView.GetWrtShell(), true);
     }
+    return !m_pQuickHlpData->m_aHelpStrings.empty();
+}
+
+void SwEditWin::ShowAutoCorrectQuickHelp(
+        const OUString& rWord, SvxAutoCorrect& rACorr,
+        bool bFromIME )
+{
+    if (rWord.isEmpty())
+        return;
+    SwWrtShell& rSh = m_rView.GetWrtShell();
+    m_pQuickHlpData->ClearContent();
 
     if( m_pQuickHlpData->m_aHelpStrings.empty() &&
-        pACorr->GetSwFlags().bAutoCompleteWords )
+        rACorr.GetSwFlags().bAutoCompleteWords )
     {
         m_pQuickHlpData->m_bIsAutoText = false;
         m_pQuickHlpData->m_bIsTip = bFromIME ||
-                    !pACorr ||
-                    pACorr->GetSwFlags().bAutoCmpltShowAsTip;
+                    rACorr.GetSwFlags().bAutoCmpltShowAsTip;
 
         // Get the necessary data to show help text.
         m_pQuickHlpData->FillStrArr( rSh, rWord );
@@ -6207,7 +6247,7 @@ void SwEditWin::ShowAutoTextCorrectQuickHelp(
     if( !m_pQuickHlpData->m_aHelpStrings.empty() )
     {
         m_pQuickHlpData->SortAndFilter(rWord);
-        m_pQuickHlpData->Start( rSh, rWord.getLength() );
+        m_pQuickHlpData->Start(rSh, true);
     }
 }
 
diff --git a/sw/source/uibase/inc/edtwin.hxx b/sw/source/uibase/inc/edtwin.hxx
index 71e3f9a7b34d..3822a3ca6856 100644
--- a/sw/source/uibase/inc/edtwin.hxx
+++ b/sw/source/uibase/inc/edtwin.hxx
@@ -193,8 +193,9 @@ class SwEditWin final : public vcl::Window,
     virtual OUString GetSurroundingText() const override;
     virtual Selection GetSurroundingTextSelection() const override;
 
-    void    ShowAutoTextCorrectQuickHelp( const OUString& rWord, SvxAutoCorrCfg const * pACfg,
-                                SvxAutoCorrect* pACorr, bool bFromIME = false );
+    void ShowAutoCorrectQuickHelp(const OUString& rWord, SvxAutoCorrect& rACorr,
+                                  bool bFromIME = false);
+    bool ShowAutoText(const std::vector<OUString>& rChunkCandidates);
 
     /// Returns true if in header/footer area, or in the header/footer control.
     bool    IsInHeaderFooter( const Point &rDocPt, FrameControlType &rControl ) const;
diff --git a/sw/source/uibase/inc/gloslst.hxx b/sw/source/uibase/inc/gloslst.hxx
index b25e63fb2f48..e60710b5b306 100644
--- a/sw/source/uibase/inc/gloslst.hxx
+++ b/sw/source/uibase/inc/gloslst.hxx
@@ -56,7 +56,8 @@ public:
         SwGlossaryList();
         virtual ~SwGlossaryList() override;
 
-    void            HasLongName(const OUString& rBegin, std::vector<OUString> *pLongNames);
+    void            HasLongName(const std::vector<OUString>& rBeginCandidates,
+                                std::vector<std::pair<OUString, sal_uInt16>>& rLongNames);
     bool            GetShortName(const OUString& rLongName,
                                        OUString& rShortName, OUString& rGroupName );
 
diff --git a/sw/source/uibase/utlui/gloslst.cxx b/sw/source/uibase/utlui/gloslst.cxx
index 96d7212643a1..e327b719c664 100644
--- a/sw/source/uibase/utlui/gloslst.cxx
+++ b/sw/source/uibase/utlui/gloslst.cxx
@@ -388,31 +388,70 @@ void SwGlossaryList::FillGroup(AutoTextGroup* pGroup, SwGlossaries* pGlossaries)
 // Give back all (not exceeding FIND_MAX_GLOS) found modules
 // with matching beginning.
 
-void SwGlossaryList::HasLongName(const OUString& rBegin, std::vector<OUString> *pLongNames)
+void SwGlossaryList::HasLongName(const std::vector<OUString>& rBeginCandidates,
+                                 std::vector<std::pair<OUString, sal_uInt16>>& rLongNames)
 {
     if(!bFilled)
         Update();
-    sal_uInt16 nFound = 0;
-    const size_t nCount = aGroupArr.size();
-    sal_Int32 nBeginLen = rBegin.getLength();
     const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore();
+    // We store results for all candidate words in separate lists, so that later
+    // we can sort them according to the candidate position
+    std::vector<std::vector<OUString>> aResults(rBeginCandidates.size());
 
-    for(size_t i = 0; i < nCount; ++i)
+    // We can't break after FIND_MAX_GLOS items found, since first items may have ended up in
+    // lower-priority lists, and those from higher-priority lists are yet to come. So process all.
+    for(size_t i = 0; i < aGroupArr.size(); ++i)
     {
         AutoTextGroup* pGroup = aGroupArr[i];
+        sal_Int32 nIdx{ 0 };
         for(sal_uInt16 j = 0; j < pGroup->nCount; j++)
         {
-            OUString sBlock = pGroup->sLongNames.getToken(j, STRING_DELIM);
-            if( nBeginLen + 1 < sBlock.getLength() &&
-                rSCmp.isEqual( sBlock.copy(0, nBeginLen), rBegin ))
+            OUString sBlock = pGroup->sLongNames.getToken(0, STRING_DELIM, nIdx);
+            for (size_t k = 0; k < rBeginCandidates.size(); ++k)
             {
-                pLongNames->push_back( sBlock );
-                nFound++;
-                if(FIND_MAX_GLOS == nFound)
-                    break;
+                const OUString& s = rBeginCandidates[k];
+                if (s.getLength() + 1 < sBlock.getLength()
+                    && rSCmp.isEqual(sBlock.copy(0, s.getLength()), s))
+                {
+                    aResults[k].push_back(sBlock);
+                }
             }
         }
     }
+
+    std::vector<std::pair<OUString, sal_uInt16>> aAllResults;
+    // Sort and concatenate all result lists. See QuickHelpData::SortAndFilter
+    for (size_t i = 0; i < rBeginCandidates.size(); ++i)
+    {
+        std::sort(aResults[i].begin(), aResults[i].end(),
+                  [origWord = rBeginCandidates[i]](const OUString& s1, const OUString& s2) {
+                      int nRet = s1.compareToIgnoreAsciiCase(s2);
+                      if (nRet == 0)
+                      {
+                          // fdo#61251 sort stuff that starts with the exact rOrigWord before
+                          // another ignore-case candidate
+                          int n1StartsWithOrig = s1.startsWith(origWord) ? 0 : 1;
+                          int n2StartsWithOrig = s2.startsWith(origWord) ? 0 : 1;
+                          return n1StartsWithOrig < n2StartsWithOrig;
+                      }
+                      return nRet < 0;
+                  });
+        // All suggestions must be accompanied with length of the text they would replace
+        std::transform(aResults[i].begin(), aResults[i].end(), std::back_inserter(aAllResults),
+                       [nLen = sal_uInt16(rBeginCandidates[i].getLength())](const OUString& s) {
+                           return std::make_pair(s, nLen);
+                       });
+    }
+
+    const auto& it = std::unique(
+        aAllResults.begin(), aAllResults.end(),
+        [](const std::pair<OUString, sal_uInt16>& s1, const std::pair<OUString, sal_uInt16>& s2) {
+            return s1.first.equalsIgnoreAsciiCase(s2.first);
+        });
+    if (const auto nCount = std::min<size_t>(std::distance(aAllResults.begin(), it), FIND_MAX_GLOS))
+    {
+        rLongNames.insert(rLongNames.end(), aAllResults.begin(), aAllResults.begin() + nCount);
+    }
 }
 
 void    SwGlossaryList::ClearGroups()
commit 7ab86555c5bca3cdc3aee6ac5fcc348956f444f0
Author:     Mike Kaganski <mike.kaganski at collabora.com>
AuthorDate: Tue Oct 8 05:08:36 2019 +0200
Commit:     Mike Kaganski <mike.kaganski at collabora.com>
CommitDate: Wed Oct 9 13:11:02 2019 +0200

    tdf#126589: only consider text to the left of cursor as AutoText short name
    
    Given a text "This is a dtfoo bar", and cursor inside "dtfoo" between "t"
    and "f", invoking AutoText function (F3) currently considers the whole word
    "dtfoo" as autotext short name. This changes it to only consider the part of
    the word to the left of cursor, i.e. only "dt" in the example. This removes
    the requirement to have a word boundary after the autotext short name.
    
    Change-Id: I6ba28e63fe25664131b7e03dda39ae997dffa464
    Reviewed-on: https://gerrit.libreoffice.org/80419
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kaganski at collabora.com>
    (cherry picked from commit 810cddee6d2ef0f4057337d699a1a55323faa1ba)
    Reviewed-on: https://gerrit.libreoffice.org/80420
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    (cherry picked from commit 9b264707ec9da318da9344a139b810755020aa69)
    Reviewed-on: https://gerrit.libreoffice.org/80525

diff --git a/sw/source/uibase/dochdl/gloshdl.cxx b/sw/source/uibase/dochdl/gloshdl.cxx
index 85964f4c1f0c..e2585ff6cc03 100644
--- a/sw/source/uibase/dochdl/gloshdl.cxx
+++ b/sw/source/uibase/dochdl/gloshdl.cxx
@@ -374,8 +374,9 @@ bool SwGlossaryHdl::ExpandGlossary()
             pWrtShell->LeaveBlockMode();
         else if(pWrtShell->IsExtMode())
             pWrtShell->LeaveExtMode();
-        // select word
-        pWrtShell->SelNearestWrd();
+        // select word (tdf#126589: part to the left of cursor)
+        if (pWrtShell->IsInWord() || pWrtShell->IsEndWrd())
+            pWrtShell->PrvWrd(true);
             // ask for word
         if(pWrtShell->IsSelection())
             aShortName = pWrtShell->GetSelText();


More information about the Libreoffice-commits mailing list