diff --git a/IDE/src/Compiler/BfParser.bf b/IDE/src/Compiler/BfParser.bf index 34a00e27..6f0c0106 100644 --- a/IDE/src/Compiler/BfParser.bf +++ b/IDE/src/Compiler/BfParser.bf @@ -78,6 +78,8 @@ namespace IDE.Compiler public bool mCancelled; public int32 mTextVersion = -1; public bool mIsUserRequested; + + public bool mDoFuzzyAutoComplete; } public class BfParser : ILeakIdentifiable @@ -126,7 +128,7 @@ namespace IDE.Compiler static extern char8* BfParser_GetDebugExpressionAt(void* bfParser, int32 cursorIdx); [CallingConvention(.Stdcall), CLink] - static extern void* BfParser_CreateResolvePassData(void* bfSystem, int32 resolveType); + static extern void* BfParser_CreateResolvePassData(void* bfSystem, int32 resolveType, bool doFuzzyAutoComplete); [CallingConvention(.Stdcall), CLink] static extern bool BfParser_BuildDefs(void* bfParser, void* bfPassInstance, void* bfResolvePassData, bool fullRefresh); @@ -253,10 +255,10 @@ namespace IDE.Compiler BfParser_GenerateAutoCompletionFrom(mNativeBfParser, srcPosition); } - public BfResolvePassData CreateResolvePassData(ResolveType resolveType = ResolveType.Autocomplete) + public BfResolvePassData CreateResolvePassData(ResolveType resolveType = ResolveType.Autocomplete, bool doFuzzyAutoComplete = false) { var resolvePassData = new BfResolvePassData(); - resolvePassData.mNativeResolvePassData = BfParser_CreateResolvePassData(mNativeBfParser, (int32)resolveType); + resolvePassData.mNativeResolvePassData = BfParser_CreateResolvePassData(mNativeBfParser, (int32)resolveType, doFuzzyAutoComplete); return resolvePassData; } diff --git a/IDE/src/Compiler/BfResolvePassData.bf b/IDE/src/Compiler/BfResolvePassData.bf index 9d7ebccc..eb48a844 100644 --- a/IDE/src/Compiler/BfResolvePassData.bf +++ b/IDE/src/Compiler/BfResolvePassData.bf @@ -94,10 +94,10 @@ namespace IDE.Compiler BfResolvePassData_SetDocumentationRequest(mNativeResolvePassData, entryName); } - public static BfResolvePassData Create(ResolveType resolveType = ResolveType.Autocomplete) + public static BfResolvePassData Create(ResolveType resolveType = ResolveType.Autocomplete, bool doFuzzyAutoComplete = false) { var resolvePassData = new BfResolvePassData(); - resolvePassData.mNativeResolvePassData = BfParser.[Friend]BfParser_CreateResolvePassData(null, (int32)resolveType); + resolvePassData.mNativeResolvePassData = BfParser.[Friend]BfParser_CreateResolvePassData(null, (int32)resolveType, doFuzzyAutoComplete); return resolvePassData; } } diff --git a/IDE/src/Settings.bf b/IDE/src/Settings.bf index 6065643e..1c3a1bec 100644 --- a/IDE/src/Settings.bf +++ b/IDE/src/Settings.bf @@ -604,7 +604,7 @@ namespace IDE No, Yes, BackupOnly - } + } public List mFonts = new .() ~ DeleteContainerAndItems!(_); public float mFontSize = 12; @@ -613,6 +613,7 @@ namespace IDE public bool mAutoCompleteRequireTab = false; public bool mAutoCompleteOnEnter = true; public bool mAutoCompleteShowDocumentation = true; + public bool mFuzzyAutoComplete = false; public bool mShowLocatorAnim = true; public bool mHiliteCursorReferences = true; public bool mLockEditing; @@ -639,6 +640,7 @@ namespace IDE sd.Add("AutoCompleteRequireTab", mAutoCompleteRequireTab); sd.Add("AutoCompleteOnEnter", mAutoCompleteOnEnter); sd.Add("AutoCompleteShowDocumentation", mAutoCompleteShowDocumentation); + sd.Add("FuzzyAutoComplete", mFuzzyAutoComplete); sd.Add("ShowLocatorAnim", mShowLocatorAnim); sd.Add("HiliteCursorReferences", mHiliteCursorReferences); sd.Add("LockEditing", mLockEditing); @@ -668,6 +670,7 @@ namespace IDE sd.Get("AutoCompleteRequireTab", ref mAutoCompleteRequireTab); sd.Get("AutoCompleteOnEnter", ref mAutoCompleteOnEnter); sd.Get("AutoCompleteShowDocumentation", ref mAutoCompleteShowDocumentation); + sd.Get("FuzzyAutoComplete", ref mFuzzyAutoComplete); sd.Get("ShowLocatorAnim", ref mShowLocatorAnim); sd.Get("HiliteCursorReferences", ref mHiliteCursorReferences); sd.Get("LockEditing", ref mLockEditing); diff --git a/IDE/src/ui/AutoComplete.bf b/IDE/src/ui/AutoComplete.bf index 9501678e..b9b923fa 100644 --- a/IDE/src/ui/AutoComplete.bf +++ b/IDE/src/ui/AutoComplete.bf @@ -1615,7 +1615,7 @@ namespace IDE.ui static extern bool fts_fuzzy_match(char8* pattern, char8* str, ref int32 outScore, uint8* matches, int maxMatches); /// Checks whether the given entry matches the filter and updates its score and match indices accordingly. - bool UpdateFilterMatch(AutoCompleteListWidget.EntryWidget entry, String filter) + bool DoesFilterMatchFuzzy(AutoCompleteListWidget.EntryWidget entry, String filter) { if (filter.Length == 0) return true; @@ -1653,6 +1653,72 @@ namespace IDE.ui return true; } + bool DoesFilterMatch(String entry, String filter) + { + if (filter.Length == 0) + return true; + + char8* entryPtr = entry.Ptr; + char8* filterPtr = filter.Ptr; + + int filterLen = (int)filter.Length; + int entryLen = (int)entry.Length; + + bool hasUnderscore = false; + bool checkInitials = filterLen > 1; + for (int i = 0; i < (int)filterLen; i++) + { + char8 c = filterPtr[i]; + if (c == '_') + hasUnderscore = true; + else if (filterPtr[i].IsLower) + checkInitials = false; + } + + if (hasUnderscore) + //return strnicmp(filter, entry, filterLen) == 0; + return (entryLen >= filterLen) && (String.Compare(entryPtr, filterLen, filterPtr, filterLen, true) == 0); + + char8[256] initialStr; + char8* initialStrP = &initialStr; + + //String initialStr; + bool prevWasUnderscore = false; + + for (int entryIdx = 0; entryIdx < entryLen; entryIdx++) + { + char8 entryC = entryPtr[entryIdx]; + + if (entryC == '_') + { + prevWasUnderscore = true; + continue; + } + + if ((entryIdx == 0) || (prevWasUnderscore) || (entryC.IsUpper) || (entryC.IsDigit)) + { + /*if (strnicmp(filter, entry + entryIdx, filterLen) == 0) + return true;*/ + if ((entryLen - entryIdx >= filterLen) && (String.Compare(entryPtr + entryIdx, filterLen, filterPtr, filterLen, true) == 0)) + return true; + if (checkInitials) + *(initialStrP++) = entryC; + } + prevWasUnderscore = false; + + if (filterLen == 1) + break; // Don't check inners for single-character case + } + + if (!checkInitials) + return false; + int initialLen = initialStrP - (char8*)&initialStr; + return (initialLen >= filterLen) && (String.Compare(&initialStr, filterLen, filterPtr, filterLen, true) == 0); + + //*(initialStrP++) = 0; + //return strnicmp(filter, initialStr, filterLen) == 0; + } + [LinkName("_stricmp")] static extern int32 stricmp(char8* lhs, char8* rhs); @@ -1708,14 +1774,22 @@ namespace IDE.ui if (curString == ".") curString.Clear(); + bool doFuzzyAutoComplete = gApp.mSettings.mEditorSettings.mFuzzyAutoComplete; + for (int i < mAutoCompleteListWidget.mFullEntryList.Count) { var entry = mAutoCompleteListWidget.mFullEntryList[i]; - if (UpdateFilterMatch(entry, curString)) + if (doFuzzyAutoComplete && DoesFilterMatchFuzzy(entry, curString)) { mAutoCompleteListWidget.mEntryList.Add(entry); visibleCount++; + } + else if (!doFuzzyAutoComplete && DoesFilterMatch(entry.mEntryDisplay, curString)) + { + mAutoCompleteListWidget.mEntryList.Add(entry); + mAutoCompleteListWidget.UpdateEntry(entry, visibleCount); + visibleCount++; } else { @@ -1723,20 +1797,23 @@ 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) + if (doFuzzyAutoComplete) { - mAutoCompleteListWidget.UpdateEntry(mAutoCompleteListWidget.mEntryList[i], i); + // 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)) diff --git a/IDE/src/ui/SettingsDialog.bf b/IDE/src/ui/SettingsDialog.bf index 38cf60b3..a3824a65 100644 --- a/IDE/src/ui/SettingsDialog.bf +++ b/IDE/src/ui/SettingsDialog.bf @@ -98,6 +98,7 @@ namespace IDE.ui AddPropertiesItem(category, "Autocomplete Require Tab", "mAutoCompleteRequireTab"); AddPropertiesItem(category, "Autocomplete on Enter", "mAutoCompleteOnEnter"); AddPropertiesItem(category, "Autocomplete Show Documentation", "mAutoCompleteShowDocumentation"); + AddPropertiesItem(category, "Fuzzy Autocomplete", "mFuzzyAutoComplete"); AddPropertiesItem(category, "Show Locator Animation", "mShowLocatorAnim"); AddPropertiesItem(category, "Hilite Symbol at Cursor", "mHiliteCursorReferences"); diff --git a/IDE/src/ui/SourceViewPanel.bf b/IDE/src/ui/SourceViewPanel.bf index 6e814b31..eb6f3f43 100644 --- a/IDE/src/ui/SourceViewPanel.bf +++ b/IDE/src/ui/SourceViewPanel.bf @@ -622,6 +622,7 @@ namespace IDE.ui ResolveParams resolveParams = new ResolveParams(); resolveParams.mIsUserRequested = options.HasFlag(.UserRequested); + resolveParams.mDoFuzzyAutoComplete = gApp.mSettings.mEditorSettings.mFuzzyAutoComplete; Classify(.Autocomplete, resolveParams); if (!resolveParams.mInDeferredList) delete resolveParams; @@ -1853,7 +1854,9 @@ namespace IDE.ui /*else (!isFullClassify) -- do we ever need to do this? parser.SetCursorIdx(mEditWidget.mEditWidgetContent.CursorTextPos);*/ - var resolvePassData = parser.CreateResolvePassData(resolveType); + bool doFuzzyAutoComplete = resolveParams?.mDoFuzzyAutoComplete ?? false; + + var resolvePassData = parser.CreateResolvePassData(resolveType, doFuzzyAutoComplete); if (resolveParams != null) { if (resolveParams.mLocalId != -1) diff --git a/IDEHelper/Compiler/BfAutoComplete.cpp b/IDEHelper/Compiler/BfAutoComplete.cpp index 98908d96..105ed529 100644 --- a/IDEHelper/Compiler/BfAutoComplete.cpp +++ b/IDEHelper/Compiler/BfAutoComplete.cpp @@ -19,6 +19,7 @@ AutoCompleteBase::AutoCompleteBase() { mIsGetDefinition = false; mIsAutoComplete = true; + mDoFuzzyAutoComplete = false; mInsertStartIdx = -1; mInsertEndIdx = -1; } @@ -45,8 +46,6 @@ inline void UpdateEntryMatchindices(uint8* matches, AutoCompleteEntry& entry) } } - //assert(entry.mMatches != nullptr); - entry.mMatches = matches; } else @@ -129,23 +128,71 @@ bool AutoCompleteBase::DoesFilterMatch(const char* entry, const char* filter, in if (!mIsAutoComplete) return false; + matches[0] = UINT8_MAX; + if (filter[0] == '\0') - { - // Kinda dirty - matches[0] = UINT8_MAX; - matches[1] = 0; return true; - } int filterLen = (int)strlen(filter); int entryLen = (int)strlen(entry); if (filterLen > entryLen) - { return false; - } - return fts::fuzzy_match(filter, entry, score, matches, maxMatches); + if (mDoFuzzyAutoComplete) + { + return fts::fuzzy_match(filter, entry, score, matches, maxMatches); + } + else + { + bool hasUnderscore = false; + bool checkInitials = filterLen > 1; + for (int i = 0; i < (int)filterLen; i++) + { + char c = filter[i]; + if (c == '_') + hasUnderscore = true; + else if (islower((uint8)filter[i])) + checkInitials = false; + } + + if (hasUnderscore) + return strnicmp(filter, entry, filterLen) == 0; + + char initialStr[256]; + char* initialStrP = initialStr; + + //String initialStr; + bool prevWasUnderscore = false; + + for (int entryIdx = 0; entryIdx < entryLen; entryIdx++) + { + char entryC = entry[entryIdx]; + + if (entryC == '_') + { + prevWasUnderscore = true; + continue; + } + + if ((entryIdx == 0) || (prevWasUnderscore) || (isupper((uint8)entryC) || (isdigit((uint8)entryC)))) + { + if (strnicmp(filter, entry + entryIdx, filterLen) == 0) + return true; + if (checkInitials) + *(initialStrP++) = entryC; + } + prevWasUnderscore = false; + + if (filterLen == 1) + break; // Don't check inners for single-character case + } + + if (!checkInitials) + return false; + *(initialStrP++) = 0; + return strnicmp(filter, initialStr, filterLen) == 0; + } } void AutoCompleteBase::Clear() @@ -157,7 +204,7 @@ void AutoCompleteBase::Clear() ////////////////////////////////////////////////////////////////////////// -BfAutoComplete::BfAutoComplete(BfResolveType resolveType) +BfAutoComplete::BfAutoComplete(BfResolveType resolveType, bool doFuzzyAutoComplete) { mResolveType = resolveType; mModule = NULL; @@ -174,6 +221,8 @@ BfAutoComplete::BfAutoComplete(BfResolveType resolveType) (resolveType == BfResolveType_GoToDefinition); mIsAutoComplete = (resolveType == BfResolveType_Autocomplete); + mDoFuzzyAutoComplete = doFuzzyAutoComplete; + mGetDefinitionNode = NULL; mShowAttributeProperties = NULL; mIdentifierUsed = NULL; diff --git a/IDEHelper/Compiler/BfAutoComplete.h b/IDEHelper/Compiler/BfAutoComplete.h index 93b973a6..6e610512 100644 --- a/IDEHelper/Compiler/BfAutoComplete.h +++ b/IDEHelper/Compiler/BfAutoComplete.h @@ -111,6 +111,7 @@ public: bool mIsGetDefinition; bool mIsAutoComplete; + bool mDoFuzzyAutoComplete; int mInsertStartIdx; int mInsertEndIdx; @@ -240,7 +241,7 @@ public: String ConstantToString(BfIRConstHolder* constHolder, BfIRValue id); public: - BfAutoComplete(BfResolveType resolveType = BfResolveType_Autocomplete); + BfAutoComplete(BfResolveType resolveType = BfResolveType_Autocomplete, bool doFuzzyAutoComplete = false); ~BfAutoComplete(); void SetModule(BfModule* module); diff --git a/IDEHelper/Compiler/BfParser.cpp b/IDEHelper/Compiler/BfParser.cpp index e96ba38f..0bcea9b5 100644 --- a/IDEHelper/Compiler/BfParser.cpp +++ b/IDEHelper/Compiler/BfParser.cpp @@ -3897,13 +3897,13 @@ BF_EXPORT const char* BF_CALLTYPE BfParser_GetDebugExpressionAt(BfParser* bfPars return outString.c_str(); } -BF_EXPORT BfResolvePassData* BF_CALLTYPE BfParser_CreateResolvePassData(BfParser* bfParser, BfResolveType resolveType) +BF_EXPORT BfResolvePassData* BF_CALLTYPE BfParser_CreateResolvePassData(BfParser* bfParser, BfResolveType resolveType, bool doFuzzyAutoComplete) { auto bfResolvePassData = new BfResolvePassData(); bfResolvePassData->mResolveType = resolveType; bfResolvePassData->mParser = bfParser; if ((bfParser != NULL) && ((bfParser->mParserFlags & ParserFlag_Autocomplete) != 0)) - bfResolvePassData->mAutoComplete = new BfAutoComplete(resolveType); + bfResolvePassData->mAutoComplete = new BfAutoComplete(resolveType, doFuzzyAutoComplete); return bfResolvePassData; }