sc/qa/unit/tiledrendering/tiledrendering.cxx |   14 +--
 sc/source/ui/app/inputhdl.cxx                |  101 ++++++++++++++++++++++-----
 sc/source/ui/inc/inputhdl.hxx                |    6 +
 sc/source/ui/view/tabvwsh4.cxx               |    5 +
 4 files changed, 101 insertions(+), 25 deletions(-)

New commits:
commit 4cfa840ecaa5a094a32a60a60b295858980109df
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Wed Oct 13 20:24:50 2021 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon Oct 18 13:20:02 2021 +0200

    tdf#145198: Bash like autocompletion for Calc autoinput
    
    Refer ESC minutes section "Calc auto-complete behavior changes" at
    https://lists.freedesktop.org/archives/libreoffice/2021-October/087911.html
    for context.
    
    Consider an example of the following data in a column:
    
    ABCD123xyz
    ABCD345qwel
    ABCD123pqr
    ABCD123xyz
    PQR
    
    1. When user types A, it will show the partial suggestion BCD.
    
    2. User can accept the suggestion with the right arrow key and then the
       cursor will be placed after the letter D, waiting for more input. User
       can choose to not accept the suggestion either by typing more or by
       ending the edit mode by pressing Esc key.
    
    3. If the user accepts the suggestion BCD by right arrow key, and
       types 1, it will show a partial suggestion of 23. User can accept
       this by pressing the right arrow key.
    
    4. If the user accepts the suggestion in the 3rd step and types x it will
       show the final suggestion yz. Again user can choose to accept or decline
       the suggestion as mentioned in the 1st step.
    
    The tiledrendering test ScTiledRenderingTest::testAutoInputExactMatch()
    is amended to match the new behaviour.
    
    Change-Id: Ib2cfc16af71483790384e70eb7332f864cf744c5
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/123578
    Tested-by: Jenkins
    Reviewed-by: Dennis Francis <dennis.fran...@collabora.com>

diff --git a/sc/qa/unit/tiledrendering/tiledrendering.cxx 
b/sc/qa/unit/tiledrendering/tiledrendering.cxx
index 3cfde4952b16..ac0ca40d241d 100644
--- a/sc/qa/unit/tiledrendering/tiledrendering.cxx
+++ b/sc/qa/unit/tiledrendering/tiledrendering.cxx
@@ -2712,12 +2712,12 @@ void ScTiledRenderingTest::testAutoInputExactMatch()
 
     ScAddress aA8(0, 7, 0);
     lcl_typeCharsInCell("S", aA8.Col(), aA8.Row(), pView, pModelObj); // Type 
"S" in A8
-    // Should not autocomplete as there are multiple matches starting with "S".
-    CPPUNIT_ASSERT_EQUAL_MESSAGE("1: A8 should have just S (should not 
autocomplete)", OUString("S"), pDoc->GetString(aA8));
+    // Should show the partial completion "i".
+    CPPUNIT_ASSERT_EQUAL_MESSAGE("1: A8 should have partial completion Si", 
OUString("Si"), pDoc->GetString(aA8));
 
     lcl_typeCharsInCell("Si", aA8.Col(), aA8.Row(), pView, pModelObj); // Type 
"Si" in A8
-    // Should not autocomplete as there are multiple matches starting with 
"Si".
-    CPPUNIT_ASSERT_EQUAL_MESSAGE("2: A8 should not autocomplete", 
OUString("Si"), pDoc->GetString(aA8));
+    // Should not show any suggestions.
+    CPPUNIT_ASSERT_EQUAL_MESSAGE("2: A8 should not show suggestions", 
OUString("Si"), pDoc->GetString(aA8));
 
     lcl_typeCharsInCell("Sim", aA8.Col(), aA8.Row(), pView, pModelObj); // 
Type "Sim" in A8
     // Should autocomplete to "Simple" which is the only match.
