diff --git a/IDE/src/ui/AutoComplete.bf b/IDE/src/ui/AutoComplete.bf index aace0a59..9501678e 100644 --- a/IDE/src/ui/AutoComplete.bf +++ b/IDE/src/ui/AutoComplete.bf @@ -383,6 +383,7 @@ namespace IDE.ui public String mDocumentation; public Image mIcon; public List mMatchIndices; + public int32 mScore; public float Y { @@ -423,7 +424,20 @@ namespace IDE.ui index = @c.NextIndex; } - } + } + + public void SetMatches(Span matchIndices) + { + mMatchIndices?.Clear(); + + if (!matchIndices.IsEmpty) + { + if(mMatchIndices == null) + mMatchIndices = new:(mAutoCompleteListWidget.mAlloc) List(matchIndices.Length); + + mMatchIndices.AddRange(matchIndices); + } + } } class Content : Widget @@ -635,14 +649,13 @@ namespace IDE.ui if (!documentation.IsEmpty) entryWidget.mDocumentation = new:mAlloc String(documentation); entryWidget.mIcon = icon; - // TODO(FUZZY): There may be a better way - if (matchIndices != null && !matchIndices.IsEmpty) - entryWidget.mMatchIndices = new:mAlloc List(matchIndices.GetEnumerator()); + + entryWidget.SetMatches(matchIndices); UpdateEntry(entryWidget, mEntryList.Count); mEntryList.Add(entryWidget); //mScrollContent.AddWidget(entryWidget); - } + } public void EnsureSelectionVisible() { @@ -1599,22 +1612,50 @@ namespace IDE.ui // IDEHelper/third_party/FtsFuzzyMatch.h [CallingConvention(.Stdcall), CLink] - static extern bool fts_fuzzy_match(char8* pattern, char8* str, ref int outScore, uint8* matches, int maxMatches); + static extern bool fts_fuzzy_match(char8* pattern, char8* str, ref int32 outScore, uint8* matches, int maxMatches); - bool DoesFilterMatch(String entry, String filter) + /// Checks whether the given entry matches the filter and updates its score and match indices accordingly. + bool UpdateFilterMatch(AutoCompleteListWidget.EntryWidget entry, String filter) { if (filter.Length == 0) return true; - if (filter.Length > entry.Length) + if (filter.Length > entry.mEntryDisplay.Length) return false; - int score = 0; + int32 score = 0; uint8[256] matches = ?; - return fts_fuzzy_match(filter.CStr(), entry.CStr(), ref score, &matches, matches.Count); + if (!fts_fuzzy_match(filter.CStr(), entry.mEntryDisplay.CStr(), ref score, &matches, matches.Count)) + { + entry.SetMatches(Span(null, 0)); + entry.mScore = score; + return false; + } + + // Should be the amount of Unicode-codepoints in filter though it' probably faster to do it this way + int matchesLength = 0; + + for (uint8 i = 0;; i++) + { + uint8 matchIndex = matches[i]; + + if ((matchIndex == 0 && i != 0) || i == uint8.MaxValue) + { + matchesLength = i; + break; + } + } + + entry.SetMatches(Span(&matches, matchesLength)); + entry.mScore = score; + + return true; } + [LinkName("_stricmp")] + static extern int32 stricmp(char8* lhs, char8* rhs); + void UpdateData(String selectString, bool changedAfterInfo) { if ((mInsertEndIdx != -1) && (mInsertEndIdx < mInsertStartIdx)) @@ -1671,10 +1712,9 @@ namespace IDE.ui { var entry = mAutoCompleteListWidget.mFullEntryList[i]; - if (DoesFilterMatch(entry.mEntryDisplay, curString)) + if (UpdateFilterMatch(entry, curString)) { mAutoCompleteListWidget.mEntryList.Add(entry); - mAutoCompleteListWidget.UpdateEntry(entry, visibleCount); visibleCount++; } else @@ -1683,6 +1723,22 @@ namespace IDE.ui } } + // sort entries because the scores probably have changed + mAutoCompleteListWidget.mEntryList.Sort(scope (left, right) => + { + if (left.mScore > right.mScore) + return -1; + else if (left.mScore < right.mScore) + return 1; + else + return ((stricmp(left.mEntryDisplay.CStr(), right.mEntryDisplay.CStr()) < 0) ? -1 : 1); + }); + + for (int i < mAutoCompleteListWidget.mEntryList.Count) + { + mAutoCompleteListWidget.UpdateEntry(mAutoCompleteListWidget.mEntryList[i], i); + } + if ((visibleCount == 0) && (mInvokeSrcPositions == null)) { mPopulating = false; diff --git a/IDEHelper/Compiler/BfAutoComplete.cpp b/IDEHelper/Compiler/BfAutoComplete.cpp index 288e1b8f..98908d96 100644 --- a/IDEHelper/Compiler/BfAutoComplete.cpp +++ b/IDEHelper/Compiler/BfAutoComplete.cpp @@ -28,19 +28,16 @@ AutoCompleteBase::~AutoCompleteBase() Clear(); } -AutoCompleteEntry* AutoCompleteBase::AddEntry(AutoCompleteEntry& entry, const StringImpl& filter) +inline void UpdateEntryMatchindices(uint8* matches, AutoCompleteEntry& entry) { - uint8 matches[256]; - - if (!DoesFilterMatch(entry.mDisplay, filter.c_str(), entry.mScore, matches, 256) || (entry.mNamePrefixCount < 0)) - return NULL; - if (matches[0] != UINT8_MAX) { + // Count entries in matches + // Note: entry.mMatchesLength should be the amount of unicode-codepoints in the filter for (uint8 i = 0;; i++) { uint8 matchIndex = matches[i]; - + if ((matchIndex == 0 && i != 0) || i == UINT8_MAX) { entry.mMatchesLength = i; @@ -48,15 +45,33 @@ AutoCompleteEntry* AutoCompleteBase::AddEntry(AutoCompleteEntry& entry, const St } } - if (entry.mMatches != nullptr) - delete entry.mMatches; + //assert(entry.mMatches != nullptr); - entry.mMatches = new uint8[entry.mMatchesLength]; - - memcpy(entry.mMatches, matches, entry.mMatchesLength); + entry.mMatches = matches; } + else + { + entry.mMatches = nullptr; + entry.mMatchesLength = 0; + } +} - return AddEntry(entry); +AutoCompleteEntry* AutoCompleteBase::AddEntry(AutoCompleteEntry& entry, const StringImpl& filter) +{ + uint8 matches[256]; + + if (!DoesFilterMatch(entry.mDisplay, filter.c_str(), entry.mScore, matches, 256) || (entry.mNamePrefixCount < 0)) + return NULL; + + UpdateEntryMatchindices(matches, entry); + + auto result = AddEntry(entry); + + // Reset matches because the array will be invalid after return + entry.mMatches = nullptr; + entry.mMatchesLength = 0; + + return result; } AutoCompleteEntry* AutoCompleteBase::AddEntry(AutoCompleteEntry& entry, const char* filter) @@ -66,28 +81,15 @@ AutoCompleteEntry* AutoCompleteBase::AddEntry(AutoCompleteEntry& entry, const ch if (!DoesFilterMatch(entry.mDisplay, filter, entry.mScore, matches, 256) || (entry.mNamePrefixCount < 0)) return NULL; - if (matches[0] != UINT8_MAX) - { - for (uint8 i = 0;; i++) - { - uint8 matchIndex = matches[i]; + UpdateEntryMatchindices(matches, entry); - if ((matchIndex == 0 && i != 0) || i == UINT8_MAX) - { - entry.mMatchesLength = i; - break; - } - } + auto result = AddEntry(entry); - if (entry.mMatches != nullptr) - delete entry.mMatches; + // Reset matches because the array will be invalid after return + entry.mMatches = nullptr; + entry.mMatchesLength = 0; - entry.mMatches = new uint8[entry.mMatchesLength]; - - memcpy(entry.mMatches, matches, entry.mMatchesLength); - } - - return AddEntry(entry); + return result; } AutoCompleteEntry* AutoCompleteBase::AddEntry(const AutoCompleteEntry& entry) @@ -140,9 +142,6 @@ bool AutoCompleteBase::DoesFilterMatch(const char* entry, const char* filter, in if (filterLen > entryLen) { - // Kinda dirty - matches[0] = UINT8_MAX; - matches[1] = 0; return false; } diff --git a/IDEHelper/Compiler/BfCompiler.cpp b/IDEHelper/Compiler/BfCompiler.cpp index cc540e80..ba777852 100644 --- a/IDEHelper/Compiler/BfCompiler.cpp +++ b/IDEHelper/Compiler/BfCompiler.cpp @@ -8007,8 +8007,12 @@ void BfCompiler::GenerateAutocompleteInfo() { entries.Add(&entry); } + std::sort(entries.begin(), entries.end(), [](AutoCompleteEntry* lhs, AutoCompleteEntry* rhs) { + if (lhs->mScore == rhs->mScore) + return stricmp(lhs->mDisplay, rhs->mDisplay) < 0; + return lhs->mScore > rhs->mScore; });