From bfd4dd2c2cc3dfff5a5d3a32a5c9c4edf975f484 Mon Sep 17 00:00:00 2001 From: Mike Kaganski Date: Mon, 7 Oct 2019 17:46:36 +0300 Subject: 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 Reviewed-on: https://gerrit.libreoffice.org/80495 Tested-by: Jenkins CollaboraOffice --- sw/source/core/edit/edws.cxx | 27 +++-- 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 | 64 +++++++++--- 5 files changed, 197 insertions(+), 104 deletions(-) (limited to 'sw/source') diff --git a/sw/source/core/edit/edws.cxx b/sw/source/core/edit/edws.cxx index 1b2e720d1821..9ed3970fc164 100644 --- a/sw/source/core/edit/edws.cxx +++ b/sw/source/core/edit/edws.cxx @@ -282,11 +282,11 @@ 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 ); SwTextNode* pTNd = pCursor->GetNode().GetTextNode(); if (pTNd) @@ -294,12 +294,25 @@ bool SwEditShell::GetPrevAutoCorrWord( SvxAutoCorrect const & rACorr, OUString& SwAutoCorrDoc aSwAutoCorrDoc( *this, *pCursor, 0 ); SwTextFrame const*const pFrame(static_cast(pTNd->getLayoutFrame(GetLayout()))); TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint())); - bRet = rACorr.GetPrevAutoCorrWord( aSwAutoCorrDoc, - pFrame->GetText(), sal_Int32(nPos), rWord ); + sRet = rACorr.GetPrevAutoCorrWord(aSwAutoCorrDoc, pFrame->GetText(), sal_Int32(nPos)); } - else - bRet = false; - return bRet; + return sRet; +} + +std::vector SwEditShell::GetChunkForAutoText() +{ + SET_CURR_SHELL(this); + + std::vector aRet; + SwPaM* pCursor = getShellCursor(true); + SwTextNode* pTNd = pCursor->GetNode().GetTextNode(); + if (pTNd) + { + const auto pFrame = static_cast(pTNd->getLayoutFrame(GetLayout())); + TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint())); + aRet = SvxAutoCorrect::GetChunkForAutoText(pFrame->GetText(), sal_Int32(nPos)); + } + return aRet; } SwAutoCompleteWord& SwEditShell::GetAutoCompleteWords() diff --git a/sw/source/uibase/docvw/edtwin.cxx b/sw/source/uibase/docvw/edtwin.cxx index c7260dd99a75..ddf7c6a4902d 100644 --- a/sw/source/uibase/docvw/edtwin.cxx +++ b/sw/source/uibase/docvw/edtwin.cxx @@ -261,12 +261,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 m_aHelpStrings; + /// Strings that at least partially match an input word, and match length. + std::vector> 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::max(); /// Help data stores AutoText names rather than AutoComplete words. bool m_bIsAutoText; @@ -284,10 +283,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 ) @@ -2560,7 +2561,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(); @@ -2569,7 +2570,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); @@ -2578,7 +2579,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; @@ -2589,7 +2590,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: @@ -2656,13 +2657,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); } } @@ -5369,23 +5369,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; @@ -5852,7 +5847,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; @@ -5861,7 +5855,7 @@ void QuickHelpData::Move( QuickHelpData& rCpy ) void QuickHelpData::ClearContent() { - nLen = nCurArrPos = 0; + nCurArrPos = nNoPos; m_bIsDisplayed = m_bAppendSpace = false; nTipId = nullptr; m_aHelpStrings.clear(); @@ -5869,11 +5863,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; @@ -5885,13 +5878,13 @@ void QuickHelpData::Start( SwWrtShell& rSh, sal_uInt16 nWrdLen ) rSh.GetCharRect().Pos() ))); aPt.AdjustY( -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; @@ -5970,23 +5963,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 @@ -6014,7 +6008,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 @@ -6031,22 +6025,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()); } } @@ -6063,15 +6060,16 @@ public: { } - bool operator()(const OUString& s1, const OUString& s2) const + bool operator()(const std::pair& s1, + const std::pair& 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; @@ -6080,9 +6078,10 @@ public: struct EqualIgnoreCaseAscii { - bool operator()(const OUString& s1, const OUString& s2) const + bool operator()(const std::pair& s1, + const std::pair& s2) const { - return s1.equalsIgnoreAsciiCase(s2); + return s1.first.equalsIgnoreAsciiCase(s2.first); } }; @@ -6095,33 +6094,74 @@ void QuickHelpData::SortAndFilter(const OUString &rOrigWord) m_aHelpStrings.end(), CompareIgnoreCaseAsciiFavorExact(rOrigWord) ); - std::vector::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& 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 ); @@ -6130,7 +6170,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 abb7f4cd68a4..8f73d605d9bd 100644 --- a/sw/source/uibase/inc/edtwin.hxx +++ b/sw/source/uibase/inc/edtwin.hxx @@ -188,8 +188,9 @@ class SW_DLLPUBLIC 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& 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 bf6b8637d33b..164a083e7920 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 *pLongNames); + void HasLongName(const std::vector& rBeginCandidates, + std::vector>& 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 c8f1102813ce..d02b6d7698df 100644 --- a/sw/source/uibase/utlui/gloslst.cxx +++ b/sw/source/uibase/utlui/gloslst.cxx @@ -370,31 +370,69 @@ 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 *pLongNames) +void SwGlossaryList::HasLongName(const std::vector& rBeginCandidates, + std::vector>& 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> 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 (const auto& pGroup : aGroupArr) { - AutoTextGroup* pGroup = aGroupArr[i].get(); + 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 i = 0; i < rBeginCandidates.size(); ++i) { - pLongNames->push_back( sBlock ); - nFound++; - if(FIND_MAX_GLOS == nFound) - break; + const OUString& s = rBeginCandidates[i]; + if (s.getLength() + 1 < sBlock.getLength() + && rSCmp.isEqual(sBlock.copy(0, s.getLength()), s)) + { + aResults[i].push_back(sBlock); + } } } } + + std::vector> 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& s1, const std::pair& s2) { + return s1.first.equalsIgnoreAsciiCase(s2.first); + }); + if (const auto nCount = std::min(std::distance(aAllResults.begin(), it), FIND_MAX_GLOS)) + { + rLongNames.insert(rLongNames.end(), aAllResults.begin(), aAllResults.begin() + nCount); + } } void SwGlossaryList::ClearGroups() -- cgit