@@ -2727,9 +2727,9 @@ void ScTiledRenderingTest::testAutoInputExactMatch()
     // Should autocomplete to "Sing" which is the only match.
     CPPUNIT_ASSERT_EQUAL_MESSAGE("4: A8 should autocomplete", 
OUString("Sing"), pDoc->GetString(aA8));
 
-    lcl_typeCharsInCell("Cas", aA8.Col(), aA8.Row(), pView, pModelObj); // 
Type "Cas" in A8
-    // Should not autocomplete as there are multiple matches starting with 
"Cas".
-    CPPUNIT_ASSERT_EQUAL_MESSAGE("5: A8 should not autocomplete", 
OUString("Cas"), pDoc->GetString(aA8));
+    lcl_typeCharsInCell("C", aA8.Col(), aA8.Row(), pView, pModelObj); // Type 
"C" in A8
+    // Should show the partial completion "as".
+    CPPUNIT_ASSERT_EQUAL_MESSAGE("5: A8 should have partial completion Cas", 
OUString("Cas"), pDoc->GetString(aA8));
 
     lcl_typeCharsInCell("Cast", aA8.Col(), aA8.Row(), pView, pModelObj); // 
Type "Cast" in A8
     // Should autocomplete to "Castle" which is the only match.
diff --git a/sc/source/ui/app/inputhdl.cxx b/sc/source/ui/app/inputhdl.cxx
index 9c553cec8614..97c86b34b00d 100644
--- a/sc/source/ui/app/inputhdl.cxx
+++ b/sc/source/ui/app/inputhdl.cxx
@@ -162,15 +162,42 @@ OUString getExactMatch(const ScTypedCaseStrSet& rDataSet, 
const OUString& rStrin
     return rString;
 }
 
+// This assumes that rResults is a sorted ring w.r.t 
ScTypedStrData::LessCaseInsensitive() or
+// in the reverse direction, whose origin is specified by nRingOrigin.
+sal_Int32 getLongestCommonPrefixLength(const std::vector<OUString>& rResults, 
const OUString& rUserEntry, sal_Int32 nRingOrigin)
+{
+    sal_Int32 nResults = rResults.size();
+    if (!nResults)
+        return 0;
+
+    if (nResults == 1)
+        return rResults[0].getLength();
+
+    sal_Int32 nMinLen = rUserEntry.getLength();
+    sal_Int32 nLastIdx = nRingOrigin ? nRingOrigin - 1 : nResults - 1;
+    const OUString& rFirst = rResults[nRingOrigin];
+    const OUString& rLast = rResults[nLastIdx];
+    const sal_Int32 nMaxLen = std::min(rFirst.getLength(), rLast.getLength());
+
+    for (sal_Int32 nLen = nMaxLen; nLen > nMinLen; --nLen)
+    {
+        if (ScGlobal::GetTransliteration().isMatch(rFirst.copy(0, nLen), 
rLast))
+            return nLen;
+    }
+
+    return nMinLen;
+}
+
 ScTypedCaseStrSet::const_iterator findTextAll(
     const ScTypedCaseStrSet& rDataSet, ScTypedCaseStrSet::const_iterator const 
& itPos,
-    const OUString& rStart, ::std::vector< OUString > &rResultVec, bool bBack, 
size_t nMax = 0)
+    const OUString& rStart, ::std::vector< OUString > &rResultVec, bool bBack, 
sal_Int32* pLongestPrefixLen = nullptr)
 {
     rResultVec.clear(); // clear contents
 
     if (!rDataSet.size())
         return rDataSet.end();
 
+    sal_Int32 nRingOrigin = 0;
     size_t nCount = 0;
     ScTypedCaseStrSet::const_iterator retit;
     if ( bBack ) // Backwards
@@ -198,7 +225,10 @@ ScTypedCaseStrSet::const_iterator findTextAll(
         {
             ++it;
             if ( it == rDataSet.rend() ) // go to the first if reach the end
+            {
                 it = rDataSet.rbegin();
+                nRingOrigin = nCount;
+            }
 
             if ( bFirstTime )
                 bFirstTime = false;
@@ -221,8 +251,6 @@ ScTypedCaseStrSet::const_iterator findTextAll(
                 std::advance(retit, nPos);
             }
             ++nCount;
-            if (nMax > 0 && nCount >= nMax)
-                break;
         }
     }
     else // Forwards
@@ -238,7 +266,10 @@ ScTypedCaseStrSet::const_iterator findTextAll(
         {
             ++it;
             if ( it == rDataSet.end() ) // go to the first if reach the end
+            {
                 it = rDataSet.begin();
+                nRingOrigin = nCount;
+            }
 
             if ( bFirstTime )
                 bFirstTime = false;
@@ -255,11 +286,21 @@ ScTypedCaseStrSet::const_iterator findTextAll(
             if ( nCount == 0 )
                 retit = it; // remember first match iterator
             ++nCount;
-            if (nMax > 0 && nCount >= nMax)
-                break;
         }
     }
 
+    if (pLongestPrefixLen)
+    {
+        if (nRingOrigin >= static_cast<sal_Int32>(nCount))
+        {
+            // All matches were picked when rDataSet was read in one direction.
+            nRingOrigin = 0;
+        }
+        // rResultsVec is a sorted ring with nRingOrigin "origin".
+        // The direction of sorting is not important for 
getLongestCommonPrefixLength.
+        *pLongestPrefixLen = getLongestCommonPrefixLength(rResultVec, rStart, 
nRingOrigin);
+    }
+
     if ( nCount > 0 ) // at least one function has matched
         return retit;
     return rDataSet.end(); // no matching text found
@@ -788,6 +829,7 @@ ScInputHandler::ScInputHandler()
         bProtected( false ),
         bLastIsSymbol( false ),
         mbDocumentDisposing(false),
+        mbPartialPrefix(false),
         nValidation( 0 ),
         eAttrAdjust( SvxCellHorJustify::Standard ),
         aScaleX( 1,1 ),
@@ -1971,24 +2013,28 @@ void ScInputHandler::UseColData() // When typing
 
     std::vector< OUString > aResultVec;
     OUString aNew;
+    sal_Int32 nLongestPrefixLen = 0;
     miAutoPosColumn = pColumnData->end();
-    miAutoPosColumn = findTextAll(*pColumnData, miAutoPosColumn, aText, 
aResultVec, false, 2);
-    bool bShowCompletion = (aResultVec.size() == 1);
-    bUseTab = (aResultVec.size() == 2);
-    if (bUseTab)
+    mbPartialPrefix = false;
+    miAutoPosColumn = findTextAll(*pColumnData, miAutoPosColumn, aText, 
aResultVec, false, &nLongestPrefixLen);
+
+    if (nLongestPrefixLen <= 0 || aResultVec.empty())
+        return;
+
+    if (aResultVec.size() > 1)
     {
-        // Allow cycling through possible matches using shortcut.
-        // Make miAutoPosColumn invalid so that Ctrl+TAB provides the first 
matching one.
+        mbPartialPrefix = true;
+        bUseTab = true; // Allow Ctrl (+ Shift + ) + TAB cycling.
         miAutoPosColumn = pColumnData->end();
-        aAutoSearch = aText;
-        return;
-    }
 
-    if (!bShowCompletion)
-        return;
+        // Display the rest of longest common prefix as suggestion.
+        aNew = aResultVec[0].copy(0, nLongestPrefixLen);
+    }
+    else
+    {
+        aNew = aResultVec[0];
+    }
 
-    assert(miAutoPosColumn != pColumnData->end());
-    aNew = aResultVec[0];
     // Strings can contain line endings (e.g. due to dBase import),
     // which would result in multiple paragraphs here, which is not desirable.
     //! Then GetExactMatch doesn't work either
@@ -2047,6 +2093,7 @@ void ScInputHandler::NextAutoEntry( bool bBack )
                         // match found!
                         miAutoPosColumn = itNew;
                         bInOwnChange = true;        // disable ModifyHdl 
(reset below)
+                        mbPartialPrefix = false;
 
                         lcl_RemoveLineEnd( aNew );
                         OUString aIns = aNew.copy(aAutoSearch.getLength());
@@ -2942,6 +2989,7 @@ void ScInputHandler::EnterHandler( ScEnterMode nBlockMode 
)
     if (bInEnterHandler) return;
     bInEnterHandler = true;
     bInOwnChange = true; // disable ModifyHdl (reset below)
+    mbPartialPrefix = false;
 
     ImplCreateEditEngine();
 
@@ -3306,6 +3354,7 @@ void ScInputHandler::CancelHandler()
     ImplCreateEditEngine();
 
     bModified = false;
+    mbPartialPrefix = false;
 
     // Don't rely on ShowRefFrame switching the active view synchronously
     // execute the function directly on the correct view's bindings instead
@@ -3598,6 +3647,22 @@ bool ScInputHandler::KeyInput( const KeyEvent& rKEvt, 
bool bStartEdit /* = false
         // Alt-Return and Alt-Ctrl-* are accepted. Everything else with ALT 
are not.
         return false;
 
+    // There is a partial autocomplete suggestion.
+    // Allow its completion with right arrow key (without modifiers).
+    if (mbPartialPrefix && nCode == KEY_RIGHT && !bControl && !bShift && !bAlt 
&&
+        (pTopView || pTableView))
+    {
+        if (pTopView)
+            pTopView->PostKeyEvent(KeyEvent(0, 
css::awt::Key::MOVE_TO_END_OF_PARAGRAPH));
+        if (pTableView)
+            pTableView->PostKeyEvent(KeyEvent(0, 
css::awt::Key::MOVE_TO_END_OF_PARAGRAPH));
+
+        mbPartialPrefix = false;
+
+        // Indicate that this event has been consumed and ScTabViewShell 
should not act on this.
+        return true;
+    }
+
     if (!bControl && nCode == KEY_TAB)
     {
         // Normal TAB moves the cursor right.
diff --git a/sc/source/ui/inc/inputhdl.hxx b/sc/source/ui/inc/inputhdl.hxx
index aabac21240a0..fb3880e97a69 100644
--- a/sc/source/ui/inc/inputhdl.hxx
+++ b/sc/source/ui/inc/inputhdl.hxx
@@ -103,6 +103,9 @@ private:
     bool                    bProtected:1;
     bool                    bLastIsSymbol:1;
     bool                    mbDocumentDisposing:1;
+    /// To indicate if there is a partial prefix completion.
+    bool                    mbPartialPrefix:1;
+
     sal_uLong                   nValidation;
     SvxCellHorJustify       eAttrAdjust;
 
@@ -266,6 +269,9 @@ public:
     bool            IsInEnterHandler() const                { return 
bInEnterHandler; }
     bool            IsInOwnChange() const                   { return 
bInOwnChange; }
 
+    /// Returns true if there is a partial autocomplete suggestion.
+    bool            HasPartialComplete() const              { return 
mbPartialPrefix; };
+
     bool            IsModalMode( const SfxObjectShell* pDocSh );
 
     void            ForgetLastPattern();
diff --git a/sc/source/ui/view/tabvwsh4.cxx b/sc/source/ui/view/tabvwsh4.cxx
index e32bc217b090..8130ac4c0e12 100644
--- a/sc/source/ui/view/tabvwsh4.cxx
+++ b/sc/source/ui/view/tabvwsh4.cxx
@@ -1240,6 +1240,11 @@ bool ScTabViewShell::TabKeyInput(const KeyEvent& rKEvt)
                 default:
                     bIsType = true;
             }
+        else if (nCode == KEY_RIGHT && !bControl && !bShift && !bAlt)
+        {
+            ScInputHandler* pHdl = pScMod->GetInputHdl(this);
+            bIsType = pHdl && pHdl->HasPartialComplete();
+        }
 
         if( bIsType )
             bUsed = pScMod->InputKeyEvent( rKEvt );     // input

Reply via email to