From f9f53eb97b07cbec2453e13c6ab0c32818275d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Wed, 8 Dec 2021 22:08:57 +0100 Subject: [PATCH 1/8] Basic fuzzy search for autocomplete --- IDE/src/ui/AutoComplete.bf | 65 +++++++- IDEHelper/Compiler/BfAutoComplete.cpp | 40 ++++- IDEHelper/Compiler/BfAutoComplete.h | 11 +- IDEHelper/Compiler/BfCompiler.cpp | 26 ++- IDEHelper/Compiler/FtsFuzzyMatch.h | 223 ++++++++++++++++++++++++++ IDEHelper/IDEHelper.vcxproj | 1 + IDEHelper/IDEHelper.vcxproj.filters | 1 + 7 files changed, 347 insertions(+), 20 deletions(-) create mode 100644 IDEHelper/Compiler/FtsFuzzyMatch.h diff --git a/IDE/src/ui/AutoComplete.bf b/IDE/src/ui/AutoComplete.bf index 1e0a4640..0777d4f6 100644 --- a/IDE/src/ui/AutoComplete.bf +++ b/IDE/src/ui/AutoComplete.bf @@ -382,6 +382,7 @@ namespace IDE.ui public String mEntryInsert; public String mDocumentation; public Image mIcon; + public List mMatchIndices; public float Y { @@ -401,7 +402,31 @@ namespace IDE.ui g.Draw(mIcon, 0, 0); g.SetFont(IDEApp.sApp.mCodeFont); - g.DrawString(mEntryDisplay, GS!(20), 0); + + float offset = GS!(20); + + // TODO(FUZZY): this is not unicode compatible + for(int i < mEntryDisplay.Length) + { + char8 c = mEntryDisplay[i]; + + if(mMatchIndices.Contains((uint8)i)) + { + g.PushColor(.Blue); + } + else + { + g.PushColor(.White); + } + + g.DrawString(.(&c, 1), offset, 0); + + offset += IDEApp.sApp.mCodeFont.GetWidth(.(&c, 1)); + + g.PopColor(); + } + + //g.DrawString(mEntryDisplay, GS!(20), 0); } } @@ -602,8 +627,8 @@ namespace IDE.ui mMaxWidth = Math.Max(mMaxWidth, entryWidth); }*/ } - - public void AddEntry(StringView entryType, StringView entryDisplay, Image icon, StringView entryInsert = default, StringView documentation = default) + + public void AddEntry(StringView entryType, StringView entryDisplay, Image icon, StringView entryInsert = default, StringView documentation = default, List matchIndices = null) { var entryWidget = new:mAlloc EntryWidget(); entryWidget.mAutoCompleteListWidget = this; @@ -614,6 +639,9 @@ 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()); UpdateEntry(entryWidget, mEntryList.Count); mEntryList.Add(entryWidget); @@ -1981,9 +2009,9 @@ namespace IDE.ui InvokeWidget oldInvokeWidget = null; String selectString = null; + List matchIndices = new:ScopedAlloc! .(256); for (var entryView in info.Split('\n')) { - Image entryIcon = null; StringView entryType = StringView(entryView); int tabPos = entryType.IndexOf('\t'); @@ -1993,13 +2021,34 @@ namespace IDE.ui entryDisplay = StringView(entryView, tabPos + 1); entryType = StringView(entryType, 0, tabPos); } + StringView matches = default; + int matchesPos = entryDisplay.IndexOf('\x02'); + matchIndices.Clear(); + if (matchesPos != -1) + { + matches = StringView(entryDisplay, matchesPos + 1); + entryDisplay = StringView(entryDisplay, 0, matchesPos); + + for(var sub in matches.Split(',')) + { + if(sub == "X") + break; + + var result = int64.Parse(sub, .HexNumber); + + Debug.Assert((result case .Ok(let value)) && value <= uint8.MaxValue); + + // TODO(FUZZY): we could save start and length instead of single chars + matchIndices.Add((uint8)result.Value); + } + } StringView documentation = default; - int docPos = entryDisplay.IndexOf('\x03'); + int docPos = matches.IndexOf('\x03'); if (docPos != -1) { - documentation = StringView(entryDisplay, docPos + 1); - entryDisplay = StringView(entryDisplay, 0, docPos); + documentation = StringView(matches, docPos + 1); + matches = StringView(matches, 0, docPos); } StringView entryInsert = default; @@ -2128,7 +2177,7 @@ namespace IDE.ui if (!mInvokeOnly) { mIsFixit |= entryType == "fixit"; - mAutoCompleteListWidget.AddEntry(entryType, entryDisplay, entryIcon, entryInsert, documentation); + mAutoCompleteListWidget.AddEntry(entryType, entryDisplay, entryIcon, entryInsert, documentation, matchIndices); } } } diff --git a/IDEHelper/Compiler/BfAutoComplete.cpp b/IDEHelper/Compiler/BfAutoComplete.cpp index aca82e51..22b87c9d 100644 --- a/IDEHelper/Compiler/BfAutoComplete.cpp +++ b/IDEHelper/Compiler/BfAutoComplete.cpp @@ -6,6 +6,9 @@ #include "BfFixits.h" #include "BfResolvedTypeUtils.h" +#define FTS_FUZZY_MATCH_IMPLEMENTATION +#include "FtsFuzzyMatch.h" + #pragma warning(disable:4996) using namespace llvm; @@ -25,16 +28,16 @@ AutoCompleteBase::~AutoCompleteBase() Clear(); } -AutoCompleteEntry* AutoCompleteBase::AddEntry(const AutoCompleteEntry& entry, const StringImpl& filter) +AutoCompleteEntry* AutoCompleteBase::AddEntry(AutoCompleteEntry& entry, const StringImpl& filter) { - if ((!DoesFilterMatch(entry.mDisplay, filter.c_str())) || (entry.mNamePrefixCount < 0)) + if ((!DoesFilterMatch(entry.mDisplay, filter.c_str(), entry.mScore, entry.mMatches, sizeof(entry.mMatches))) || (entry.mNamePrefixCount < 0)) return NULL; return AddEntry(entry); } -AutoCompleteEntry* AutoCompleteBase::AddEntry(const AutoCompleteEntry& entry, const char* filter) +AutoCompleteEntry* AutoCompleteBase::AddEntry(AutoCompleteEntry& entry, const char* filter) { - if ((!DoesFilterMatch(entry.mDisplay, filter)) || (entry.mNamePrefixCount < 0)) + if ((!DoesFilterMatch(entry.mDisplay, filter, entry.mScore, entry.mMatches, sizeof(entry.mMatches))) || (entry.mNamePrefixCount < 0)) return NULL; return AddEntry(entry); } @@ -60,7 +63,7 @@ AutoCompleteEntry* AutoCompleteBase::AddEntry(const AutoCompleteEntry& entry) return insertedEntry; } -bool AutoCompleteBase::DoesFilterMatch(const char* entry, const char* filter) +bool AutoCompleteBase::DoesFilterMatch(const char* entry, const char* filter, int& score, uint8* matches, int maxMatches) { if (mIsGetDefinition) { @@ -73,12 +76,28 @@ bool AutoCompleteBase::DoesFilterMatch(const char* entry, const char* filter) if (!mIsAutoComplete) return false; - if (filter[0] == 0) + 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) + { + // Kinda dirty + matches[0] = UINT8_MAX; + matches[1] = 0; + return false; + } + + // TODO: also do matches (but probably optimize them) + return fts::fuzzy_match(filter, entry, score, matches, maxMatches); + /* bool hasUnderscore = false; bool checkInitials = filterLen > 1; for (int i = 0; i < (int)filterLen; i++) @@ -126,6 +145,7 @@ bool AutoCompleteBase::DoesFilterMatch(const char* entry, const char* filter) return false; *(initialStrP++) = 0; return strnicmp(filter, initialStr, filterLen) == 0; + */ } void AutoCompleteBase::Clear() @@ -550,7 +570,9 @@ void BfAutoComplete::AddTypeDef(BfTypeDef* typeDef, const StringImpl& filter, bo return; } - if (!DoesFilterMatch(name.c_str(), filter.c_str())) + int score; + uint8 matches[256]; + if (!DoesFilterMatch(name.c_str(), filter.c_str(), score, matches, sizeof(matches))) return; auto type = mModule->ResolveTypeDef(typeDef, BfPopulateType_Declaration); @@ -1128,8 +1150,10 @@ void BfAutoComplete::AddExtensionMethods(BfTypeInstance* targetType, BfTypeInsta if (methodInstance == NULL) continue; + int score; + uint8 matches[256]; // Do filter match first- may be cheaper than generic validation - if (!DoesFilterMatch(methodDef->mName.c_str(), filter.c_str())) + if (!DoesFilterMatch(methodDef->mName.c_str(), filter.c_str(), score, matches, sizeof(matches))) continue; auto thisType = methodInstance->GetParamType(0); diff --git a/IDEHelper/Compiler/BfAutoComplete.h b/IDEHelper/Compiler/BfAutoComplete.h index 183bf514..881a2c90 100644 --- a/IDEHelper/Compiler/BfAutoComplete.h +++ b/IDEHelper/Compiler/BfAutoComplete.h @@ -16,6 +16,8 @@ public: const char* mDisplay; const char* mDocumentation; int8 mNamePrefixCount; + int mScore; + uint8 mMatches[256]; public: AutoCompleteEntry() @@ -29,6 +31,7 @@ public: mDisplay = display; mDocumentation = NULL; mNamePrefixCount = 0; + mScore = 0; } AutoCompleteEntry(const char* entryType, const StringImpl& display) @@ -37,6 +40,7 @@ public: mDisplay = display.c_str(); mDocumentation = NULL; mNamePrefixCount = 0; + mScore = 0; } AutoCompleteEntry(const char* entryType, const StringImpl& display, int namePrefixCount) @@ -45,6 +49,7 @@ public: mDisplay = display.c_str(); mDocumentation = NULL; mNamePrefixCount = (int8)namePrefixCount; + mScore = 0; } bool operator==(const AutoCompleteEntry& other) const @@ -100,9 +105,9 @@ public: int mInsertStartIdx; int mInsertEndIdx; - bool DoesFilterMatch(const char* entry, const char* filter); - AutoCompleteEntry* AddEntry(const AutoCompleteEntry& entry, const StringImpl& filter); - AutoCompleteEntry* AddEntry(const AutoCompleteEntry& entry, const char* filter); + bool DoesFilterMatch(const char* entry, const char* filter, int& score, uint8* matches, int maxMatches); + AutoCompleteEntry* AddEntry(AutoCompleteEntry& entry, const StringImpl& filter); + AutoCompleteEntry* AddEntry(AutoCompleteEntry& entry, const char* filter); AutoCompleteEntry* AddEntry(const AutoCompleteEntry& entry); AutoCompleteBase(); diff --git a/IDEHelper/Compiler/BfCompiler.cpp b/IDEHelper/Compiler/BfCompiler.cpp index f7757a1d..bf329458 100644 --- a/IDEHelper/Compiler/BfCompiler.cpp +++ b/IDEHelper/Compiler/BfCompiler.cpp @@ -8007,7 +8007,9 @@ void BfCompiler::GenerateAutocompleteInfo() } std::sort(entries.begin(), entries.end(), [](AutoCompleteEntry* lhs, AutoCompleteEntry* rhs) { - return stricmp(lhs->mDisplay, rhs->mDisplay) < 0; + // TODO(FUZZY): SORT BY Score + return lhs->mScore > rhs->mScore; + //return stricmp(lhs->mDisplay, rhs->mDisplay) < 0; }); String docString; @@ -8022,6 +8024,28 @@ void BfCompiler::GenerateAutocompleteInfo() autoCompleteResultString += '@'; autoCompleteResultString += String(entry->mDisplay); + // TODO(FUZZY): OUTPUT + // TODO(FUZZY): this is not really efficient + autoCompleteResultString += "\x02"; + for (int i = 0; i < 256; i++) + { + int match = entry->mMatches[i]; + + // no more matches after this + if (match == 0 && i != 0) + break; + + // Need max 3 chars (largest Hex (FF) + '\0') + char buffer[3]; + + _itoa_s(match, buffer, 16); + + autoCompleteResultString += String(buffer); + autoCompleteResultString += ","; + } + + autoCompleteResultString += "X"; + if (entry->mDocumentation != NULL) { autoCompleteResultString += '\x03'; diff --git a/IDEHelper/Compiler/FtsFuzzyMatch.h b/IDEHelper/Compiler/FtsFuzzyMatch.h new file mode 100644 index 00000000..0e80eb62 --- /dev/null +++ b/IDEHelper/Compiler/FtsFuzzyMatch.h @@ -0,0 +1,223 @@ +// LICENSE +// +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// VERSION +// 0.2.0 (2017-02-18) Scored matches perform exhaustive search for best score +// 0.1.0 (2016-03-28) Initial release +// +// AUTHOR +// Forrest Smith +// +// NOTES +// Compiling +// You MUST add '#define FTS_FUZZY_MATCH_IMPLEMENTATION' before including this header in ONE source file to create implementation. +// +// fuzzy_match_simple(...) +// Returns true if each character in pattern is found sequentially within str +// +// fuzzy_match(...) +// Returns true if pattern is found AND calculates a score. +// Performs exhaustive search via recursion to find all possible matches and match with highest score. +// Scores values have no intrinsic meaning. Possible score range is not normalized and varies with pattern. +// Recursion is limited internally (default=10) to prevent degenerate cases (pattern="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") +// Uses uint8_t for match indices. Therefore patterns are limited to 256 characters. +// Score system should be tuned for YOUR use case. Words, sentences, file names, or method names all prefer different tuning. + + +#ifndef FTS_FUZZY_MATCH_H +#define FTS_FUZZY_MATCH_H + + +#include // uint8_t +#include // ::tolower, ::toupper +#include // memcpy + +#include + +// Public interface +namespace fts { + static bool fuzzy_match_simple(char const* pattern, char const* str); + static bool fuzzy_match(char const* pattern, char const* str, int& outScore); + static bool fuzzy_match(char const* pattern, char const* str, int& outScore, uint8_t* matches, int maxMatches); +} + + +#ifdef FTS_FUZZY_MATCH_IMPLEMENTATION +namespace fts { + + // Forward declarations for "private" implementation + namespace fuzzy_internal { + static bool fuzzy_match_recursive(const char* pattern, const char* str, int& outScore, const char* strBegin, + uint8_t const* srcMatches, uint8_t* newMatches, int maxMatches, int nextMatch, + int& recursionCount, int recursionLimit); + } + + // Public interface + static bool fuzzy_match_simple(char const* pattern, char const* str) { + while (*pattern != '\0' && *str != '\0') { + if (tolower(*pattern) == tolower(*str)) + ++pattern; + ++str; + } + + return *pattern == '\0' ? true : false; + } + + static bool fuzzy_match(char const* pattern, char const* str, int& outScore) { + + uint8_t matches[256]; + return fuzzy_match(pattern, str, outScore, matches, sizeof(matches)); + } + + static bool fuzzy_match(char const* pattern, char const* str, int& outScore, uint8_t* matches, int maxMatches) { + int recursionCount = 0; + int recursionLimit = 10; + + return fuzzy_internal::fuzzy_match_recursive(pattern, str, outScore, str, nullptr, matches, maxMatches, 0, recursionCount, recursionLimit); + } + + // Private implementation + static bool fuzzy_internal::fuzzy_match_recursive(const char* pattern, const char* str, int& outScore, + const char* strBegin, uint8_t const* srcMatches, uint8_t* matches, int maxMatches, + int nextMatch, int& recursionCount, int recursionLimit) + { + // Count recursions + ++recursionCount; + if (recursionCount >= recursionLimit) + return false; + + // Detect end of strings + if (*pattern == '\0' || *str == '\0') + return false; + + // Recursion params + bool recursiveMatch = false; + uint8_t bestRecursiveMatches[256]; + int bestRecursiveScore = 0; + + // Loop through pattern and str looking for a match + bool first_match = true; + while (*pattern != '\0' && *str != '\0') { + + // Found match + if (tolower(*pattern) == tolower(*str)) { + + // Supplied matches buffer was too short + if (nextMatch >= maxMatches) + return false; + + // "Copy-on-Write" srcMatches into matches + if (first_match && srcMatches) { + memcpy(matches, srcMatches, nextMatch); + first_match = false; + } + + // Recursive call that "skips" this match + uint8_t recursiveMatches[256]; + int recursiveScore; + if (fuzzy_match_recursive(pattern, str + 1, recursiveScore, strBegin, matches, recursiveMatches, sizeof(recursiveMatches), nextMatch, recursionCount, recursionLimit)) { + + // Pick best recursive score + if (!recursiveMatch || recursiveScore > bestRecursiveScore) { + memcpy(bestRecursiveMatches, recursiveMatches, 256); + bestRecursiveScore = recursiveScore; + } + recursiveMatch = true; + } + + // Advance + matches[nextMatch++] = (uint8_t)(str - strBegin); + // Clear the next char so that we know which match is the last one + matches[nextMatch + 1] = 0; + ++pattern; + } + ++str; + } + + // Determine if full pattern was matched + bool matched = *pattern == '\0' ? true : false; + + // Calculate score + if (matched) { + const int sequential_bonus = 15; // bonus for adjacent matches + const int separator_bonus = 30; // bonus if match occurs after a separator + const int camel_bonus = 30; // bonus if match is uppercase and prev is lower + const int first_letter_bonus = 15; // bonus if the first letter is matched + + const int leading_letter_penalty = -5; // penalty applied for every letter in str before the first match + const int max_leading_letter_penalty = -15; // maximum penalty for leading letters + const int unmatched_letter_penalty = -1; // penalty for every letter that doesn't matter + + // Iterate str to end + while (*str != '\0') + ++str; + + // Initialize score + outScore = 100; + + // Apply leading letter penalty + int penalty = leading_letter_penalty * matches[0]; + if (penalty < max_leading_letter_penalty) + penalty = max_leading_letter_penalty; + outScore += penalty; + + // Apply unmatched penalty + int unmatched = (int)(str - strBegin) - nextMatch; + outScore += unmatched_letter_penalty * unmatched; + + // Apply ordering bonuses + for (int i = 0; i < nextMatch; ++i) { + uint8_t currIdx = matches[i]; + + if (i > 0) { + uint8_t prevIdx = matches[i - 1]; + + // Sequential + if (currIdx == (prevIdx + 1)) + outScore += sequential_bonus; + } + + // Check for bonuses based on neighbor character value + if (currIdx > 0) { + // Camel case + char neighbor = strBegin[currIdx - 1]; + char curr = strBegin[currIdx]; + if (::islower(neighbor) && ::isupper(curr)) + outScore += camel_bonus; + + // Separator + bool neighborSeparator = neighbor == '_' || neighbor == ' '; + if (neighborSeparator) + outScore += separator_bonus; + } + else { + // First letter + outScore += first_letter_bonus; + } + } + } + + // Return best result + if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) { + // Recursive score is better than "this" + memcpy(matches, bestRecursiveMatches, maxMatches); + outScore = bestRecursiveScore; + return true; + } + else if (matched) { + // "this" score is better than recursive + return true; + } + else { + // no match + return false; + } + } +} // namespace fts + +#endif // FTS_FUZZY_MATCH_IMPLEMENTATION + +#endif // FTS_FUZZY_MATCH_H diff --git a/IDEHelper/IDEHelper.vcxproj b/IDEHelper/IDEHelper.vcxproj index 10e6e389..59635b05 100644 --- a/IDEHelper/IDEHelper.vcxproj +++ b/IDEHelper/IDEHelper.vcxproj @@ -400,6 +400,7 @@ + diff --git a/IDEHelper/IDEHelper.vcxproj.filters b/IDEHelper/IDEHelper.vcxproj.filters index 8055b890..ea9206d1 100644 --- a/IDEHelper/IDEHelper.vcxproj.filters +++ b/IDEHelper/IDEHelper.vcxproj.filters @@ -399,5 +399,6 @@ Compiler + \ No newline at end of file From 8847de545a1f3d2c21f6ee74ee8c785701c782bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Sat, 11 Dec 2021 23:38:25 +0100 Subject: [PATCH 2/8] Basic Utf8 support --- IDE/src/ui/AutoComplete.bf | 26 +++++++++++--------------- IDEHelper/Compiler/FtsFuzzyMatch.h | 30 +++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/IDE/src/ui/AutoComplete.bf b/IDE/src/ui/AutoComplete.bf index 0777d4f6..c1a74748 100644 --- a/IDE/src/ui/AutoComplete.bf +++ b/IDE/src/ui/AutoComplete.bf @@ -405,28 +405,24 @@ namespace IDE.ui float offset = GS!(20); - // TODO(FUZZY): this is not unicode compatible - for(int i < mEntryDisplay.Length) + int index = 0; + for(char32 c in mEntryDisplay.DecodedChars) + loop: { - char8 c = mEntryDisplay[i]; - - if(mMatchIndices.Contains((uint8)i)) + if(mMatchIndices.Contains((uint8)index)) { - g.PushColor(.Blue); - } - else - { - g.PushColor(.White); + g.PushColor(DarkTheme.COLOR_MENU_FOCUSED); + defer:loop g.PopColor(); } - g.DrawString(.(&c, 1), offset, 0); + let str = StringView(mEntryDisplay, index, @c.NextIndex - index); - offset += IDEApp.sApp.mCodeFont.GetWidth(.(&c, 1)); + g.DrawString(str, offset, 0); - g.PopColor(); + offset += IDEApp.sApp.mCodeFont.GetWidth(str); + + index = @c.NextIndex; } - - //g.DrawString(mEntryDisplay, GS!(20), 0); } } diff --git a/IDEHelper/Compiler/FtsFuzzyMatch.h b/IDEHelper/Compiler/FtsFuzzyMatch.h index 0e80eb62..3b654da6 100644 --- a/IDEHelper/Compiler/FtsFuzzyMatch.h +++ b/IDEHelper/Compiler/FtsFuzzyMatch.h @@ -37,6 +37,8 @@ #include +#include "BeefySysLib/util/UTF8.h" + // Public interface namespace fts { static bool fuzzy_match_simple(char const* pattern, char const* str); @@ -102,8 +104,14 @@ namespace fts { bool first_match = true; while (*pattern != '\0' && *str != '\0') { + int patternOffset = 0; + uint32 patternChar = Beefy::u8_nextchar((char*)pattern, &patternOffset); + int strOffset = 0; + uint32 strChar = Beefy::u8_nextchar((char*)str, &strOffset); + + // TODO: tolower only works for A-Z // Found match - if (tolower(*pattern) == tolower(*str)) { + if (tolower(patternChar) == tolower(strChar)) { // Supplied matches buffer was too short if (nextMatch >= maxMatches) @@ -118,7 +126,7 @@ namespace fts { // Recursive call that "skips" this match uint8_t recursiveMatches[256]; int recursiveScore; - if (fuzzy_match_recursive(pattern, str + 1, recursiveScore, strBegin, matches, recursiveMatches, sizeof(recursiveMatches), nextMatch, recursionCount, recursionLimit)) { + if (fuzzy_match_recursive(pattern, str + strOffset, recursiveScore, strBegin, matches, recursiveMatches, sizeof(recursiveMatches), nextMatch, recursionCount, recursionLimit)) { // Pick best recursive score if (!recursiveMatch || recursiveScore > bestRecursiveScore) { @@ -132,9 +140,9 @@ namespace fts { matches[nextMatch++] = (uint8_t)(str - strBegin); // Clear the next char so that we know which match is the last one matches[nextMatch + 1] = 0; - ++pattern; + pattern += patternOffset; } - ++str; + str += strOffset; } // Determine if full pattern was matched @@ -172,19 +180,27 @@ namespace fts { for (int i = 0; i < nextMatch; ++i) { uint8_t currIdx = matches[i]; + int currOffset = currIdx; + uint32 curr = Beefy::u8_nextchar((char*)strBegin, &currOffset); + if (i > 0) { uint8_t prevIdx = matches[i - 1]; + int offsetPrevidx = prevIdx; + Beefy::u8_inc((char*)strBegin, &offsetPrevidx); + // Sequential - if (currIdx == (prevIdx + 1)) + if (currIdx == offsetPrevidx) outScore += sequential_bonus; } // Check for bonuses based on neighbor character value if (currIdx > 0) { + int neighborOffset = currIdx; + Beefy::u8_dec((char*)strBegin, &neighborOffset); + uint32 neighbor = Beefy::u8_nextchar((char*)strBegin, &neighborOffset); + // Camel case - char neighbor = strBegin[currIdx - 1]; - char curr = strBegin[currIdx]; if (::islower(neighbor) && ::isupper(curr)) outScore += camel_bonus; From 2a446afc730130f8f83d106edbbd4da63af8131d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Sun, 12 Dec 2021 00:39:49 +0100 Subject: [PATCH 3/8] Unicode upper/lower case matching for fuzzy match --- IDEHelper/Compiler/FtsFuzzyMatch.h | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/IDEHelper/Compiler/FtsFuzzyMatch.h b/IDEHelper/Compiler/FtsFuzzyMatch.h index 3b654da6..77a58b97 100644 --- a/IDEHelper/Compiler/FtsFuzzyMatch.h +++ b/IDEHelper/Compiler/FtsFuzzyMatch.h @@ -38,6 +38,7 @@ #include #include "BeefySysLib/util/UTF8.h" +#include "BeefySysLib/third_party/utf8proc/utf8proc.h" // Public interface namespace fts { @@ -81,6 +82,16 @@ namespace fts { return fuzzy_internal::fuzzy_match_recursive(pattern, str, outScore, str, nullptr, matches, maxMatches, 0, recursionCount, recursionLimit); } + bool IsLower(uint32 c) + { + return utf8proc_category(c) == UTF8PROC_CATEGORY_LL; + } + + bool IsUpper(uint32 c) + { + return utf8proc_category(c) == UTF8PROC_CATEGORY_LU; + } + // Private implementation static bool fuzzy_internal::fuzzy_match_recursive(const char* pattern, const char* str, int& outScore, const char* strBegin, uint8_t const* srcMatches, uint8_t* matches, int maxMatches, @@ -111,7 +122,7 @@ namespace fts { // TODO: tolower only works for A-Z // Found match - if (tolower(patternChar) == tolower(strChar)) { + if (utf8proc_tolower(patternChar) == utf8proc_tolower(strChar)) { // Supplied matches buffer was too short if (nextMatch >= maxMatches) @@ -201,7 +212,7 @@ namespace fts { uint32 neighbor = Beefy::u8_nextchar((char*)strBegin, &neighborOffset); // Camel case - if (::islower(neighbor) && ::isupper(curr)) + if (IsLower(neighbor) && IsUpper(curr)) outScore += camel_bonus; // Separator From c2c7431620b4dd3c5949bb7275c76e25c91b4a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Wed, 15 Dec 2021 11:01:14 +0100 Subject: [PATCH 4/8] Fixed autocomplete flickering while typing --- IDE/src/ui/AutoComplete.bf | 118 ++++++++---------- IDEHelper/BeefProj.toml | 8 ++ IDEHelper/Compiler/BfAutoComplete.cpp | 2 +- IDEHelper/Compiler/BfCompiler.cpp | 4 - IDEHelper/IDEHelper.vcxproj | 2 +- IDEHelper/IDEHelper.vcxproj.filters | 7 +- .../{Compiler => third_party}/FtsFuzzyMatch.h | 6 + 7 files changed, 75 insertions(+), 72 deletions(-) rename IDEHelper/{Compiler => third_party}/FtsFuzzyMatch.h (96%) diff --git a/IDE/src/ui/AutoComplete.bf b/IDE/src/ui/AutoComplete.bf index c1a74748..77c242fa 100644 --- a/IDE/src/ui/AutoComplete.bf +++ b/IDE/src/ui/AutoComplete.bf @@ -1597,70 +1597,22 @@ namespace IDE.ui mInvokeWidget.mIgnoreMove += ignoreMove ? 1 : -1; } + // 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); + 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) + if (filter.Length > entry.Length) 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; + int score = 0; + uint8[256] matches = ?; + + return fts_fuzzy_match(filter.CStr(), entry.CStr(), ref score, &matches, matches.Count); } void UpdateData(String selectString, bool changedAfterInfo) @@ -1718,7 +1670,7 @@ namespace IDE.ui for (int i < mAutoCompleteListWidget.mFullEntryList.Count) { var entry = mAutoCompleteListWidget.mFullEntryList[i]; - //if (String.Compare(entry.mEntryDisplay, 0, curString, 0, curString.Length, true) == 0) + if (DoesFilterMatch(entry.mEntryDisplay, curString)) { mAutoCompleteListWidget.mEntryList.Add(entry); @@ -1876,6 +1828,7 @@ namespace IDE.ui public void UpdateInfo(String info) { + List matchIndices = new:ScopedAlloc! .(256); for (var entryView in info.Split('\n')) { StringView entryType = StringView(entryView); @@ -1886,13 +1839,35 @@ namespace IDE.ui entryDisplay = StringView(entryView, tabPos + 1); entryType = StringView(entryType, 0, tabPos); } + + StringView matches = default; + int matchesPos = entryDisplay.IndexOf('\x02'); + matchIndices.Clear(); + if (matchesPos != -1) + { + matches = StringView(entryDisplay, matchesPos + 1); + entryDisplay = StringView(entryDisplay, 0, matchesPos); + + for(var sub in matches.Split(',')) + { + if(sub.StartsWith('X')) + break; + + var result = int64.Parse(sub, .HexNumber); + + Debug.Assert((result case .Ok(let value)) && value <= uint8.MaxValue); + + // TODO(FUZZY): we could save start and length instead of single chars + matchIndices.Add((uint8)result.Value); + } + } StringView documentation = default; - int docPos = entryDisplay.IndexOf('\x03'); + int docPos = matches.IndexOf('\x03'); if (docPos != -1) { - documentation = StringView(entryDisplay, docPos + 1); - entryDisplay = StringView(entryDisplay, 0, docPos); + documentation = StringView(matches, docPos + 1); + matches = StringView(matches, 0, docPos); } StringView entryInsert = default; @@ -1915,15 +1890,27 @@ namespace IDE.ui case "select": default: { - if ((!documentation.IsEmpty) && (mAutoCompleteListWidget != null)) + if (((!documentation.IsEmpty) || (!matchIndices.IsEmpty)) && (mAutoCompleteListWidget != null)) { while (entryIdx < mAutoCompleteListWidget.mEntryList.Count) { let entry = mAutoCompleteListWidget.mEntryList[entryIdx]; if ((entry.mEntryDisplay == entryDisplay) && (entry.mEntryType == entryType)) { - if (entry.mDocumentation == null) + if (!matchIndices.IsEmpty) + { + if (entry.mMatchIndices == null) + entry.mMatchIndices = new:(mAutoCompleteListWidget.[Friend]mAlloc) List(matchIndices.GetEnumerator()); + else + { + entry.mMatchIndices.Clear(); + entry.mMatchIndices.AddRange(matchIndices); + } + } + + if ((!documentation.IsEmpty) && entry.mDocumentation == null) entry.mDocumentation = new:(mAutoCompleteListWidget.[Friend]mAlloc) String(documentation); + break; } entryIdx++; @@ -2017,6 +2004,7 @@ namespace IDE.ui entryDisplay = StringView(entryView, tabPos + 1); entryType = StringView(entryType, 0, tabPos); } + StringView matches = default; int matchesPos = entryDisplay.IndexOf('\x02'); matchIndices.Clear(); @@ -2027,7 +2015,7 @@ namespace IDE.ui for(var sub in matches.Split(',')) { - if(sub == "X") + if(sub.StartsWith('X')) break; var result = int64.Parse(sub, .HexNumber); diff --git a/IDEHelper/BeefProj.toml b/IDEHelper/BeefProj.toml index 7f6d1208..f773ae51 100644 --- a/IDEHelper/BeefProj.toml +++ b/IDEHelper/BeefProj.toml @@ -508,3 +508,11 @@ Path = "X86Target.h" [[ProjectFolder.Items]] Type = "Source" Path = "X86XmmInfo.cpp" + +[[ProjectFolder.Items]] +Type = "Folder" +Name = "third_party" + +[[ProjectFolder.Items.Items]] +Type = "Source" +Path = "third_party/FtsFuzzyMatch.h" diff --git a/IDEHelper/Compiler/BfAutoComplete.cpp b/IDEHelper/Compiler/BfAutoComplete.cpp index 22b87c9d..b83b2d46 100644 --- a/IDEHelper/Compiler/BfAutoComplete.cpp +++ b/IDEHelper/Compiler/BfAutoComplete.cpp @@ -7,7 +7,7 @@ #include "BfResolvedTypeUtils.h" #define FTS_FUZZY_MATCH_IMPLEMENTATION -#include "FtsFuzzyMatch.h" +#include "../third_party/FtsFuzzyMatch.h" #pragma warning(disable:4996) diff --git a/IDEHelper/Compiler/BfCompiler.cpp b/IDEHelper/Compiler/BfCompiler.cpp index bf329458..f45e4b49 100644 --- a/IDEHelper/Compiler/BfCompiler.cpp +++ b/IDEHelper/Compiler/BfCompiler.cpp @@ -8007,9 +8007,7 @@ void BfCompiler::GenerateAutocompleteInfo() } std::sort(entries.begin(), entries.end(), [](AutoCompleteEntry* lhs, AutoCompleteEntry* rhs) { - // TODO(FUZZY): SORT BY Score return lhs->mScore > rhs->mScore; - //return stricmp(lhs->mDisplay, rhs->mDisplay) < 0; }); String docString; @@ -8024,8 +8022,6 @@ void BfCompiler::GenerateAutocompleteInfo() autoCompleteResultString += '@'; autoCompleteResultString += String(entry->mDisplay); - // TODO(FUZZY): OUTPUT - // TODO(FUZZY): this is not really efficient autoCompleteResultString += "\x02"; for (int i = 0; i < 256; i++) { diff --git a/IDEHelper/IDEHelper.vcxproj b/IDEHelper/IDEHelper.vcxproj index 59635b05..f2e39a4f 100644 --- a/IDEHelper/IDEHelper.vcxproj +++ b/IDEHelper/IDEHelper.vcxproj @@ -400,7 +400,7 @@ - + diff --git a/IDEHelper/IDEHelper.vcxproj.filters b/IDEHelper/IDEHelper.vcxproj.filters index ea9206d1..5f026346 100644 --- a/IDEHelper/IDEHelper.vcxproj.filters +++ b/IDEHelper/IDEHelper.vcxproj.filters @@ -24,6 +24,9 @@ {83b97406-2f83-49ad-bbbc-3ff70ecda6bb} + + {d36777f2-b326-4a8c-84a3-5c2f39153f75} + @@ -399,6 +402,8 @@ Compiler - + + third_party + \ No newline at end of file diff --git a/IDEHelper/Compiler/FtsFuzzyMatch.h b/IDEHelper/third_party/FtsFuzzyMatch.h similarity index 96% rename from IDEHelper/Compiler/FtsFuzzyMatch.h rename to IDEHelper/third_party/FtsFuzzyMatch.h index 77a58b97..988adf82 100644 --- a/IDEHelper/Compiler/FtsFuzzyMatch.h +++ b/IDEHelper/third_party/FtsFuzzyMatch.h @@ -47,6 +47,7 @@ namespace fts { static bool fuzzy_match(char const* pattern, char const* str, int& outScore, uint8_t* matches, int maxMatches); } +BF_EXPORT bool BF_CALLTYPE fts_fuzzy_match(char const* pattern, char const* str, int& outScore, uint8_t* matches, int maxMatches); #ifdef FTS_FUZZY_MATCH_IMPLEMENTATION namespace fts { @@ -245,6 +246,11 @@ namespace fts { } } // namespace fts +BF_EXPORT bool BF_CALLTYPE fts_fuzzy_match(char const* pattern, char const* str, int& outScore, uint8_t* matches, int maxMatches) +{ + return fts::fuzzy_match(pattern, str, outScore, matches, maxMatches); +} + #endif // FTS_FUZZY_MATCH_IMPLEMENTATION #endif // FTS_FUZZY_MATCH_H From b70745ef1e4c972538e51ff304dc645f5ee2d4dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Fri, 17 Dec 2021 18:05:39 +0100 Subject: [PATCH 5/8] Merge branch 'master' of https://github.com/beefytech/Beef into FuzzyAutoComplete --- BeefLibs/Beefy2D/src/widgets/Dialog.bf | 3 + BeefLibs/corlib/src/Collections/Dictionary.bf | 43 +- BeefLibs/corlib/src/Compiler.bf | 190 +++- BeefLibs/corlib/src/Double.bf | 3 +- BeefLibs/corlib/src/GC.bf | 2 +- BeefLibs/corlib/src/IO/BufferedStream.bf | 21 +- BeefLibs/corlib/src/IO/FileDialog.bf | 580 ++++++++++++ BeefLibs/corlib/src/IO/FileStream.bf | 38 +- BeefLibs/corlib/src/IO/FolderBrowserDialog.bf | 149 +-- BeefLibs/corlib/src/IO/OpenFileDialog.bf | 521 ++--------- BeefLibs/corlib/src/IO/SaveFileDialog.bf | 42 + BeefLibs/corlib/src/Windows.bf | 209 +++++ BeefySysLib/util/Array.h | 4 +- IDE/Tests/CompileFail001/src/Generics.bf | 2 +- IDE/mintest/minlib/src/System/Compiler.bf | 5 + IDE/src/CommandQueueManager.bf | 16 +- IDE/src/Compiler/BfCompiler.bf | 38 +- IDE/src/Project.bf | 16 +- IDE/src/ui/ClassViewPanel.bf | 4 + IDE/src/ui/GenerateDialog.bf | 882 ++++++++++++++++++ IDE/src/ui/OutputPanel.bf | 2 +- IDE/src/ui/ProjectPanel.bf | 105 ++- IDE/src/ui/SourceViewPanel.bf | 5 + IDE/src/ui/WatchPanel.bf | 2 +- IDEHelper/Backend/BeCOFFObject.cpp | 18 +- IDEHelper/Compiler/BfAutoComplete.cpp | 2 +- IDEHelper/Compiler/BfCompiler.cpp | 275 +++++- IDEHelper/Compiler/BfCompiler.h | 11 +- IDEHelper/Compiler/BfContext.cpp | 26 +- IDEHelper/Compiler/BfExprEvaluator.cpp | 77 +- IDEHelper/Compiler/BfIRBuilder.cpp | 62 +- IDEHelper/Compiler/BfIRBuilder.h | 1 + IDEHelper/Compiler/BfModule.cpp | 95 +- IDEHelper/Compiler/BfModule.h | 1 + IDEHelper/Compiler/BfModuleTypeUtils.cpp | 126 ++- IDEHelper/Compiler/BfParser.cpp | 66 +- IDEHelper/Compiler/BfParser.h | 1 + IDEHelper/Compiler/BfPrinter.cpp | 54 +- IDEHelper/Compiler/BfPrinter.h | 1 + IDEHelper/Compiler/BfReducer.cpp | 7 +- IDEHelper/Compiler/BfResolvedTypeUtils.cpp | 16 +- IDEHelper/Compiler/BfResolvedTypeUtils.h | 5 +- IDEHelper/Compiler/BfSystem.cpp | 50 +- IDEHelper/Compiler/BfSystem.h | 1 + IDEHelper/Compiler/CeMachine.cpp | 39 +- IDEHelper/Compiler/CeMachine.h | 7 + IDEHelper/Tests/src/Comptime.bf | 60 ++ IDEHelper/WinDebugger.cpp | 10 +- 48 files changed, 2975 insertions(+), 918 deletions(-) create mode 100644 BeefLibs/corlib/src/IO/FileDialog.bf create mode 100644 IDE/src/ui/GenerateDialog.bf diff --git a/BeefLibs/Beefy2D/src/widgets/Dialog.bf b/BeefLibs/Beefy2D/src/widgets/Dialog.bf index b879a7e0..01723f6b 100644 --- a/BeefLibs/Beefy2D/src/widgets/Dialog.bf +++ b/BeefLibs/Beefy2D/src/widgets/Dialog.bf @@ -256,7 +256,10 @@ namespace Beefy.widgets public virtual void PopupWindow(WidgetWindow parentWindow, float offsetX = 0, float offsetY = 0) { if (mClosed) + { + BFApp.sApp.DeferDelete(this); return; + } mInPopupWindow = true; diff --git a/BeefLibs/corlib/src/Collections/Dictionary.bf b/BeefLibs/corlib/src/Collections/Dictionary.bf index 493a3309..1aaa7ccf 100644 --- a/BeefLibs/corlib/src/Collections/Dictionary.bf +++ b/BeefLibs/corlib/src/Collections/Dictionary.bf @@ -578,6 +578,43 @@ namespace System.Collections return oldPtr; } + private bool RemoveEntry(int32 hashCode, int_cosize index) + { + if (mBuckets != null) + { + int bucket = hashCode % (int_cosize)mAllocSize; + int lastIndex = -1; + + for (int_cosize i = mBuckets[bucket]; i >= 0; lastIndex = i, i = mEntries[i].mNext) + { + if (i == index) + { + if (lastIndex < 0) + { + mBuckets[bucket] = mEntries[index].mNext; + } + else + { + mEntries[lastIndex].mNext = mEntries[index].mNext; + } + mEntries[index].mHashCode = -1; + mEntries[index].mNext = mFreeList; +#if BF_ENABLE_REALTIME_LEAK_CHECK + mEntries[index].mKey = default; + mEntries[index].mValue = default; +#endif + mFreeList = index; + mFreeCount++; +#if VERSION_DICTIONARY + mVersion++; +#endif + return true; + } + } + } + return false; + } + public bool Remove(TKey key) { if (mBuckets != null) @@ -901,7 +938,7 @@ namespace System.Collections public void Remove() mut { int_cosize curIdx = mIndex - 1; - mDictionary.Remove(mDictionary.mEntries[curIdx].mKey); + mDictionary.RemoveEntry(mDictionary.mEntries[curIdx].mHashCode, curIdx); #if VERSION_DICTIONARY mVersion = mDictionary.mVersion; #endif @@ -1052,7 +1089,7 @@ namespace System.Collections public void Remove() mut { int_cosize curIdx = mIndex - 1; - mDictionary.Remove(mDictionary.mEntries[curIdx].mKey); + mDictionary.RemoveEntry(mDictionary.mEntries[curIdx].mHashCode, curIdx); #if VERSION_DICTIONARY mVersion = mDictionary.mVersion; #endif @@ -1158,7 +1195,7 @@ namespace System.Collections public void Remove() mut { int_cosize curIdx = mIndex - 1; - mDictionary.Remove(mDictionary.mEntries[curIdx].mKey); + mDictionary.RemoveEntry(mDictionary.mEntries[curIdx].mHashCode, curIdx); #if VERSION_DICTIONARY mVersion = mDictionary.mVersion; #endif diff --git a/BeefLibs/corlib/src/Compiler.bf b/BeefLibs/corlib/src/Compiler.bf index 45a8e3c3..3030c444 100644 --- a/BeefLibs/corlib/src/Compiler.bf +++ b/BeefLibs/corlib/src/Compiler.bf @@ -1,8 +1,189 @@ using System.Reflection; +using System.Diagnostics; +using System.Collections; +using System.Security.Cryptography; + namespace System { static class Compiler { + public abstract class Generator + { + public enum Flags + { + None = 0, + AllowRegenerate = 1 + } + + public String mCmdInfo = new String() ~ delete _; + public Dictionary mParams = new .() ~ delete _; + public abstract String Name { get; } + + public StringView ProjectName => mParams["ProjectName"]; + public StringView ProjectDir => mParams["ProjectDir"]; + public StringView FolderDir => mParams["FolderDir"]; + public StringView Namespace => mParams["Namespace"]; + public StringView DefaultNamespace => mParams["DefaultNamespace"]; + public StringView WorkspaceName => mParams["WorkspaceName"]; + public StringView WorkspaceDir => mParams["WorkspaceDir"]; + public StringView DateTime => mParams["DateTime"]; + public bool IsRegenerating => mParams.GetValueOrDefault("Regenerating") == "True"; + + public void Fail(StringView error) + { + mCmdInfo.AppendF("error\t"); + error.QuoteString(mCmdInfo); + mCmdInfo.Append("\n"); + } + + public void AddEdit(StringView dataName, StringView label, StringView defaultValue) + { + mCmdInfo.AppendF($"addEdit\t"); + dataName.QuoteString(mCmdInfo); + mCmdInfo.Append("\t"); + label.QuoteString(mCmdInfo); + mCmdInfo.Append("\t"); + defaultValue.QuoteString(mCmdInfo); + mCmdInfo.Append("\n"); + } + + public void AddCombo(StringView dataName, StringView label, StringView defaultValue, Span values) + { + mCmdInfo.AppendF($"addCombo\t"); + dataName.QuoteString(mCmdInfo); + mCmdInfo.Append("\t"); + label.QuoteString(mCmdInfo); + mCmdInfo.Append("\t"); + defaultValue.QuoteString(mCmdInfo); + for (var value in values) + { + mCmdInfo.Append("\t"); + value.QuoteString(mCmdInfo); + } + mCmdInfo.Append("\n"); + } + + public void AddCheckbox(StringView dataName, StringView label, bool defaultValue) + { + mCmdInfo.AppendF($"addCheckbox\t"); + dataName.QuoteString(mCmdInfo); + mCmdInfo.Append("\t"); + label.QuoteString(mCmdInfo); + mCmdInfo.AppendF($"\t{defaultValue}\n"); + } + + public bool GetString(StringView key, String outVal) + { + if (mParams.TryGetAlt(key, var matchKey, var value)) + { + outVal.Append(value); + return true; + } + return false; + } + + public virtual void InitUI() + { + } + + public virtual void Generate(String outFileName, String outText, ref Flags generateFlags) + { + } + + static String GetName() where T : Generator + { + T val = scope T(); + String str = val.Name; + return str; + } + + void HandleArgs(String args) + { + for (var line in args.Split('\n', .RemoveEmptyEntries)) + { + int tabPos = line.IndexOf('\t'); + var key = line.Substring(0, tabPos); + var value = line.Substring(tabPos + 1); + if (mParams.TryAdd(key, var keyPtr, var valuePtr)) + { + *keyPtr = key; + *valuePtr = value; + } + } + } + + static String InitUI(String args) where T : Generator + { + T val = scope T(); + val.HandleArgs(args); + val.InitUI(); + return val.mCmdInfo; + } + + static String Generate(String args) where T : Generator + { + T val = scope T(); + val.HandleArgs(args); + String fileName = scope .(); + String outText = scope .(); + Flags flags = .None; + val.Generate(fileName, outText, ref flags); + val.mCmdInfo.Append("fileName\t"); + fileName.QuoteString(val.mCmdInfo); + val.mCmdInfo.Append("\n"); + val.mCmdInfo.Append("data\n"); + + if (flags.HasFlag(.AllowRegenerate)) + { + bool writeArg = false; + for (var line in args.Split('\n', .RemoveEmptyEntries)) + { + int tabPos = line.IndexOf('\t'); + var key = line.Substring(0, tabPos); + var value = line.Substring(tabPos + 1); + + if (key == "Generator") + writeArg = true; + if (writeArg) + { + val.mCmdInfo.AppendF($"// {key}={value}\n"); + } + } + var hash = MD5.Hash(.((.)outText.Ptr, outText.Length)); + val.mCmdInfo.AppendF($"// GenHash={hash}\n\n"); + } + val.mCmdInfo.Append(outText); + return val.mCmdInfo; + } + } + + public class NewClassGenerator : Generator + { + public override String Name => "New Class"; + + public override void InitUI() + { + AddEdit("name", "Class Name", ""); + } + + public override void Generate(String outFileName, String outText, ref Flags generateFlags) + { + var name = mParams["name"]; + if (name.EndsWith(".bf", .OrdinalIgnoreCase)) + name.RemoveFromEnd(3); + outFileName.Append(name); + outText.AppendF( + $""" + namespace {Namespace} + {{ + class {name} + {{ + }} + }} + """); + } + } + public struct MethodBuilder { void* mNative; @@ -43,7 +224,7 @@ namespace System public static extern String CallerProject; [LinkName("#CallerExpression")] - public static extern String[0x0FFFFFFF] CallerExpression; + public static extern String[0x00FFFFFF] CallerExpression; [LinkName("#ProjectName")] public static extern String ProjectName; @@ -76,6 +257,7 @@ namespace System static extern void* Comptime_MethodBuilder_EmitStr(void* native, StringView str); static extern void* Comptime_CreateMethod(int32 typeId, StringView methodName, Type returnType, MethodFlags methodFlags); static extern void Comptime_EmitTypeBody(int32 typeId, StringView text); + static extern void Comptime_EmitAddInterface(int32 typeId, int32 ifaceTypeId); static extern void Comptime_EmitMethodEntry(int64 methodHandle, StringView text); static extern void Comptime_EmitMethodExit(int64 methodHandle, StringView text); static extern void Comptime_EmitMixin(StringView text); @@ -94,6 +276,12 @@ namespace System Comptime_EmitTypeBody((.)owner.TypeId, text); } + [Comptime(OnlyFromComptime=true)] + public static void EmitAddInterface(Type owner, Type iface) + { + Comptime_EmitAddInterface((.)owner.TypeId, (.)iface.TypeId); + } + [Comptime(OnlyFromComptime=true)] public static void EmitMethodEntry(ComptimeMethodInfo methodHandle, StringView text) { diff --git a/BeefLibs/corlib/src/Double.bf b/BeefLibs/corlib/src/Double.bf index 63176ab3..5c75b453 100644 --- a/BeefLibs/corlib/src/Double.bf +++ b/BeefLibs/corlib/src/Double.bf @@ -21,8 +21,7 @@ namespace System public const double NegativeInfinity = (double)(- 1.0 / (double)(0.0)); public const double PositiveInfinity = (double)1.0 / (double)(0.0); public const double NaN = (double)0.0 / (double)0.0; - - static double NegativeZero = BitConverter.Convert(0x8000000000000000UL); + public const double NegativeZero = -0.0; public static int operator<=>(Double a, Double b) { diff --git a/BeefLibs/corlib/src/GC.bf b/BeefLibs/corlib/src/GC.bf index 231642a3..98055aa4 100644 --- a/BeefLibs/corlib/src/GC.bf +++ b/BeefLibs/corlib/src/GC.bf @@ -163,7 +163,7 @@ namespace System public static mixin Mark(T val) where T : struct { #if BF_ENABLE_REALTIME_LEAK_CHECK - val.[Friend]GCMarkMembers(); + val.[Friend, Unbound]GCMarkMembers(); #endif } diff --git a/BeefLibs/corlib/src/IO/BufferedStream.bf b/BeefLibs/corlib/src/IO/BufferedStream.bf index 08bde0ac..be432b08 100644 --- a/BeefLibs/corlib/src/IO/BufferedStream.bf +++ b/BeefLibs/corlib/src/IO/BufferedStream.bf @@ -19,7 +19,7 @@ namespace System.IO set { - mPos = Math.Min(value, Length); + mPos = value; } } @@ -43,20 +43,16 @@ namespace System.IO public override Result Seek(int64 pos, SeekKind seekKind = .Absolute) { - int64 length = Length; - - int64 newPos; switch (seekKind) { case .Absolute: - mPos = Math.Min(pos, length); - if (pos > length) - return .Err; + mPos = pos; case .FromEnd: - newPos = length - pos; + mPos = Length + pos; case .Relative: - mPos = Math.Min(mPos + pos, length); + mPos = mPos + pos; } + return .Ok; } @@ -176,7 +172,12 @@ namespace System.IO public override Result Close() { - return Flush(); + let ret = Flush(); + + mPos = 0; + mBufferPos = -Int32.MinValue; + mBufferEnd = -Int32.MinValue; + return ret; } } } diff --git a/BeefLibs/corlib/src/IO/FileDialog.bf b/BeefLibs/corlib/src/IO/FileDialog.bf new file mode 100644 index 00000000..1f81f2bd --- /dev/null +++ b/BeefLibs/corlib/src/IO/FileDialog.bf @@ -0,0 +1,580 @@ +// This file contains portions of code released by Microsoft under the MIT license as part +// of an open-sourcing initiative in 2014 of the C# core libraries. +// The original source was submitted to https://github.com/Microsoft/referencesource + +using System.Text; +using System.Collections; +using System.Threading; +using System.Diagnostics; + +#if BF_PLATFORM_WINDOWS +namespace System.IO +{ + enum DialogResult + { + None = 0, + OK = 1, + Cancel = 2 + } + + abstract class CommonDialog + { + public Windows.HWnd mHWnd; + public Windows.HWnd mDefaultControlHwnd; + public int mDefWndProc; + + private const int32 CDM_SETDEFAULTFOCUS = Windows.WM_USER + 0x51; + + public static Dictionary sHookMap = new Dictionary() ~ + { + Debug.Assert(sHookMap.Count == 0); + delete _; + }; + public static Monitor sMonitor = new Monitor() ~ delete _; + + public Result ShowDialog(INativeWindow owner = null) + { + Windows.HWnd hwndOwner = 0; + if (owner != null) + hwndOwner = (.)owner.Handle; + //Native.WndProc wndProc = scope => OwnerWndProc; + + //mDefWndProc = Native.SetWindowLong(mHWnd, Native.GWL_WNDPROC, (intptr)wndProc.GetFuncPtr().Value); + + var result = RunDialog(hwndOwner); + return result; + } + + public virtual int OwnerWndProc(Windows.HWnd hWnd, int32 msg, int wParam, int lParam) + { + return Windows.CallWindowProcW(mDefWndProc, hWnd, msg, wParam, lParam); + } + + protected virtual int HookProc(Windows.HWnd hWnd, int32 msg, int wParam, int lparam) + { + if (msg == Windows.WM_INITDIALOG) + { + //TODO: MoveToScreenCenter(hWnd); + // Under some circumstances, the dialog + // does not initially focus on any control. We fix that by explicitly + // setting focus ourselves. See ASURT 39435. + // + mDefaultControlHwnd = (Windows.HWnd)wParam; + if (mDefaultControlHwnd != 0) + Windows.SetFocus(mDefaultControlHwnd); + } + else if (msg == Windows.WM_SETFOCUS) + { + Windows.PostMessageW(hWnd, CDM_SETDEFAULTFOCUS, 0, 0); + } + else if (msg == CDM_SETDEFAULTFOCUS) + { + // If the dialog box gets focus, bounce it to the default control. + // so we post a message back to ourselves to wait for the focus change then push it to the default + // control. See ASURT 84016. + // + if (mDefaultControlHwnd != 0) + Windows.SetFocus(mDefaultControlHwnd); + } + return 0; + } + + protected abstract Result RunDialog(Windows.HWnd hWndOwner); + } + + abstract class FileDialog : CommonDialog + { + protected abstract Result RunFileDialog(ref Windows.OpenFileName ofn); + + protected override Result RunDialog(Windows.HWnd hWndOwner) + { + if (TryRunDialogVista(hWndOwner) case .Ok(let result)) + return .Ok(result); + + return RunDialogOld(hWndOwner); + } + + private const int32 FILEBUFSIZE = 8192; + protected const int32 OPTION_ADDEXTENSION = (int32)0x80000000; + + protected int32 mOptions; + private String mTitle ~ delete _; + private String mInitialDir ~ delete _; + private String mDefaultExt ~ delete _; + protected String[] mFileNames ~ DeleteContainerAndItems!(_); + private bool mSecurityCheckFileNames; + private String mFilter ~ delete _; + private String mFilterBuffer = new String() ~ delete _; + private int32 mFilterIndex; + private bool mSupportMultiDottedExtensions; + private bool mIgnoreSecondFileOkNotification; // Used for VS Whidbey 95342 + private int32 mOKNotificationCount; // Same + //private String char8Buffer = new String(FILEBUFSIZE) ~ delete _; + + public this() + { + Reset(); + } + + public virtual void Reset() + { + DeleteAndNullify!(mTitle); + DeleteAndNullify!(mInitialDir); + DeleteAndNullify!(mDefaultExt); + DeleteContainerAndItems!(mFileNames); + mFileNames = null; + DeleteAndNullify!(mFilter); + mFilterIndex = 1; + mSupportMultiDottedExtensions = false; + mOptions = Windows.OFN_HIDEREADONLY | Windows.OFN_PATHMUSTEXIST | + OPTION_ADDEXTENSION; + } + + + + protected int32 Options + { + get + { + return mOptions & ( + Windows.OFN_READONLY | + Windows.OFN_HIDEREADONLY | + Windows.OFN_NOCHANGEDIR | + Windows.OFN_SHOWHELP | + Windows.OFN_NOVALIDATE | + Windows.OFN_ALLOWMULTISELECT | + Windows.OFN_PATHMUSTEXIST | + Windows.OFN_FILEMUSTEXIST | + Windows.OFN_NODEREFERENCELINKS | + Windows.OFN_OVERWRITEPROMPT); + //return mOptions; + } + } + + public StringView Title + { + set + { + String.NewOrSet!(mTitle, value); + } + + get + { + return mTitle; + } + } + + public StringView InitialDirectory + { + set + { + String.NewOrSet!(mInitialDir, value); + } + + get + { + return mInitialDir; + } + } + + public String[] FileNames + { + get + { + return mFileNames; + } + } + + public StringView FileName + { + set + { + if (mFileNames == null) + { + mFileNames = new String[](new String(value)); + } + } + } + + public bool AddExtension + { + get + { + return GetOption(OPTION_ADDEXTENSION); + } + + set + { + SetOption(OPTION_ADDEXTENSION, value); + } + } + + public virtual bool CheckFileExists + { + get + { + return GetOption(Windows.OFN_FILEMUSTEXIST); + } + + set + { + SetOption(Windows.OFN_FILEMUSTEXIST, value); + } + } + + public bool DereferenceLinks + { + get + { + return !GetOption(Windows.OFN_NODEREFERENCELINKS); + } + set + { + SetOption(Windows.OFN_NODEREFERENCELINKS, !value); + } + } + + public bool CheckPathExists + { + get + { + return GetOption(Windows.OFN_PATHMUSTEXIST); + } + + set + { + SetOption(Windows.OFN_PATHMUSTEXIST, value); + } + } + + public bool Multiselect + { + get + { + return GetOption(Windows.OFN_ALLOWMULTISELECT); + } + + set + { + SetOption(Windows.OFN_ALLOWMULTISELECT, value); + } + } + + public bool ValidateNames + { + get + { + return !GetOption(Windows.OFN_NOVALIDATE); + } + + set + { + SetOption(Windows.OFN_NOVALIDATE, !value); + } + } + + public StringView DefaultExt + { + get + { + return mDefaultExt == null ? "" : mDefaultExt; + } + + set + { + delete mDefaultExt; + mDefaultExt = null; + + //if (!String.IsNullOrEmpty(value)) + if (value.Length > 0) + { + mDefaultExt = new String(value); + if (mDefaultExt.StartsWith(".")) + mDefaultExt.Remove(0, 1); + } + } + } + + public void GetFilter(String outFilter) + { + if (mFilter != null) + outFilter.Append(mFilter); + } + + public Result SetFilter(StringView value) + { + String useValue = scope String(value); + if (useValue != null && useValue.Length > 0) + { + var formats = String.StackSplit!(useValue, '|'); + if (formats == null || formats.Count % 2 != 0) + { + return .Err; + } + /// + /*String[] formats = value.Split('|'); + if (formats == null || formats.Length % 2 != 0) + { + throw new ArgumentException(SR.GetString(SR.FileDialogInvalidFilter)); + }*/ + String.NewOrSet!(mFilter, useValue); + } + else + { + useValue = null; + DeleteAndNullify!(mFilter); + } + + return .Ok; + } + + protected bool GetOption(int32 option) + { + return (mOptions & option) != 0; + } + + protected void SetOption(int32 option, bool value) + { + if (value) + { + mOptions |= option; + } + else + { + mOptions &= ~option; + } + } + + private static Result MakeFilterString(String s, bool dereferenceLinks, String filterBuffer) + { + String useStr = s; + if (useStr == null || useStr.Length == 0) + { + // Workaround for Whidbey bug #5165 + // Apply the workaround only when DereferenceLinks is true and OS is at least WinXP. + if (dereferenceLinks && System.Environment.OSVersion.Version.Major >= 5) + { + useStr = " |*.*"; + } + else if (useStr == null) + { + return .Err; + } + } + + filterBuffer.Set(s); + for (int32 i = 0; i < filterBuffer.Length; i++) + if (filterBuffer[i] == '|') + filterBuffer[i] = (char8)0; + filterBuffer.Append((char8)0); + return .Ok; + } + + private Result RunDialogOld(Windows.HWnd hWndOwner) + { + //RunDialogTest(hWndOwner); + + Windows.WndProc hookProcPtr = => StaticHookProc; + Windows.OpenFileName ofn = Windows.OpenFileName(); + + char16[FILEBUFSIZE] char16Buffer = .(0, ?); + + if (mFileNames != null && !mFileNames.IsEmpty) + { + //int len = UTF16.GetEncodedLen(fileNames[0]); + //char16Buffer = scope:: char16[len + 1]*; + UTF16.Encode(mFileNames[0], (char16*)&char16Buffer, FILEBUFSIZE); + } + // Degrade to the older style dialog if we're not on Win2K. + // We do this by setting the struct size to a different value + // + + if (Environment.OSVersion.Platform != System.PlatformID.Win32NT || + Environment.OSVersion.Version.Major < 5) { + ofn.mStructSize = 0x4C; + } + ofn.mHwndOwner = hWndOwner; + ofn.mHInstance = (Windows.HInstance)Windows.GetModuleHandleW(null); + + if (mFilter != null) + { + Try!(MakeFilterString(mFilter, this.DereferenceLinks, mFilterBuffer)); + ofn.mFilter = mFilterBuffer.ToScopedNativeWChar!::(); + } + ofn.nFilterIndex = mFilterIndex; + ofn.mFile = (char16*)&char16Buffer; + ofn.nMaxFile = FILEBUFSIZE; + if (mInitialDir != null) + ofn.mInitialDir = mInitialDir.ToScopedNativeWChar!::(); + if (mTitle != null) + ofn.mTitle = mTitle.ToScopedNativeWChar!::(); + ofn.mFlags = Options | (Windows.OFN_EXPLORER | Windows.OFN_ENABLEHOOK | Windows.OFN_ENABLESIZING); + ofn.mHook = hookProcPtr; + ofn.mCustData = (int)Internal.UnsafeCastToPtr(this); + ofn.mFlagsEx = Windows.OFN_USESHELLITEM; + if (mDefaultExt != null && AddExtension) + ofn.mDefExt = mDefaultExt.ToScopedNativeWChar!::(); + + DeleteContainerAndItems!(mFileNames); + mFileNames = null; + //Security checks happen here + return RunFileDialog(ref ofn); + } + + static int StaticHookProc(Windows.HWnd hWnd, int32 msg, int wParam, int lparam) + { + if (msg == Windows.WM_INITDIALOG) + { + using (sMonitor.Enter()) + { + var ofn = (Windows.OpenFileName*)(void*)lparam; + sHookMap[(int)hWnd] = (CommonDialog)Internal.UnsafeCastToObject((void*)ofn.mCustData); + } + } + + CommonDialog dlg; + using (sMonitor.Enter()) + { + sHookMap.TryGetValue((int)hWnd, out dlg); + } + if (dlg == null) + return 0; + + dlg.[Friend]HookProc(hWnd, msg, wParam, lparam); + if (msg == Windows.WM_DESTROY) + { + using (sMonitor.Enter()) + { + sHookMap.Remove((int)hWnd); + } + } + return 0; + } + //TODO: Add ProcessFileNames for validation + + protected abstract Result CreateVistaDialog(); + + private Result TryRunDialogVista(Windows.HWnd hWndOwner) + { + Windows.COM_IFileDialog* dialog; + if (!(CreateVistaDialog() case .Ok(out dialog))) + return .Err; + + OnBeforeVistaDialog(dialog); + dialog.VT.Show(dialog, hWndOwner); + + List files = scope .(); + ProcessVistaFiles(dialog, files); + + DeleteContainerAndItems!(mFileNames); + mFileNames = new String[files.Count]; + files.CopyTo(mFileNames); + + dialog.VT.Release(dialog); + + return .Ok(files.IsEmpty ? .Cancel : .OK); + } + + private void OnBeforeVistaDialog(Windows.COM_IFileDialog* dialog) + { + dialog.VT.SetDefaultExtension(dialog, DefaultExt.ToScopedNativeWChar!()); + + if (mFileNames != null && !mFileNames.IsEmpty) + dialog.VT.SetFileName(dialog, mFileNames[0].ToScopedNativeWChar!()); + + if (!String.IsNullOrEmpty(mInitialDir)) + { + Windows.COM_IShellItem* folderShellItem = null; + Windows.SHCreateItemFromParsingName(mInitialDir.ToScopedNativeWChar!(), null, Windows.COM_IShellItem.sIID, (void**)&folderShellItem); + if (folderShellItem != null) + { + dialog.VT.SetDefaultFolder(dialog, folderShellItem); + dialog.VT.SetFolder(dialog, folderShellItem); + folderShellItem.VT.Release(folderShellItem); + } + } + + dialog.VT.SetTitle(dialog, mTitle.ToScopedNativeWChar!()); + dialog.VT.SetOptions(dialog, GetOptions()); + SetFileTypes(dialog); + } + + private Windows.COM_IFileDialog.FOS GetOptions() + { + const Windows.COM_IFileDialog.FOS BlittableOptions = + Windows.COM_IFileDialog.FOS.OVERWRITEPROMPT + | Windows.COM_IFileDialog.FOS.NOCHANGEDIR + | Windows.COM_IFileDialog.FOS.NOVALIDATE + | Windows.COM_IFileDialog.FOS.ALLOWMULTISELECT + | Windows.COM_IFileDialog.FOS.PATHMUSTEXIST + | Windows.COM_IFileDialog.FOS.FILEMUSTEXIST + | Windows.COM_IFileDialog.FOS.CREATEPROMPT + | Windows.COM_IFileDialog.FOS.NODEREFERENCELINKS; + + const int32 UnexpectedOptions = + (int32)(Windows.OFN_SHOWHELP // If ShowHelp is true, we don't use the Vista Dialog + | Windows.OFN_ENABLEHOOK // These shouldn't be set in options (only set in the flags for the legacy dialog) + | Windows.OFN_ENABLESIZING // These shouldn't be set in options (only set in the flags for the legacy dialog) + | Windows.OFN_EXPLORER); // These shouldn't be set in options (only set in the flags for the legacy dialog) + + Debug.Assert((UnexpectedOptions & mOptions) == 0, "Unexpected FileDialog options"); + + Windows.COM_IFileDialog.FOS ret = (Windows.COM_IFileDialog.FOS)mOptions & BlittableOptions; + + // Force no mini mode for the SaveFileDialog + ret |= Windows.COM_IFileDialog.FOS.DEFAULTNOMINIMODE; + + // Make sure that the Open dialog allows the user to specify + // non-file system locations. This flag will cause the dialog to copy the resource + // to a local cache (Temporary Internet Files), and return that path instead. This + // also affects the Save dialog by disallowing navigation to these areas. + // An example of a non-file system location is a URL (http://), or a file stored on + // a digital camera that is not mapped to a drive letter. + // This reproduces the behavior of the "classic" Open and Save dialogs. + ret |= Windows.COM_IFileDialog.FOS.FORCEFILESYSTEM; + + return ret; + } + + protected abstract void ProcessVistaFiles(Windows.COM_IFileDialog* dialog, List files); + + private Result SetFileTypes(Windows.COM_IFileDialog* dialog) + { + List filterItems = scope .(); + + // Expected input types + // "Text files (*.txt)|*.txt|All files (*.*)|*.*" + // "Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|All files (*.*)|*.*" + if (!String.IsNullOrEmpty(mFilter)) + { + StringView[] tokens = mFilter.Split!('|'); + if (0 == tokens.Count % 2) + { + // All even numbered tokens should be labels + // Odd numbered tokens are the associated extensions + for (int i = 1; i < tokens.Count; i += 2) + { + Windows.COMDLG_FILTERSPEC ext; + ext.pszSpec = tokens[i].ToScopedNativeWChar!::(); // This may be a semicolon delimited list of extensions (that's ok) + ext.pszName = tokens[i - 1].ToScopedNativeWChar!::(); + filterItems.Add(ext); + } + } + } + + if (filterItems.IsEmpty) + return .Ok; + + Windows.COM_IUnknown.HResult hr = dialog.VT.SetFileTypes(dialog, (uint32)filterItems.Count, filterItems.Ptr); + if (hr.Failed) + return .Err; + + hr = dialog.VT.SetFileTypeIndex(dialog, (uint32)mFilterIndex); + if (hr.Failed) + return .Err; + + return .Ok; + } + } +} +#endif \ No newline at end of file diff --git a/BeefLibs/corlib/src/IO/FileStream.bf b/BeefLibs/corlib/src/IO/FileStream.bf index 0be6c773..de46955a 100644 --- a/BeefLibs/corlib/src/IO/FileStream.bf +++ b/BeefLibs/corlib/src/IO/FileStream.bf @@ -296,6 +296,15 @@ namespace System.IO } } + public override int64 Position + { + set + { + // Matches the behavior of Platform.BfpFile_Seek(mBfpFile, value, .Absolute); + mPos = Math.Max(value, 0); + } + } + public this() { @@ -416,16 +425,37 @@ namespace System.IO mFileAccess = access; } + public override Result Seek(int64 pos, SeekKind seekKind = .Absolute) + { + int64 newPos; + switch (seekKind) + { + case .Absolute: + newPos = pos; + case .FromEnd: + newPos = Length + pos; + case .Relative: + newPos = mPos + pos; + } + + // Matches the behaviour of Platform.BfpFile_Seek(mBfpFile, value, .Absolute); + mPos = Math.Max(newPos, 0); + if (seekKind == .Absolute && newPos < 0) + return .Err; + + return .Ok; + } + public override Result Close() { - var hadError = Flush() case .Err; + let ret = base.Close(); if (mBfpFile != null) Platform.BfpFile_Release(mBfpFile); + mBfpFile = null; mFileAccess = default; - if (hadError) - return .Err; - return .Ok; + mBfpFilePos = 0; + return ret; } protected override void UpdateLength() diff --git a/BeefLibs/corlib/src/IO/FolderBrowserDialog.bf b/BeefLibs/corlib/src/IO/FolderBrowserDialog.bf index 91844228..420757ac 100644 --- a/BeefLibs/corlib/src/IO/FolderBrowserDialog.bf +++ b/BeefLibs/corlib/src/IO/FolderBrowserDialog.bf @@ -57,7 +57,7 @@ namespace System.IO ShowNewFolderButton = true; } - protected Result RunDialog_New(Windows.HWnd hWndOwner, FolderBrowserDialog.COM_IFileDialog* fileDialog) + protected Result RunDialog_New(Windows.HWnd hWndOwner, Windows.COM_IFileDialog* fileDialog) { //COM_IFileDialogEvents evts; /*COM_IFileDialogEvents.VTable funcs; @@ -106,8 +106,8 @@ namespace System.IO if (!mSelectedPath.IsEmpty) { - COM_IShellItem* folderShellItem = null; - Windows.SHCreateItemFromParsingName(mSelectedPath.ToScopedNativeWChar!(), null, COM_IShellItem.sIID, (void**)&folderShellItem); + Windows.COM_IShellItem* folderShellItem = null; + Windows.SHCreateItemFromParsingName(mSelectedPath.ToScopedNativeWChar!(), null, Windows.COM_IShellItem.sIID, (void**)&folderShellItem); if (folderShellItem != null) { fileDialog.VT.SetDefaultFolder(fileDialog, folderShellItem); @@ -121,7 +121,7 @@ namespace System.IO DialogResult result = .Cancel; mSelectedPath.Clear(); - COM_IShellItem* shellItem = null; + Windows.COM_IShellItem* shellItem = null; fileDialog.VT.GetResult(fileDialog, out shellItem); if (shellItem != null) { @@ -142,10 +142,10 @@ namespace System.IO protected override Result RunDialog(Windows.HWnd hWndOwner) { - FolderBrowserDialog.COM_IFileDialog* fileDialog = null; + Windows.COM_IFileDialog* fileDialog = null; Windows.COM_IUnknown.HResult hr; //if (mFolderKind == .Open) - hr = Windows.COM_IUnknown.CoCreateInstance(ref FolderBrowserDialog.COM_IFileDialog.sCLSID, null, .INPROC_SERVER, ref FolderBrowserDialog.COM_IFileDialog.sIID, (void**)&fileDialog); + hr = Windows.COM_IUnknown.CoCreateInstance(ref Windows.COM_IFileDialog.sCLSID, null, .INPROC_SERVER, ref Windows.COM_IFileDialog.sIID, (void**)&fileDialog); //else //hr = Windows.COM_IUnknown.CoCreateInstance(ref FolderBrowserDialog.COM_FileSaveDialog.sCLSID, null, .INPROC_SERVER, ref FolderBrowserDialog.COM_FileSaveDialog.sIID, (void**)&fileDialog); if (hr == 0) @@ -219,143 +219,6 @@ namespace System.IO } return 0; } - - struct FDE_SHAREVIOLATION_RESPONSE; - struct FDE_OVERWRITE_RESPONSE; - - struct COM_IFileDialogEvents : Windows.COM_IUnknown - { - public struct VTable : Windows.COM_IUnknown.VTable - { - public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog) OnFileOk; - public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog, COM_IShellItem* psiFolder) OnFolderChanging; - public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog) OnFolderChange; - public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog) OnSelectionChange; - public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog, FDE_SHAREVIOLATION_RESPONSE* pResponse) OnShareViolation; - public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog) OnTypeChange; - public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog, COM_IShellItem* shellItem, FDE_OVERWRITE_RESPONSE* response) OnOverwrite; - - } - } - - struct COM_IShellItem : Windows.COM_IUnknown - { - public static Guid sIID = .(0x43826d1e, 0xe718, 0x42ee, 0xbc, 0x55, 0xa1, 0xe2, 0x61, 0xc3, 0x7b, 0xfe); - - public enum SIGDN : uint32 - { - NORMALDISPLAY = 0x00000000, // SHGDN_NORMAL - PARENTRELATIVEPARSING = 0x80018001, // SHGDN_INFOLDER | SHGDN_FORPARSING - DESKTOPABSOLUTEPARSING = 0x80028000, // SHGDN_FORPARSING - PARENTRELATIVEEDITING = 0x80031001, // SHGDN_INFOLDER | SHGDN_FOREDITING - DESKTOPABSOLUTEEDITING = 0x8004c000, // SHGDN_FORPARSING | SHGDN_FORADDRESSBAR - FILESYSPATH = 0x80058000, // SHGDN_FORPARSING - URL = 0x80068000, // SHGDN_FORPARSING - PARENTRELATIVEFORADDRESSBAR = 0x8007c001, // SHGDN_INFOLDER | SHGDN_FORPARSING | SHGDN_FORADDRESSBAR - PARENTRELATIVE = 0x80080001 // SHGDN_INFOLDER - } - - public struct VTable : Windows.COM_IUnknown.VTable - { - public function HResult(COM_IShellItem* self, void* pbc, ref Guid bhid, ref Guid riid, void** ppv) BindToHandler; - public function HResult(COM_IShellItem* self, out COM_IShellItem* ppsi) GetParent; - public function HResult(COM_IShellItem* self, SIGDN sigdnName, out char16* ppszName) GetDisplayName; - public function HResult(COM_IShellItem* self, uint sfgaoMask, out uint psfgaoAttribs) GetAttributes; - public function HResult(COM_IShellItem* self, COM_IShellItem* psi, uint32 hint, out int32 piOrder) Compare; - - } - public new VTable* VT - { - get - { - return (.)mVT; - } - } - } - - struct COMDLG_FILTERSPEC - { - public char16* pszName; - public char16* pszSpec; - } - - enum FDAP : uint32 - { - FDAP_BOTTOM = 0x00000000, - FDAP_TOP = 0x00000001, - } - - public struct COM_IFileDialog : Windows.COM_IUnknown - { - public static Guid sIID = .(0x42f85136, 0xdb7e, 0x439c, 0x85, 0xf1, 0xe4, 0x07, 0x5d, 0x13, 0x5f, 0xc8); - public static Guid sCLSID = .(0xdc1c5a9c, 0xe88a, 0x4dde, 0xa5, 0xa1, 0x60, 0xf8, 0x2a, 0x20, 0xae, 0xf7); - - ///s - public enum FOS : uint32 - { - OVERWRITEPROMPT = 0x00000002, - STRICTFILETYPES = 0x00000004, - NOCHANGEDIR = 0x00000008, - PICKFOLDERS = 0x00000020, - FORCEFILESYSTEM = 0x00000040, - ALLNONSTORAGEITEMS = 0x00000080, - NOVALIDATE = 0x00000100, - ALLOWMULTISELECT = 0x00000200, - PATHMUSTEXIST = 0x00000800, - FILEMUSTEXIST = 0x00001000, - CREATEPROMPT = 0x00002000, - SHAREAWARE = 0x00004000, - NOREADONLYRETURN = 0x00008000, - NOTESTFILECREATE = 0x00010000, - HIDEMRUPLACES = 0x00020000, - HIDEPINNEDPLACES = 0x00040000, - NODEREFERENCELINKS = 0x00100000, - DONTADDTORECENT = 0x02000000, - FORCESHOWHIDDEN = 0x10000000, - DEFAULTNOMINIMODE = 0x20000000 - } - - public struct VTable : Windows.COM_IUnknown.VTable - { - public function HResult(COM_IFileDialog* self, Windows.HWnd parent) Show; - public function HResult(COM_IFileDialog* self, uint cFileTypes, COMDLG_FILTERSPEC* rgFilterSpec) SetFileTypes; - public function HResult(COM_IFileDialog* self, uint iFileType) SetFileTypeIndex; - public function HResult(COM_IFileDialog* self, out uint piFileType) GetFileTypeIndex; - public function HResult(COM_IFileDialog* self, COM_IFileDialogEvents* pfde, out uint pdwCookie) Advise; - public function HResult(COM_IFileDialog* self, uint dwCookie) Unadvise; - public function HResult(COM_IFileDialog* self, FOS fos) SetOptions; - public function HResult(COM_IFileDialog* self, out FOS pfos) GetOptions; - public function HResult(COM_IFileDialog* self, COM_IShellItem* psi) SetDefaultFolder; - public function HResult(COM_IFileDialog* self, COM_IShellItem* psi) SetFolder; - public function HResult(COM_IFileDialog* self, out COM_IShellItem* ppsi) GetFolder; - public function HResult(COM_IFileDialog* self, out COM_IShellItem* ppsi) GetCurrentSelection; - public function HResult(COM_IFileDialog* self, char16* pszName) SetFileName; - public function HResult(COM_IFileDialog* self, out char16* pszName) GetFileName; - public function HResult(COM_IFileDialog* self, char16* pszTitle) SetTitle; - public function HResult(COM_IFileDialog* self, char16* pszText) SetOkButtonLabel; - public function HResult(COM_IFileDialog* self, char16* pszLabel) SetFileNameLabel; - public function HResult(COM_IFileDialog* self, out COM_IShellItem* ppsi) GetResult; - public function HResult(COM_IFileDialog* self, COM_IShellItem* psi, FDAP fdap) AddPlace; - public function HResult(COM_IFileDialog* self, char16* pszDefaultExtension) SetDefaultExtension; - public function HResult(COM_IFileDialog* self, int hr) Close; - public function HResult(COM_IFileDialog* self, ref Guid guid) SetClientGuid; - public function HResult(COM_IFileDialog* self) ClearClientData; - public function HResult(COM_IFileDialog* self, void* pFilter) SetFilter; - } - public new VTable* VT - { - get - { - return (.)mVT; - } - } - } - - public struct COM_FileSaveDialog : COM_IFileDialog - { - public static new Guid sIID = .(0x84bccd23, 0x5fde, 0x4cdb, 0xae, 0xa4, 0xaf, 0x64, 0xb8, 0x3d, 0x78, 0xab); - public static new Guid sCLSID = .(0xC0B4E2F3, 0xBA21, 0x4773, 0x8D, 0xBA, 0x33, 0x5E, 0xC9, 0x46, 0xEB, 0x8B); - } } } #endif \ No newline at end of file diff --git a/BeefLibs/corlib/src/IO/OpenFileDialog.bf b/BeefLibs/corlib/src/IO/OpenFileDialog.bf index 3697c1c5..c7c79d11 100644 --- a/BeefLibs/corlib/src/IO/OpenFileDialog.bf +++ b/BeefLibs/corlib/src/IO/OpenFileDialog.bf @@ -11,460 +11,6 @@ using System.Text; namespace System.IO { - enum DialogResult - { - None = 0, - OK = 1, - Cancel = 2 - } - - abstract class CommonDialog - { - public Windows.HWnd mHWnd; - public Windows.HWnd mDefaultControlHwnd; - public int mDefWndProc; - - private const int32 CDM_SETDEFAULTFOCUS = Windows.WM_USER + 0x51; - - public static Dictionary sHookMap = new Dictionary() ~ - { - Debug.Assert(sHookMap.Count == 0); - delete _; - }; - public static Monitor sMonitor = new Monitor() ~ delete _; - - public Result ShowDialog(INativeWindow owner = null) - { - Windows.HWnd hwndOwner = 0; - if (owner != null) - hwndOwner = (.)owner.Handle; - //Native.WndProc wndProc = scope => OwnerWndProc; - - //mDefWndProc = Native.SetWindowLong(mHWnd, Native.GWL_WNDPROC, (intptr)wndProc.GetFuncPtr().Value); - - var result = RunDialog(hwndOwner); - return result; - } - - public virtual int OwnerWndProc(Windows.HWnd hWnd, int32 msg, int wParam, int lParam) - { - return Windows.CallWindowProcW(mDefWndProc, hWnd, msg, wParam, lParam); - } - - protected virtual int HookProc(Windows.HWnd hWnd, int32 msg, int wParam, int lparam) - { - if (msg == Windows.WM_INITDIALOG) - { - //TODO: MoveToScreenCenter(hWnd); - // Under some circumstances, the dialog - // does not initially focus on any control. We fix that by explicitly - // setting focus ourselves. See ASURT 39435. - // - mDefaultControlHwnd = (Windows.HWnd)wParam; - if (mDefaultControlHwnd != 0) - Windows.SetFocus(mDefaultControlHwnd); - } - else if (msg == Windows.WM_SETFOCUS) - { - Windows.PostMessageW(hWnd, CDM_SETDEFAULTFOCUS, 0, 0); - } - else if (msg == CDM_SETDEFAULTFOCUS) - { - // If the dialog box gets focus, bounce it to the default control. - // so we post a message back to ourselves to wait for the focus change then push it to the default - // control. See ASURT 84016. - // - if (mDefaultControlHwnd != 0) - Windows.SetFocus(mDefaultControlHwnd); - } - return 0; - } - - protected abstract Result RunDialog(Windows.HWnd hWndOwner); - } - - abstract class FileDialog : CommonDialog - { - protected abstract Result RunFileDialog(ref Windows.OpenFileName ofn); - - protected override Result RunDialog(Windows.HWnd hWndOwner) - { - return RunDialogOld(hWndOwner); - } - - private const int32 FILEBUFSIZE = 8192; - protected const int32 OPTION_ADDEXTENSION = (int32)0x80000000; - - protected int32 mOptions; - private String mTitle ~ delete _; - private String mInitialDir ~ delete _; - private String mDefaultExt ~ delete _; - protected String[] mFileNames ~ DeleteContainerAndItems!(_); - private bool mSecurityCheckFileNames; - private String mFilter ~ delete _; - private String mFilterBuffer = new String() ~ delete _; - private int32 mFilterIndex; - private bool mSupportMultiDottedExtensions; - private bool mIgnoreSecondFileOkNotification; // Used for VS Whidbey 95342 - private int32 mOKNotificationCount; // Same - //private String char8Buffer = new String(FILEBUFSIZE) ~ delete _; - - public this() - { - Reset(); - } - - public virtual void Reset() - { - DeleteAndNullify!(mTitle); - DeleteAndNullify!(mInitialDir); - DeleteAndNullify!(mDefaultExt); - DeleteContainerAndItems!(mFileNames); - mFileNames = null; - DeleteAndNullify!(mFilter); - mFilterIndex = 1; - mSupportMultiDottedExtensions = false; - mOptions = Windows.OFN_HIDEREADONLY | Windows.OFN_PATHMUSTEXIST | - OPTION_ADDEXTENSION; - } - - protected int32 Options - { - get - { - return mOptions & ( - Windows.OFN_READONLY | - Windows.OFN_HIDEREADONLY | - Windows.OFN_NOCHANGEDIR | - Windows.OFN_SHOWHELP | - Windows.OFN_NOVALIDATE | - Windows.OFN_ALLOWMULTISELECT | - Windows.OFN_PATHMUSTEXIST | - Windows.OFN_FILEMUSTEXIST | - Windows.OFN_NODEREFERENCELINKS | - Windows.OFN_OVERWRITEPROMPT); - //return mOptions; - } - } - - public StringView Title - { - set - { - String.NewOrSet!(mTitle, value); - } - - get - { - return mTitle; - } - } - - public StringView InitialDirectory - { - set - { - String.NewOrSet!(mInitialDir, value); - } - - get - { - return mInitialDir; - } - } - - public String[] FileNames - { - get - { - return mFileNames; - } - } - - public StringView FileName - { - set - { - if (mFileNames == null) - { - mFileNames = new String[](new String(value)); - } - } - } - - public bool AddExtension - { - get - { - return GetOption(OPTION_ADDEXTENSION); - } - - set - { - SetOption(OPTION_ADDEXTENSION, value); - } - } - - public virtual bool CheckFileExists - { - get - { - return GetOption(Windows.OFN_FILEMUSTEXIST); - } - - set - { - SetOption(Windows.OFN_FILEMUSTEXIST, value); - } - } - - public bool DereferenceLinks - { - get - { - return !GetOption(Windows.OFN_NODEREFERENCELINKS); - } - set - { - SetOption(Windows.OFN_NODEREFERENCELINKS, !value); - } - } - - public bool CheckPathExists - { - get - { - return GetOption(Windows.OFN_PATHMUSTEXIST); - } - - set - { - SetOption(Windows.OFN_PATHMUSTEXIST, value); - } - } - - public bool Multiselect - { - get - { - return GetOption(Windows.OFN_ALLOWMULTISELECT); - } - - set - { - SetOption(Windows.OFN_ALLOWMULTISELECT, value); - } - } - - public bool ValidateNames - { - get - { - return !GetOption(Windows.OFN_NOVALIDATE); - } - - set - { - SetOption(Windows.OFN_NOVALIDATE, !value); - } - } - - public StringView DefaultExt - { - get - { - return mDefaultExt == null ? "" : mDefaultExt; - } - - set - { - delete mDefaultExt; - mDefaultExt = null; - - //if (!String.IsNullOrEmpty(value)) - if (value.Length > 0) - { - mDefaultExt = new String(value); - if (mDefaultExt.StartsWith(".")) - mDefaultExt.Remove(0, 1); - } - } - } - - public void GetFilter(String outFilter) - { - if (mFilter != null) - outFilter.Append(mFilter); - } - - public Result SetFilter(StringView value) - { - String useValue = scope String(value); - if (useValue != null && useValue.Length > 0) - { - var formats = String.StackSplit!(useValue, '|'); - if (formats == null || formats.Count % 2 != 0) - { - return .Err; - } - /// - /*String[] formats = value.Split('|'); - if (formats == null || formats.Length % 2 != 0) - { - throw new ArgumentException(SR.GetString(SR.FileDialogInvalidFilter)); - }*/ - String.NewOrSet!(mFilter, useValue); - } - else - { - useValue = null; - DeleteAndNullify!(mFilter); - } - - return .Ok; - } - - protected bool GetOption(int32 option) - { - return (mOptions & option) != 0; - } - - protected void SetOption(int32 option, bool value) - { - if (value) - { - mOptions |= option; - } - else - { - mOptions &= ~option; - } - } - - private static Result MakeFilterString(String s, bool dereferenceLinks, String filterBuffer) - { - String useStr = s; - if (useStr == null || useStr.Length == 0) - { - // Workaround for Whidbey bug #5165 - // Apply the workaround only when DereferenceLinks is true and OS is at least WinXP. - if (dereferenceLinks && System.Environment.OSVersion.Version.Major >= 5) - { - useStr = " |*.*"; - } - else if (useStr == null) - { - return .Err; - } - } - - filterBuffer.Set(s); - for (int32 i = 0; i < filterBuffer.Length; i++) - if (filterBuffer[i] == '|') - filterBuffer[i] = (char8)0; - filterBuffer.Append((char8)0); - return .Ok; - } - - public static mixin Testie() - { - int a = 123; - char16* buf; - if (a == 0) - { - buf = null; - } - else - { - buf = new char16[123]* ( ? ); - defer:mixin delete buf; - } - buf - } - - private Result RunDialogOld(Windows.HWnd hWndOwner) - { - //RunDialogTest(hWndOwner); - - Windows.WndProc hookProcPtr = => StaticHookProc; - Windows.OpenFileName ofn = Windows.OpenFileName(); - - char16[FILEBUFSIZE] char16Buffer = .(0, ?); - - if (mFileNames != null) - { - //int len = UTF16.GetEncodedLen(fileNames[0]); - //char16Buffer = scope:: char16[len + 1]*; - UTF16.Encode(mFileNames[0], (char16*)&char16Buffer, FILEBUFSIZE); - } - // Degrade to the older style dialog if we're not on Win2K. - // We do this by setting the struct size to a different value - // - - if (Environment.OSVersion.Platform != System.PlatformID.Win32NT || - Environment.OSVersion.Version.Major < 5) { - ofn.mStructSize = 0x4C; - } - ofn.mHwndOwner = hWndOwner; - ofn.mHInstance = (Windows.HInstance)Windows.GetModuleHandleW(null); - - if (mFilter != null) - { - Try!(MakeFilterString(mFilter, this.DereferenceLinks, mFilterBuffer)); - ofn.mFilter = mFilterBuffer.ToScopedNativeWChar!::(); - } - ofn.nFilterIndex = mFilterIndex; - ofn.mFile = (char16*)&char16Buffer; - ofn.nMaxFile = FILEBUFSIZE; - if (mInitialDir != null) - ofn.mInitialDir = mInitialDir.ToScopedNativeWChar!::(); - if (mTitle != null) - ofn.mTitle = mTitle.ToScopedNativeWChar!::(); - ofn.mFlags = Options | (Windows.OFN_EXPLORER | Windows.OFN_ENABLEHOOK | Windows.OFN_ENABLESIZING); - ofn.mHook = hookProcPtr; - ofn.mCustData = (int)Internal.UnsafeCastToPtr(this); - ofn.mFlagsEx = Windows.OFN_USESHELLITEM; - if (mDefaultExt != null && AddExtension) - ofn.mDefExt = mDefaultExt.ToScopedNativeWChar!::(); - - DeleteContainerAndItems!(mFileNames); - mFileNames = null; - //Security checks happen here - return RunFileDialog(ref ofn); - } - - static int StaticHookProc(Windows.HWnd hWnd, int32 msg, int wParam, int lparam) - { - if (msg == Windows.WM_INITDIALOG) - { - using (sMonitor.Enter()) - { - var ofn = (Windows.OpenFileName*)(void*)lparam; - sHookMap[(int)hWnd] = (CommonDialog)Internal.UnsafeCastToObject((void*)ofn.mCustData); - } - } - - CommonDialog dlg; - using (sMonitor.Enter()) - { - sHookMap.TryGetValue((int)hWnd, out dlg); - } - if (dlg == null) - return 0; - - dlg.[Friend]HookProc(hWnd, msg, wParam, lparam); - if (msg == Windows.WM_DESTROY) - { - using (sMonitor.Enter()) - { - sHookMap.Remove((int)hWnd); - } - } - return 0; - } - //TODO: Add ProcessFileNames for validation - } - class OpenFileDialog : FileDialog { public override void Reset() @@ -557,6 +103,73 @@ namespace System.IO return DialogResult.OK; } + + protected override void ProcessVistaFiles(Windows.COM_IFileDialog* dialog, List files) + { + mixin GetFilePathFromShellItem(Windows.COM_IShellItem* shellItem) + { + String str = null; + if (shellItem.VT.GetDisplayName(shellItem, .FILESYSPATH, let cStr) == .OK) + { + str = new String()..Append(cStr); + Windows.COM_IUnknown.CoTaskMemFree(cStr); + } + str + } + + Windows.COM_IFileOpenDialog* openDialog = (.)dialog; + if (Multiselect) + { + Windows.COM_IShellItemArray* results = null; + openDialog.VT.GetResults(openDialog, out results); + + if (results != null) + { + results.VT.GetCount(results, let count); + for (uint32 i < count) + { + Windows.COM_IShellItem* item = null; + results.VT.GetItemAt(results, i, out item); + if (item != null) + { + let filePath = GetFilePathFromShellItem!(item); + if (filePath != null) + files.Add(filePath); + } + } + results.VT.Release(results); + } + } + else + { + Windows.COM_IShellItem* shellItem = null; + openDialog.VT.GetResult(openDialog, out shellItem); + + if (shellItem != null) + { + let filePath = GetFilePathFromShellItem!(shellItem); + if (filePath != null) + files.Add(filePath); + shellItem.VT.Release(shellItem); + } + } + } + + protected override Result CreateVistaDialog() + { + Windows.COM_IFileDialog* fileDialog = null; + + Windows.COM_IUnknown.HResult hr = (Windows.COM_IUnknown.CoCreateInstance( + ref Windows.COM_IFileDialog.sCLSID, + null, + .INPROC_SERVER | .LOCAL_SERVER | .REMOTE_SERVER, + ref Windows.COM_IFileOpenDialog.sIID, + (void**)&fileDialog)); + if (hr.Failed) + return .Err; + + return fileDialog; + } } } diff --git a/BeefLibs/corlib/src/IO/SaveFileDialog.bf b/BeefLibs/corlib/src/IO/SaveFileDialog.bf index 89585e2d..58e1bca9 100644 --- a/BeefLibs/corlib/src/IO/SaveFileDialog.bf +++ b/BeefLibs/corlib/src/IO/SaveFileDialog.bf @@ -94,6 +94,48 @@ namespace System.IO return DialogResult.OK; } + + protected override void ProcessVistaFiles(Windows.COM_IFileDialog* dialog, System.Collections.List files) + { + mixin GetFilePathFromShellItem(Windows.COM_IShellItem* shellItem) + { + String str = null; + if (shellItem.VT.GetDisplayName(shellItem, .FILESYSPATH, let cStr) == .OK) + { + str = new String()..Append(cStr); + Windows.COM_IUnknown.CoTaskMemFree(cStr); + } + str + } + + Windows.COM_IFileSaveDialog* saveDialog = (.)dialog; + Windows.COM_IShellItem* shellItem = null; + saveDialog.VT.GetResult(saveDialog, out shellItem); + + if (shellItem != null) + { + let filePath = GetFilePathFromShellItem!(shellItem); + if (filePath != null) + files.Add(filePath); + shellItem.VT.Release(shellItem); + } + } + + protected override Result CreateVistaDialog() + { + Windows.COM_IFileDialog* fileDialog = null; + + Windows.COM_IUnknown.HResult hr = (Windows.COM_IUnknown.CoCreateInstance( + ref Windows.COM_IFileSaveDialog.sCLSID, + null, + .INPROC_SERVER | .LOCAL_SERVER | .REMOTE_SERVER, + ref Windows.COM_IFileSaveDialog.sIID, + (void**)&fileDialog)); + if (hr.Failed) + return .Err; + + return fileDialog; + } } } diff --git a/BeefLibs/corlib/src/Windows.bf b/BeefLibs/corlib/src/Windows.bf index 44f3ba04..23de97ad 100644 --- a/BeefLibs/corlib/src/Windows.bf +++ b/BeefLibs/corlib/src/Windows.bf @@ -1117,6 +1117,215 @@ namespace System TRUSTEE_W Trustee; } + struct COM_IFileDialogEvents : Windows.COM_IUnknown + { + struct FDE_SHAREVIOLATION_RESPONSE; + struct FDE_OVERWRITE_RESPONSE; + + public struct VTable : Windows.COM_IUnknown.VTable + { + public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog) OnFileOk; + public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog, COM_IShellItem* psiFolder) OnFolderChanging; + public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog) OnFolderChange; + public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog) OnSelectionChange; + public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog, FDE_SHAREVIOLATION_RESPONSE* pResponse) OnShareViolation; + public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog) OnTypeChange; + public function HResult(COM_IFileDialogEvents* self, COM_IFileDialog* fileDialog, COM_IShellItem* shellItem, FDE_OVERWRITE_RESPONSE* response) OnOverwrite; + + } + } + + public struct COM_IShellItem : Windows.COM_IUnknown + { + public static Guid sIID = .(0x43826d1e, 0xe718, 0x42ee, 0xbc, 0x55, 0xa1, 0xe2, 0x61, 0xc3, 0x7b, 0xfe); + + public enum SIGDN : uint32 + { + NORMALDISPLAY = 0x00000000, // SHGDN_NORMAL + PARENTRELATIVEPARSING = 0x80018001, // SHGDN_INFOLDER | SHGDN_FORPARSING + DESKTOPABSOLUTEPARSING = 0x80028000, // SHGDN_FORPARSING + PARENTRELATIVEEDITING = 0x80031001, // SHGDN_INFOLDER | SHGDN_FOREDITING + DESKTOPABSOLUTEEDITING = 0x8004c000, // SHGDN_FORPARSING | SHGDN_FORADDRESSBAR + FILESYSPATH = 0x80058000, // SHGDN_FORPARSING + URL = 0x80068000, // SHGDN_FORPARSING + PARENTRELATIVEFORADDRESSBAR = 0x8007c001, // SHGDN_INFOLDER | SHGDN_FORPARSING | SHGDN_FORADDRESSBAR + PARENTRELATIVE = 0x80080001 // SHGDN_INFOLDER + } + + public struct VTable : Windows.COM_IUnknown.VTable + { + public function HResult(COM_IShellItem* self, void* pbc, ref Guid bhid, ref Guid riid, void** ppv) BindToHandler; + public function HResult(COM_IShellItem* self, out COM_IShellItem* ppsi) GetParent; + public function HResult(COM_IShellItem* self, SIGDN sigdnName, out char16* ppszName) GetDisplayName; + public function HResult(COM_IShellItem* self, uint sfgaoMask, out uint psfgaoAttribs) GetAttributes; + public function HResult(COM_IShellItem* self, COM_IShellItem* psi, uint32 hint, out int32 piOrder) Compare; + + } + public new VTable* VT + { + get + { + return (.)mVT; + } + } + } + + public struct COM_IShellItemArray : Windows.COM_IUnknown + { + public enum GETPROPERTYSTOREFLAGS : uint + { + DEFAULT = 0x00000000, + HANDLERPROPERTIESONLY = 0x00000001, + READWRITE = 0x00000002, + TEMPORARY = 0x00000004, + FASTPROPERTIESONLY = 0x00000008, + OPENSLOWITEM = 0x00000010, + DELAYCREATION = 0x00000020, + BESTEFFORT = 0x00000040, + NO_OPLOCK = 0x00000080, + PREFERQUERYPROPERTIES = 0x00000100, + EXTRINSICPROPERTIES = 0x00000200, + EXTRINSICPROPERTIESONLY = 0x00000400, + VOLATILEPROPERTIES = 0x00000800, + VOLATILEPROPERTIESONLY = 0x00001000, + MASK_VALID = 0x00001FFF + } + + [AllowDuplicates] + public enum SIATTRIBFLAGS : uint + { + AND = 0x1, + OR = 0x2, + APPCOMPAT = 0x3, + MASK = 0x3, + ALLITEMS = 0x4000 + } + + public struct PROPERTYKEY; + + public static Guid sIID = .(0xB63EA76D, 0x1F85, 0x456F, 0xA1, 0x9C, 0x48, 0x15, 0x9E, 0xFA, 0x85, 0x8B); + + public struct VTable : Windows.COM_IUnknown.VTable + { + public function HResult(COM_IShellItemArray* self, void* pbc, ref Guid rbhid, ref Guid riid, out void* ppvOut) BindToHandler; + public function HResult(COM_IShellItemArray* self, GETPROPERTYSTOREFLAGS flags, ref Guid riid, out void* ppv) GetPropertyStore; + public function HResult(COM_IShellItemArray* self, ref PROPERTYKEY keyType, ref Guid riid, out void* ppv) GetPropertyDescriptionList; + public function HResult(COM_IShellItemArray* self, SIATTRIBFLAGS dwAttribFlags, uint32 sfgaoMask, out uint32 psfgaoAttribs) GetAttributes; + public function HResult(COM_IShellItemArray* self, out uint32 pdwNumItems) GetCount; + public function HResult(COM_IShellItemArray* self, uint32 dwIndex, out COM_IShellItem* ppsi) GetItemAt; + public function HResult(COM_IShellItemArray* self, out void* ppenumShellItems) EnumItems; + } + public new VTable* VT + { + get + { + return (.)mVT; + } + } + } + + public struct COMDLG_FILTERSPEC + { + public char16* pszName; + public char16* pszSpec; + } + + enum FDAP : uint32 + { + FDAP_BOTTOM = 0x00000000, + FDAP_TOP = 0x00000001, + } + + public struct COM_IFileDialog : Windows.COM_IUnknown + { + public static Guid sIID = .(0x42f85136, 0xdb7e, 0x439c, 0x85, 0xf1, 0xe4, 0x07, 0x5d, 0x13, 0x5f, 0xc8); + public static Guid sCLSID = .(0xdc1c5a9c, 0xe88a, 0x4dde, 0xa5, 0xa1, 0x60, 0xf8, 0x2a, 0x20, 0xae, 0xf7); + + ///s + public enum FOS : uint32 + { + OVERWRITEPROMPT = 0x00000002, + STRICTFILETYPES = 0x00000004, + NOCHANGEDIR = 0x00000008, + PICKFOLDERS = 0x00000020, + FORCEFILESYSTEM = 0x00000040, + ALLNONSTORAGEITEMS = 0x00000080, + NOVALIDATE = 0x00000100, + ALLOWMULTISELECT = 0x00000200, + PATHMUSTEXIST = 0x00000800, + FILEMUSTEXIST = 0x00001000, + CREATEPROMPT = 0x00002000, + SHAREAWARE = 0x00004000, + NOREADONLYRETURN = 0x00008000, + NOTESTFILECREATE = 0x00010000, + HIDEMRUPLACES = 0x00020000, + HIDEPINNEDPLACES = 0x00040000, + NODEREFERENCELINKS = 0x00100000, + DONTADDTORECENT = 0x02000000, + FORCESHOWHIDDEN = 0x10000000, + DEFAULTNOMINIMODE = 0x20000000 + } + + public struct VTable : Windows.COM_IUnknown.VTable + { + public function HResult(COM_IFileDialog* self, Windows.HWnd parent) Show; + public function HResult(COM_IFileDialog* self, uint cFileTypes, COMDLG_FILTERSPEC* rgFilterSpec) SetFileTypes; + public function HResult(COM_IFileDialog* self, uint iFileType) SetFileTypeIndex; + public function HResult(COM_IFileDialog* self, out uint piFileType) GetFileTypeIndex; + public function HResult(COM_IFileDialog* self, COM_IFileDialogEvents* pfde, out uint pdwCookie) Advise; + public function HResult(COM_IFileDialog* self, uint dwCookie) Unadvise; + public function HResult(COM_IFileDialog* self, FOS fos) SetOptions; + public function HResult(COM_IFileDialog* self, out FOS pfos) GetOptions; + public function HResult(COM_IFileDialog* self, COM_IShellItem* psi) SetDefaultFolder; + public function HResult(COM_IFileDialog* self, COM_IShellItem* psi) SetFolder; + public function HResult(COM_IFileDialog* self, out COM_IShellItem* ppsi) GetFolder; + public function HResult(COM_IFileDialog* self, out COM_IShellItem* ppsi) GetCurrentSelection; + public function HResult(COM_IFileDialog* self, char16* pszName) SetFileName; + public function HResult(COM_IFileDialog* self, out char16* pszName) GetFileName; + public function HResult(COM_IFileDialog* self, char16* pszTitle) SetTitle; + public function HResult(COM_IFileDialog* self, char16* pszText) SetOkButtonLabel; + public function HResult(COM_IFileDialog* self, char16* pszLabel) SetFileNameLabel; + public function HResult(COM_IFileDialog* self, out COM_IShellItem* ppsi) GetResult; + public function HResult(COM_IFileDialog* self, COM_IShellItem* psi, FDAP fdap) AddPlace; + public function HResult(COM_IFileDialog* self, char16* pszDefaultExtension) SetDefaultExtension; + public function HResult(COM_IFileDialog* self, int hr) Close; + public function HResult(COM_IFileDialog* self, ref Guid guid) SetClientGuid; + public function HResult(COM_IFileDialog* self) ClearClientData; + public function HResult(COM_IFileDialog* self, void* pFilter) SetFilter; + } + public new VTable* VT + { + get + { + return (.)mVT; + } + } + } + + public struct COM_IFileSaveDialog : COM_IFileDialog + { + public static new Guid sIID = .(0x84BCCD23, 0x5FDE, 0x4CDB, 0xAE, 0xA4, 0xAF, 0x64, 0xB8, 0x3D, 0x78, 0xAB); + public static new Guid sCLSID = .(0xC0B4E2F3, 0xBA21, 0x4773, 0x8D, 0xBA, 0x33, 0x5E, 0xC9, 0x46, 0xEB, 0x8B); + } + + public struct COM_IFileOpenDialog : COM_IFileDialog + { + public static new Guid sIID = .(0xD57C7288, 0xD4AD, 0x4768, 0xBE, 0x02, 0x9D, 0x96, 0x95, 0x32, 0xD9, 0x60); + + public struct VTable : COM_IFileDialog.VTable + { + public function HResult(COM_IFileOpenDialog* self, out COM_IShellItemArray* ppenum) GetResults; + public function HResult(COM_IFileOpenDialog* self, out COM_IShellItemArray* ppsai) GetSelectedItems; + } + public new VTable* VT + { + get + { + return (.)mVT; + } + } + } + [Import("version.lib"), CLink, CallingConvention(.Stdcall)] public static extern IntBool GetFileVersionInfoW(char16* lptstrFilename, uint32 dwHandle, uint32 dwLen, void* lpData); diff --git a/BeefySysLib/util/Array.h b/BeefySysLib/util/Array.h index e485023c..c5241db5 100644 --- a/BeefySysLib/util/Array.h +++ b/BeefySysLib/util/Array.h @@ -830,8 +830,8 @@ public: else if (size > this->mSize) { Reserve(size); - while (size > this->mSize) - this->mVals[this->mSize++] = T(); + memset(&this->mVals[this->mSize], 0, (size - this->mSize) * sizeof(T)); + this->mSize = (int_cosize)size; } } diff --git a/IDE/Tests/CompileFail001/src/Generics.bf b/IDE/Tests/CompileFail001/src/Generics.bf index 8a27fbf4..6f851c63 100644 --- a/IDE/Tests/CompileFail001/src/Generics.bf +++ b/IDE/Tests/CompileFail001/src/Generics.bf @@ -52,7 +52,7 @@ namespace IDETest void MethodB() { - function void() f = => MethodA; //FAIL Method 'IDETest.Generics.ClassA.MethodA(int a)' does not match function 'function void()' + function void() f = => MethodA; //FAIL Method 'void IDETest.Generics.ClassA.MethodA(int a)' does not match function 'function void()' } } diff --git a/IDE/mintest/minlib/src/System/Compiler.bf b/IDE/mintest/minlib/src/System/Compiler.bf index 2c559e2c..d6aefbda 100644 --- a/IDE/mintest/minlib/src/System/Compiler.bf +++ b/IDE/mintest/minlib/src/System/Compiler.bf @@ -2,6 +2,11 @@ namespace System { class Compiler { + public class Generator + { + + } + public static class Options { [LinkName("#AllocStackCount")] diff --git a/IDE/src/CommandQueueManager.bf b/IDE/src/CommandQueueManager.bf index 213fee60..4fa97410 100644 --- a/IDE/src/CommandQueueManager.bf +++ b/IDE/src/CommandQueueManager.bf @@ -15,9 +15,10 @@ namespace IDE public Action mOnThreadDone ~ delete _; [ThreadStatic] public static bool mBpSetThreadName; + public bool mAllowFastFinish; WaitEvent mWaitEvent = new WaitEvent() ~ delete _; - public void DoBackground(ThreadStart threadStart, Action onThreadDone = null, int maxWait = 0) + public void DoBackground(ThreadStart threadStart, Action onThreadDone = null, int maxWait = 0, bool allowFastFinish = true) { Debug.Assert(Thread.CurrentThread == IDEApp.sApp.mMainThread); @@ -26,6 +27,7 @@ namespace IDE BeefPerf.Event("DoBackground:starting", ""); + mAllowFastFinish = allowFastFinish; mOnThreadDone = onThreadDone; mThreadRunning = true; mWaitEvent.Reset(); @@ -47,6 +49,7 @@ namespace IDE delete threadStart; mThread = null; mThreadRunning = false; + mAllowFastFinish = true; BeefPerf.Event("DoBackground:threadEnd", ""); mWaitEvent.Set(); @@ -132,7 +135,7 @@ namespace IDE } - public virtual void RequestFastFinish() + public virtual void RequestFastFinish(bool force = false) { } @@ -190,15 +193,20 @@ namespace IDE return (mThreadWorker.mThreadRunning || mThreadWorkerHi.mThreadRunning); } + public bool IsPerformingBackgroundOperationHi() + { + return (mThreadWorkerHi.mThreadRunning); + } + public void DoBackground(ThreadStart threadStart, Action onThreadDone = null, int maxWait = 0) { CancelBackground(); mThreadWorker.DoBackground(threadStart, onThreadDone, maxWait); } - public void DoBackgroundHi(ThreadStart threadStart, Action onThreadDone = null) + public void DoBackgroundHi(ThreadStart threadStart, Action onThreadDone = null, bool allowFastFinish = true) { - mThreadWorkerHi.DoBackground(threadStart, onThreadDone); + mThreadWorkerHi.DoBackground(threadStart, onThreadDone, 0, allowFastFinish); } public void CheckThreadDone() diff --git a/IDE/src/Compiler/BfCompiler.bf b/IDE/src/Compiler/BfCompiler.bf index e00b3502..efc60b19 100644 --- a/IDE/src/Compiler/BfCompiler.bf +++ b/IDE/src/Compiler/BfCompiler.bf @@ -94,6 +94,15 @@ namespace IDE.Compiler [CallingConvention(.Stdcall), CLink] static extern char8* BfCompiler_GetUsedOutputFileNames(void* bfCompiler, void* bfProject, bool flushQueuedHotFiles, out bool hadOutputChanges); + [CallingConvention(.Stdcall), CLink] + static extern char8* BfCompiler_GetGeneratorTypeDefList(void* bfCompiler); + + [CallingConvention(.Stdcall), CLink] + static extern char8* BfCompiler_GetGeneratorInitData(void* bfCompiler, char8* typeDefName, char8* args); + + [CallingConvention(.Stdcall), CLink] + static extern char8* BfCompiler_GetGeneratorGenData(void* bfCompiler, char8* typeDefName, char8* args); + [CallingConvention(.Stdcall), CLink] static extern char8* BfCompiler_GetTypeDefList(void* bfCompiler); @@ -234,13 +243,13 @@ namespace IDE.Compiler public void GetAutocompleteInfo(String outAutocompleteInfo) { char8* result = BfCompiler_GetAutocompleteInfo(mNativeBfCompiler); - scope String(result).MoveTo(outAutocompleteInfo); + outAutocompleteInfo.Append(StringView(result)); } public void GetSymbolReferences(BfPassInstance passInstance, BfResolvePassData resolvePassData, String outSymbolReferences) { char8* result = BfCompiler_GetSymbolReferences(mNativeBfCompiler, passInstance.mNativeBfPassInstance, resolvePassData.mNativeResolvePassData); - scope String(result).MoveTo(outSymbolReferences); + outSymbolReferences.Append(StringView(result)); } /*public void UpdateRenameSymbols(BfPassInstance passInstance, BfResolvePassData resolvePassData) @@ -674,17 +683,21 @@ namespace IDE.Compiler public override void RequestCancelBackground() { - if ([Friend]mThreadWorker.mThreadRunning) + if (mThreadWorker.mThreadRunning) { if ((mNativeBfCompiler != null) && (!gApp.mDeterministic)) BfCompiler_Cancel(mNativeBfCompiler); } } - public override void RequestFastFinish() + public override void RequestFastFinish(bool force = false) { - if ([Friend]mThreadWorker.mThreadRunning || [Friend]mThreadWorkerHi.mThreadRunning) + if (mThreadWorker.mThreadRunning || mThreadWorkerHi.mThreadRunning) { + if ((!force) && + ((!mThreadWorker.mAllowFastFinish) || (!mThreadWorkerHi.mAllowFastFinish))) + return; + if ((mNativeBfCompiler != null) && (!gApp.mDeterministic)) BfCompiler_RequestFastFinish(mNativeBfCompiler); } @@ -710,6 +723,21 @@ namespace IDE.Compiler return BfCompiler_GetCurConstEvalExecuteId(mNativeBfCompiler); } + public void GetGeneratorTypeDefList(String outStr) + { + outStr.Append(BfCompiler_GetGeneratorTypeDefList(mNativeBfCompiler)); + } + + public void GetGeneratorInitData(String typeDefName, String args, String outStr) + { + outStr.Append(BfCompiler_GetGeneratorInitData(mNativeBfCompiler, typeDefName, args)); + } + + public void GetGeneratorGenData(String typeDefName, String args, String outStr) + { + outStr.Append(BfCompiler_GetGeneratorGenData(mNativeBfCompiler, typeDefName, args)); + } + public void GetTypeDefList(String outStr) { outStr.Append(BfCompiler_GetTypeDefList(mNativeBfCompiler)); diff --git a/IDE/src/Project.bf b/IDE/src/Project.bf index 7418ea2a..56922862 100644 --- a/IDE/src/Project.bf +++ b/IDE/src/Project.bf @@ -37,6 +37,7 @@ namespace IDE public ProjectFolder mParentFolder; public String mName = new String() ~ delete _; public String mComment = new String() ~ delete _; + public bool mDetached; public virtual bool IncludeInMap { @@ -105,6 +106,7 @@ namespace IDE public virtual void Detach() { + mDetached = true; ReleaseRef(); } } @@ -353,7 +355,7 @@ namespace IDE return .SimpleSource; } - public override void Dispose() + public void ClearEditData() { if (mEditData != null) { @@ -363,6 +365,11 @@ namespace IDE } } + public override void Dispose() + { + ClearEditData(); + } + public override void Detach() { Dispose(); @@ -843,6 +850,13 @@ namespace IDE childFileItem.OnRename(childFileItem.mPath, newChildPath); + String oldFullName = scope String(); + mProject.GetProjectFullPath(childFileItem.mPath, oldFullName); + String newFullName = scope String(); + mProject.GetProjectFullPath(newChildPath, newFullName); + + IDEApp.sApp.FileRenamed(childFileItem, oldFullName, newFullName); + childFileItem.mPath.Set(newChildPath); } } diff --git a/IDE/src/ui/ClassViewPanel.bf b/IDE/src/ui/ClassViewPanel.bf index 3088cccc..51fac019 100644 --- a/IDE/src/ui/ClassViewPanel.bf +++ b/IDE/src/ui/ClassViewPanel.bf @@ -598,10 +598,14 @@ namespace IDE.ui } } + var bfSystem = gApp.mBfResolveSystem; + String typeName = scope .(); GetName(item, typeName); String info = scope .(); + bfSystem.Lock(0); gApp.mBfResolveCompiler.GetTypeDefInfo(typeName, info); + bfSystem.Unlock(); for (let str in info.Split('\n')) { diff --git a/IDE/src/ui/GenerateDialog.bf b/IDE/src/ui/GenerateDialog.bf new file mode 100644 index 00000000..7bb15545 --- /dev/null +++ b/IDE/src/ui/GenerateDialog.bf @@ -0,0 +1,882 @@ +using Beefy.theme.dark; +using Beefy.widgets; +using System; +using System.Collections; +using Beefy.gfx; +using Beefy.events; +using IDE.Compiler; +using System.Threading; +using System.Security.Cryptography; +using Beefy.theme; + +namespace IDE.ui +{ + class GenerateListView : DarkListView + { + public GenerateDialog mNewClassDialog; + } + + class GenerateKindBar : DarkComboBox + { + public class Entry + { + public String mTypeName ~ delete _; + public String mName ~ delete _; + } + + public static Dictionary sMRU = new Dictionary() ~ delete _; + public static int32 sCurrentMRUIndex = 1; + + public GenerateDialog mNewClassDialog; + public List mEntries = new List() ~ DeleteContainerAndItems!(_); + public List mShownEntries = new List() ~ delete _; + public String mFilterString ~ delete _; + public String mCurLocation = new String() ~ delete _; + public bool mIgnoreChange = false; + + public this(GenerateDialog dialog) + { + mNewClassDialog = dialog; + mLabelAlign = FontAlign.Left; + Label = ""; + mLabelX = GS!(16); + mPopulateMenuAction.Add(new => PopulateNavigationBar); + MakeEditable(); + mEditWidget.mOnContentChanged.Add(new => NavigationBarChanged); + mEditWidget.mOnGotFocus.Add(new (widget) => mEditWidget.mEditWidgetContent.SelectAll()); + mEditWidget.mEditWidgetContent.mWantsUndo = false; + mEditWidget.mOnSubmit.Add(new => mNewClassDialog.[Friend]EditSubmitHandler); + mFocusDropdown = false; + } + + public ~this() + { + } + + static ~this() + { + for (var key in sMRU.Keys) + delete key; + } + + public void SetLocation(String location) + { + if (mCurMenuWidget == null) + { + mIgnoreChange = true; + mEditWidget.SetText(location); + mEditWidget.mEditWidgetContent.SelectAll(); + // SetText can attempt to scroll to the right to make the cursor position visible. Just scroll back to the start. + mEditWidget.HorzScrollTo(0); + //mNewClassDialog.SelectKind(); + mIgnoreChange = false; + } + } + private void PopulateNavigationBar(Menu menu) + { + List findStrs = null; + if (mFilterString != null) + findStrs = scope:: List(mFilterString.Split(' ')); + + EntryLoop: for (int32 entryIdx = 0; entryIdx < mEntries.Count; entryIdx++) + { + var entry = mEntries[entryIdx]; + if (findStrs != null) + { + for (let findStr in findStrs) + { + if (entry.mName.IndexOf(findStr, true) == -1) + continue EntryLoop; + } + } + + mShownEntries.Add(entry); + var menuItem = menu.AddItem(entry.mName); + menuItem.mOnMenuItemSelected.Add(new (evt) => + { + mNewClassDialog.mPendingUIFocus = true; + ShowEntry(entryIdx, entry); + }); + } + } + + void ShowEntry(int32 entryIdx, Entry entry) + { + mEditWidget.SetText(entry.mName); + mEditWidget.mEditWidgetContent.SelectAll(); + mCurMenuWidget?.Close(); + } + + private void NavigationBarChanged(EditEvent theEvent) + { + if (mIgnoreChange) + return; + + var editWidget = (EditWidget)theEvent.mSender; + var searchText = scope String(); + editWidget.GetText(searchText); + searchText.Trim(); + mFilterString = searchText; + ShowDropdown(); + mFilterString = null; + } + + bool mIgnoreShowDropdown; + + public override MenuWidget ShowDropdown() + { + if (mIgnoreShowDropdown) + return null; + mIgnoreShowDropdown = true; + defer { mIgnoreShowDropdown = false; } + + if (!mEditWidget.mHasFocus) + SetFocus(); + + if (mFilterString == null) + mEditWidget.Content.SelectAll(); + + mShownEntries.Clear(); + base.ShowDropdown(); + + int32 bestItem = -1; + int32 bestPri = -1; + var menuWidget = (DarkMenuWidget)mCurMenuWidget; + + for (int32 itemIdx = 0; itemIdx < menuWidget.mItemWidgets.Count; itemIdx++) + { + var menuItemWidget = (DarkMenuItem)menuWidget.mItemWidgets[itemIdx]; + + int32 pri; + sMRU.TryGetValue(menuItemWidget.mMenuItem.mLabel, out pri); + if (pri > bestPri) + { + bestItem = itemIdx; + bestPri = pri; + } + } + + if (bestItem != -1) + { + mCurMenuWidget.mOnSelectionChanged.Add(new => SelectionChanged); + mCurMenuWidget.SetSelection(bestItem); + } + + return menuWidget; + } + + void SelectionChanged(int selIdx) + { + if (mEditWidget.mEditWidgetContent.HasSelection()) + { + bool prevIgnoreShowDropdown = mIgnoreShowDropdown; + mIgnoreShowDropdown = true; + mEditWidget.SetText(""); + mIgnoreShowDropdown = prevIgnoreShowDropdown; + } + } + + public override void MenuClosed() + { + } + } + + class GenerateDialog : IDEDialog + { + public class UIEntry + { + public String mName ~ delete _; + public String mData ~ delete _; + public String mLabel ~ delete _; + public Widget mWidget; + } + + public enum ThreadState + { + None, + Executing, + Done + } + + public bool mPendingGenList; + public GenerateKindBar mKindBar; + public ThreadState mThreadState; + public int mThreadWaitCount; + public String mNamespace ~ delete _; + public String mProjectName ~ delete _; + public ProjectItem mProjectItem ~ _.ReleaseRef(); + public String mFolderPath ~ delete _; + public List mUIEntries = new .() ~ DeleteContainerAndItems!(_); + public GenerateKindBar.Entry mSelectedEntry; + public GenerateKindBar.Entry mPendingSelectedEntry; + public String mUIData ~ delete _; + public float mUIHeight = 0; + public OutputPanel mOutputPanel; + public bool mPendingUIFocus; + public bool mSubmitting; + public bool mSubmitQueued; + public bool mRegenerating; + + public this(ProjectItem projectItem, bool allowHashMismatch = false) + { + var project = projectItem.mProject; + mProjectItem = projectItem; + mProjectItem.AddRef(); + + mNamespace = new .(); + + var projectFolder = projectItem as ProjectFolder; + var projectSource = projectItem as ProjectSource; + if (projectSource != null) + { + projectFolder = projectSource.mParentFolder; + mRegenerating = true; + } + + projectFolder.GetRelDir(mNamespace); mNamespace.Replace('/', '.'); mNamespace.Replace('\\', '.'); mNamespace.Replace(" ", ""); + if (mNamespace.StartsWith("src.")) + { + mNamespace.Remove(0, 4); + if (!project.mBeefGlobalOptions.mDefaultNamespace.IsWhiteSpace) + { + mNamespace.Insert(0, "."); + mNamespace.Insert(0, project.mBeefGlobalOptions.mDefaultNamespace); + } + } + else if (projectItem.mParentFolder == null) + { + mNamespace.Clear(); + mNamespace.Append(project.mBeefGlobalOptions.mDefaultNamespace); + } + else + mNamespace.Clear(); + + mFolderPath = projectFolder.GetFullImportPath(.. new .()); + mProjectName = new String(projectItem.mProject.mProjectName); + + mWindowFlags = .ClientSized | .TopMost | .Caption | .Border | .SysMenu | .Resizable | .PopupPosition; + + if (!mRegenerating) + { + AddOkCancelButtons(new (evt) => + { + Submit(); + evt.mCloseDialog = false; + }, null, 0, 1); + } + + Title = "Generate File"; + + mKindBar = new GenerateKindBar(this); + AddWidget(mKindBar); + mKindBar.mEditWidget.mOnContentChanged.Add(new (theEvent) => { SelectKind(); }); + + if (mRegenerating) + { + mSubmitQueued = true; + mKindBar.SetVisible(false); + + SourceViewPanel sourceViewPanel = gApp.ShowProjectItem(projectSource, false); + + String filePath = projectSource.GetFullImportPath(.. scope .()); + + String text = scope .(); + sourceViewPanel.mEditWidget.GetText(text); + + StringView generatorName = default; + StringView hash = default; + + int dataIdx = -1; + for (var line in text.Split('\n')) + { + if (!line.StartsWith("// ")) + { + dataIdx = @line.MatchPos + 1; + break; + } + int eqPos = line.IndexOf('='); + if (eqPos == -1) + break; + StringView key = line.Substring(3, eqPos - 3); + StringView value = line.Substring(eqPos + 1); + if (key == "Generator") + generatorName = value; + else if (key == "GenHash") + hash = value; + else + { + UIEntry uiEntry = new .(); + uiEntry.mName = new .(key); + uiEntry.mData = new .(value); + mUIEntries.Add(uiEntry); + } + } + + if ((generatorName == default) || (hash == default)) + { + Close(); + gApp.Fail(scope $"File '{filePath}' was not generated by a generator that includes regeneration information"); + return; + } + + if ((dataIdx != -1) && (!allowHashMismatch)) + { + var origHash = MD5Hash.Parse(hash).GetValueOrDefault(); + + StringView dataStr = text.Substring(dataIdx); + var checkHash = MD5.Hash(.((.)dataStr.Ptr, dataStr.Length)); + + if (origHash != checkHash) + { + Close(); + Dialog dialog = ThemeFactory.mDefault.CreateDialog("Regenerate?", "This file has been modified since it was generated. Are you sure you want to regenerate?", DarkTheme.sDarkTheme.mIconWarning); + dialog.AddButton("Yes", new (evt) => + { + gApp.mProjectPanel.Regenerate(true); + //dialog.Close(); + }); + dialog.AddButton("No", new (evt) => + { + //dialog.Close(); + }); + dialog.PopupWindow(gApp.GetActiveWindow()); + return; + } + } + + GenerateKindBar.Entry entry = new .(); + entry.mName = new .(generatorName); + entry.mTypeName = new .(generatorName); + mKindBar.mEntries.Add(entry); + mPendingSelectedEntry = entry; + } + else + mPendingGenList = true; + + mKindBar.mMouseVisible = false; + mTabWidgets.Add(mKindBar.mEditWidget); + } + + public ~this() + { + var bfCompiler = gApp.mBfResolveCompiler; + if (mThreadState == .Executing) + { + bfCompiler.WaitForBackground(); + } + } + + public void SelectKind() + { + GenerateKindBar.Entry foundEntry = null; + + String text = mKindBar.mEditWidget.GetText(.. scope .()); + for (var entry in mKindBar.mEntries) + if (entry.mName == text) + foundEntry = entry; + + if (foundEntry == null) + return; + + if (mSelectedEntry == foundEntry) + return; + + mPendingSelectedEntry = foundEntry; + } + + public void ThreadProc() + { + var bfSystem = gApp.mBfResolveSystem; + var bfCompiler = gApp.mBfResolveCompiler; + + String outStr = scope String(); + + bfSystem.Lock(0); + defer bfSystem.Unlock(); + + if (mSelectedEntry != null) + { + String args = scope .(); + var project = gApp.mWorkspace.FindProject(mProjectName); + if (project == null) + return; + using (gApp.mMonitor.Enter()) + { + if (mRegenerating) + args.Append("Regenerating\tTrue\n"); + + args.AppendF( + $""" + ProjectName\t{mProjectName} + ProjectDir\t{project.mProjectPath} + FolderDir\t{mFolderPath} + Namespace\t{mNamespace} + DefaultNamespace\t{project.mBeefGlobalOptions.mDefaultNamespace} + WorkspaceName\t{gApp.mWorkspace.mName} + WorkspaceDir\t{gApp.mWorkspace.mDir} + DateTime\t{DateTime.Now} + + """); + + if (mSubmitting) + { + args.AppendF($"Generator\t{mSelectedEntry.mTypeName}\n"); + for (var uiEntry in mUIEntries) + { + String data = scope .(); + if (uiEntry.mData != null) + { + data.Append(uiEntry.mData); + } + else if (var editWidget = uiEntry.mWidget as EditWidget) + { + editWidget.GetText(data); + } + else if (var comboBox = uiEntry.mWidget as DarkComboBox) + { + comboBox.GetLabel(data); + } + else if (var checkBox = uiEntry.mWidget as CheckBox) + { + checkBox.Checked.ToString(data); + } + data.Replace('\n', '\r'); + args.AppendF($"{uiEntry.mName}\t{data}\n"); + } + } + } + + mUIData = new String(); + if (mSubmitting) + bfCompiler.GetGeneratorGenData(mSelectedEntry.mTypeName, args, mUIData); + else + bfCompiler.GetGeneratorInitData(mSelectedEntry.mTypeName, args, mUIData); + } + else + { + bfCompiler.GetGeneratorTypeDefList(outStr); + + for (var line in outStr.Split('\n', .RemoveEmptyEntries)) + { + if (line.StartsWith("!error")) + { + ShowError(line.Substring(7)); + + RehupMinSize(); + continue; + } + + var entry = new GenerateKindBar.Entry(); + var partItr = line.Split('\t'); + entry.mTypeName = new String(partItr.GetNext().Value); + if (partItr.GetNext() case .Ok(let val)) + entry.mName = new String(val); + else + { + entry.mName = new String(entry.mTypeName); + int termPos = entry.mName.LastIndexOf('.'); + if (termPos != -1) + entry.mName.Remove(0, termPos + 1); + termPos = entry.mName.LastIndexOf('+'); + if (termPos != -1) + entry.mName.Remove(0, termPos + 1); + } + mKindBar.mEntries.Add(entry); + } + } + } + + public override void CalcSize() + { + mWidth = GS!(320); + mHeight = GS!(96); + mMinWidth = mWidth; + } + + protected override void RehupMinSize() + { + mWidgetWindow.SetMinimumSize(GS!(240), (.)mUIHeight + GS!(24), true); + } + + void ShowError(StringView error) + { + if (mOutputPanel == null) + { + IDEApp.Beep(.Error); + mOutputPanel = new OutputPanel(); + AddWidget(mOutputPanel); + ResizeComponents(); + } + String str = scope .(); + str.Append(error); + str.Replace('\r', '\n'); + str.Append("\n"); + mOutputPanel.WriteSmart(str); + } + + public override void Update() + { + base.Update(); + + if ((!mKindBar.mEditWidget.mHasFocus) && (mWidgetWindow.mHasFocus)) + { + var sel = mPendingSelectedEntry ?? mSelectedEntry; + String editText = mKindBar.mEditWidget.GetText(.. scope .()); + if ((sel != null) && (editText != sel.mName)) + { + mKindBar.mIgnoreChange = true; + mKindBar.mEditWidget.SetText(sel.mName); + mKindBar.mIgnoreChange = false; + } + } + + if (mThreadState == .Done) + { + if (mSelectedEntry != null) + { + List oldEntries = scope .(); + Dictionary entryMap = scope .(); + if (!mSubmitting) + { + for (var uiEntry in mUIEntries) + { + if (!entryMap.TryAdd(uiEntry.mName, uiEntry)) + oldEntries.Add(uiEntry); + } + mUIEntries.Clear(); + } + + if (mUIData != null) + { + if (mOutputPanel != null) + { + mOutputPanel.RemoveSelf(); + DeleteAndNullify!(mOutputPanel); + } + + String fileName = default; + StringView genText = default; + bool hadError = false; + + if (mUIData.IsEmpty) + { + gApp.Fail("Generator failed to return results"); + } + + LinesLoop: for (var line in mUIData.Split('\n', .RemoveEmptyEntries)) + { + var partItr = line.Split('\t'); + var kind = partItr.GetNext().Value; + + switch (kind) + { + case "!error": + ShowError(line.Substring(7)); + case "addEdit": + if (mSubmitting) + break; + UIEntry uiEntry = new UIEntry(); + uiEntry.mName = partItr.GetNext().Value.UnQuoteString(.. new .()); + uiEntry.mLabel = partItr.GetNext().Value.UnQuoteString(.. new .()); + var defaultValue = partItr.GetNext().Value.UnQuoteString(.. scope .()); + DarkEditWidget editWidget = new DarkEditWidget(); + uiEntry.mWidget = editWidget; + editWidget.SetText(defaultValue); + editWidget.mEditWidgetContent.SelectAll(); + editWidget.mOnSubmit.Add(new => EditSubmitHandler); + AddWidget(editWidget); + mUIEntries.Add(uiEntry); + mTabWidgets.Add(editWidget); + case "addCombo": + if (mSubmitting) + break; + UIEntry uiEntry = new UIEntry(); + uiEntry.mName = partItr.GetNext().Value.UnQuoteString(.. new .()); + uiEntry.mLabel = partItr.GetNext().Value.UnQuoteString(.. new .()); + var defaultValue = partItr.GetNext().Value.UnQuoteString(.. scope .()); + List choices = new List(); + DarkComboBox comboBox = new DarkComboBox(); + while (partItr.GetNext() case .Ok(let val)) + { + choices.Add(val.UnQuoteString(.. new .())); + } + comboBox.mOnDeleted.Add(new (widget) => { DeleteContainerAndItems!(choices); }); + comboBox.mPopulateMenuAction.Add(new (menu) => + { + for (var choice in choices) + { + var item = menu.AddItem(choice); + item.mOnMenuItemSelected.Add(new (menu) => + { + comboBox.Label = menu.mLabel; + }); + } + }); + uiEntry.mWidget = comboBox; + comboBox.Label = defaultValue; + AddWidget(comboBox); + mUIEntries.Add(uiEntry); + mTabWidgets.Add(comboBox); + case "addCheckbox": + if (mSubmitting) + break; + UIEntry uiEntry = new UIEntry(); + uiEntry.mName = partItr.GetNext().Value.UnQuoteString(.. new .()); + uiEntry.mLabel = partItr.GetNext().Value.UnQuoteString(.. new .()); + var defaultValue = partItr.GetNext().Value; + DarkCheckBox checkbox = new DarkCheckBox(); + uiEntry.mWidget = checkbox; + checkbox.Label = uiEntry.mLabel; + checkbox.Checked = defaultValue == "True"; + AddWidget(checkbox); + mUIEntries.Add(uiEntry); + mTabWidgets.Add(checkbox); + case "error": + hadError = true; + gApp.Fail(line.Substring(6).UnQuoteString(.. scope .())); + case "fileName": + fileName = line.Substring(9).UnQuoteString(.. scope:: .()); + case "options": + case "data": + genText = .(mUIData, @line.MatchPos + 1); + break LinesLoop; + } + } + + ResizeComponents(); + RehupMinSize(); + + if (fileName?.EndsWith(".bf", .OrdinalIgnoreCase) == true) + fileName.RemoveFromEnd(3); + + if ((fileName != null) && (!mRegenerating)) + { + for (char8 c in fileName.RawChars) + { + if (!c.IsLetterOrDigit && c != '_') + { + gApp.Fail(scope $"Invalid generated file name: {fileName}"); + hadError = true; + break; + } + } + + if (fileName.IsEmpty) + { + gApp.Fail("Geneator failed to specify file name"); + hadError = true; + } + } + + if ((!hadError) && (genText != default) && (fileName != null)) + { + if (!mProjectItem.mDetached) + { + if (mRegenerating) + { + gApp.mProjectPanel.Regenerate(mProjectItem as ProjectSource, genText); + } + else + { + gApp.mProjectPanel.Generate(mProjectItem as ProjectFolder, fileName, genText); + } + } + Close(); + } + + if ((hadError) && (mRegenerating) && (mOutputPanel == null)) + { + Close(); + } + + if (mPendingUIFocus) + { + mPendingUIFocus = false; + if (!mUIEntries.IsEmpty) + mUIEntries[0].mWidget.SetFocus(); + } + + DeleteAndNullify!(mUIData); + } + + // + + if (mSubmitting) + { + if (!mClosed) + { + mSubmitting = false; + mSubmitQueued = false; + mDefaultButton?.mDisabled = false; + mEscButton?.mDisabled = false; + } + } + else + { + for (var uiEntry in entryMap.Values) + oldEntries.Add(uiEntry); + + for (var uiEntry in oldEntries) + { + mTabWidgets.Remove(uiEntry.mWidget); + uiEntry.mWidget.RemoveSelf(); + DeleteAndNullify!(uiEntry.mWidget); + } + + ClearAndDeleteItems(oldEntries); + } + } + else + { + mKindBar.mMouseVisible = true; + mKindBar.SetFocus(); + mKindBar.SetLocation("New Class"); + } + mThreadState = .None; + MarkDirty(); + } + + bool isWorking = false; + if (mThreadState == .None) + { + if ((mPendingGenList) || (mPendingSelectedEntry != null)) + { + isWorking = true; + var bfCompiler = gApp.mBfResolveCompiler; + if (!bfCompiler.IsPerformingBackgroundOperation()) + { + bfCompiler.CheckThreadDone(); + + mPendingGenList = false; + if (mPendingSelectedEntry != null) + { + if (mSubmitQueued) + mSubmitting = true; + mSelectedEntry = mPendingSelectedEntry; + mPendingSelectedEntry = null; + } + mThreadState = .Executing; + bfCompiler.DoBackgroundHi(new => ThreadProc, new () => + { + mThreadState = .Done; + }, false); + } + } + } + + gApp.mBfResolveCompiler.CheckThreadDone(); + + if ((mThreadState == .Executing) || (isWorking)) + { + mThreadWaitCount++; + if (mUpdateCnt % 8 == 0) + MarkDirty(); + } + else + mThreadWaitCount = 0; + } + + public override void Resize(float x, float y, float width, float height) + { + base.Resize(x, y, width, height); + ResizeComponents(); + //mClassViewPanel.Resize(0, 0, width, height - GS!(34)); + } + + public override void PopupWindow(WidgetWindow parentWindow, float offsetX = 0, float offsetY = 0) + { + base.PopupWindow(parentWindow, offsetX, offsetY); + //mKindBar.SetFocus(); + } + + public override void Draw(Graphics g) + { + base.Draw(g); + + void DrawLabel(Widget widget, StringView label) + { + if (widget == null) + return; + if (widget is CheckBox) + return; + g.DrawString(label, widget.mX + GS!(6), widget.mY - GS!(20)); + } + + DrawLabel(mKindBar, mRegenerating ? "Regenerating ..." : "Generator"); + for (var uiEntry in mUIEntries) + DrawLabel(uiEntry.mWidget, uiEntry.mLabel); + } + + public override void DrawAll(Graphics g) + { + base.DrawAll(g); + + if (mThreadWaitCount > 10) + { + using (g.PushColor(0x60505050)) + g.FillRect(0, 0, mWidth, mHeight - ((mDefaultButton != null) ? GS!(40) : GS!(0))); + IDEUtils.DrawWait(g, mWidth/2, mHeight/2, mUpdateCnt); + } + } + + public override void ResizeComponents() + { + base.ResizeComponents(); + + mUIHeight = GS!(32); + + float insetSize = GS!(12); + + mKindBar.Resize(insetSize, mUIHeight, mWidth - insetSize - insetSize, GS!(22)); + mUIHeight += GS!(52); + + for (var uiEntry in mUIEntries) + { + if (uiEntry.mWidget == null) + continue; + + float height = GS!(22); + if (uiEntry.mWidget is ComboBox) + height = GS!(26); + + if (uiEntry.mWidget is CheckBox) + { + mUIHeight -= GS!(20); + height = GS!(20); + } + + uiEntry.mWidget.Resize(insetSize, mUIHeight, mWidth - insetSize - insetSize, height); + mUIHeight += height + GS!(28); + } + + if (mOutputPanel != null) + { + float startY = mKindBar.mVisible ? GS!(60) : GS!(36); + mOutputPanel.Resize(insetSize, startY, mWidth - insetSize - insetSize, Math.Max(mHeight - startY - ((mDefaultButton != null) ? GS!(44) : GS!(12)), GS!(32))); + mUIHeight = Math.Max(mUIHeight, GS!(160)); + } + } + + public override void Close() + { + if (mThreadState == .Executing) + { + var bfCompiler = gApp.mBfResolveCompiler; + bfCompiler.RequestFastFinish(true); + bfCompiler.WaitForBackground(); + } + base.Close(); + } + + public override void Submit() + { + mDefaultButton.mDisabled = true; + mEscButton.mDisabled = true; + + if (mSubmitQueued) + return; + mSubmitQueued = true; + mPendingSelectedEntry = mPendingSelectedEntry ?? mSelectedEntry; + } + } +} diff --git a/IDE/src/ui/OutputPanel.bf b/IDE/src/ui/OutputPanel.bf index 8d002fa1..e99e10a6 100644 --- a/IDE/src/ui/OutputPanel.bf +++ b/IDE/src/ui/OutputPanel.bf @@ -327,7 +327,7 @@ namespace IDE.ui int lineIdx = (curLine + lineOfs) % lineCount; if (content.GotoRefrenceAtLine(lineIdx)) - break; + break; } } diff --git a/IDE/src/ui/ProjectPanel.bf b/IDE/src/ui/ProjectPanel.bf index 0019a29e..2e05a409 100644 --- a/IDE/src/ui/ProjectPanel.bf +++ b/IDE/src/ui/ProjectPanel.bf @@ -792,16 +792,96 @@ namespace IDE.ui } } - public void NewClass(ProjectFolder folder) + public void GenerateCode(ProjectFolder folder) { - DarkDialog dialog = (DarkDialog)ThemeFactory.mDefault.CreateDialog("New Class", "Class Name"); + /*DarkDialog dialog = (DarkDialog)ThemeFactory.mDefault.CreateDialog("New Class", "Class Name"); dialog.mMinWidth = GS!(300); dialog.mDefaultButton = dialog.AddButton("OK", new (evt) => DoNewClass(folder, evt)); dialog.mEscButton = dialog.AddButton("Cancel"); dialog.AddEdit("Unnamed"); - dialog.PopupWindow(gApp.GetActiveWindow()); + dialog.PopupWindow(gApp.GetActiveWindow());*/ + + var dialog = new GenerateDialog(folder); + dialog.PopupWindow(gApp.GetActiveWindow()); } + public void Regenerate(bool allowHashMismatch) + { + mListView.GetRoot().WithSelectedItems(scope (selectedItem) => + { + if (mListViewToProjectMap.GetValue(selectedItem) case .Ok(var sourceProjectItem)) + { + var dialog = new GenerateDialog(sourceProjectItem, allowHashMismatch); + dialog.PopupWindow(gApp.GetActiveWindow()); + } + }); + } + + public void Regenerate(ProjectSource projectSource, StringView fileText) + { + var sourceViewPanel = gApp.ShowProjectItem(projectSource, false); + sourceViewPanel.mEditWidget.SetText(scope .(fileText)); + } + + public void Generate(ProjectFolder folder, StringView fileName, StringView fileText) + { + let project = folder.mProject; + if (project.mNeedsCreate) + project.FinishCreate(); + String relFileName = scope .(fileName); + if (!relFileName.Contains('.')) + relFileName.Append(".bf"); + + String fullFilePath = scope String(); + String relPath = scope String(); + folder.GetRelDir(relPath); + if (relPath.Length > 0) + relPath.Append("/"); + relPath.Append(relFileName); + folder.mProject.GetProjectFullPath(relPath, fullFilePath); + String dirName = scope String(); + Path.GetDirectoryPath(fullFilePath, dirName); + Directory.CreateDirectory(dirName).IgnoreError(); + + if (File.Exists(fullFilePath)) + { + var error = scope String(); + error.AppendF("File '{0}' already exists", fullFilePath); + IDEApp.sApp.Fail(error); + return; + } + + if (File.WriteAllText(fullFilePath, fileText) case .Err) + { + var error = scope String(); + error.AppendF("Failed to create file '{0}'", fullFilePath); + gApp.Fail(error); + return; + } + + ProjectSource projectSource = new ProjectSource(); + projectSource.mIncludeKind = (folder.mIncludeKind == .Auto) ? .Auto : .Manual; + projectSource.mName.Set(relFileName); + projectSource.mPath = new String(); + folder.mProject.GetProjectRelPath(fullFilePath, projectSource.mPath); + projectSource.mProject = folder.mProject; + projectSource.mParentFolder = folder; + folder.AddChild(projectSource); + let projectItem = AddProjectItem(projectSource); + if (projectItem != null) + { + mListView.GetRoot().SelectItemExclusively(projectItem); + mListView.EnsureItemVisible(projectItem, false); + } + Sort(); + if (folder.mIncludeKind != .Auto) + folder.mProject.SetChanged(); + + gApp.RecordHistoryLocation(true); + gApp.ShowProjectItem(projectSource); + gApp.RecordHistoryLocation(true); + } + void DoNewClass(ProjectFolder folder, DialogEvent evt) { Dialog dlg = (Dialog)evt.mSender; @@ -1470,7 +1550,10 @@ namespace IDE.ui } if (doReleaseRef) + { + projectItem.mDetached = true; projectItem.ReleaseRef(); + } //TODO: Defer this, projectItem is needed for a backgrounded QueueProjectSourceRemoved //delete projectItem; } @@ -2373,7 +2456,15 @@ namespace IDE.ui }); } - if (let projectFolder = projectItem as ProjectFolder) + if (projectItem is ProjectSource) + { + item = menu.AddItem("Regenerate"); + item.mOnMenuItemSelected.Add(new (item) => + { + Regenerate(false); + }); + } + else if (let projectFolder = projectItem as ProjectFolder) { //if (projectFolder.mIncludeKind == .Manual) { @@ -2471,17 +2562,17 @@ namespace IDE.ui } }); - item = menu.AddItem("New Class..."); + item = menu.AddItem("Generate File..."); item.mOnMenuItemSelected.Add(new (item) => { var projectFolder = GetSelectedProjectFolder(); if (projectFolder != null) { if (CheckProjectModify(projectFolder.mProject)) - NewClass(projectFolder); + GenerateCode(projectFolder); } }); - + item = menu.AddItem("Import File..."); item.mOnMenuItemSelected.Add(new (item) => { mImportFileDeferred = true; /* ImportFile();*/ }); diff --git a/IDE/src/ui/SourceViewPanel.bf b/IDE/src/ui/SourceViewPanel.bf index e94ca7d2..6e814b31 100644 --- a/IDE/src/ui/SourceViewPanel.bf +++ b/IDE/src/ui/SourceViewPanel.bf @@ -546,6 +546,11 @@ namespace IDE.ui public ~this() { + if (mProjectSource?.mEditData?.HasTextChanged() == true) + { + mProjectSource.ClearEditData(); + } + if (mInPostRemoveUpdatePanels) { //Debug.WriteLine("Removing sourceViewPanel from mPostRemoveUpdatePanel {0} in ~this ", this); diff --git a/IDE/src/ui/WatchPanel.bf b/IDE/src/ui/WatchPanel.bf index 497beab4..06da55be 100644 --- a/IDE/src/ui/WatchPanel.bf +++ b/IDE/src/ui/WatchPanel.bf @@ -1295,7 +1295,7 @@ namespace IDE.ui addrsCount = mWatchSeriesInfo.mAddrs.Length / entryAddrSize; int totalCount = mWatchSeriesInfo.mCount; - if (totalCount == -1) + if ((totalCount == -1) && (mWatchSeriesInfo.mContinuationData != null)) { //int wantNewCount = Math.Min(idx + 32, mWatchSeriesInfo.mCount) - addrsCount; bool continuationDone = false; diff --git a/IDEHelper/Backend/BeCOFFObject.cpp b/IDEHelper/Backend/BeCOFFObject.cpp index 3ef70a7a..7dffb212 100644 --- a/IDEHelper/Backend/BeCOFFObject.cpp +++ b/IDEHelper/Backend/BeCOFFObject.cpp @@ -555,6 +555,7 @@ void BeCOFFObject::DbgTEndTag() BF_ASSERT(mTTagStartPos != -1); DbgTAlign(); int tagSize = mDebugTSect.mData.GetPos() - mTTagStartPos; + BF_ASSERT(tagSize <= 0xFFFF); *((int16*)&mDebugTSect.mData.mData[mTTagStartPos]) = (int16)(tagSize - 2); mTTagStartPos = -1; } @@ -597,11 +598,7 @@ int BeCOFFObject::DbgGetTypeId(BeDbgType* dbgType, bool doDefine) DbgGetTypeId(structType->mDerivedFrom); for (auto member : structType->mMembers) { - auto type = member->mType; - //TODO: - //if (member->mName == "VersionName") - //continue; - + auto type = member->mType; DbgGetTypeId(type); } for (auto func : structType->mMethods) @@ -627,7 +624,7 @@ int BeCOFFObject::DbgGetTypeId(BeDbgType* dbgType, bool doDefine) auto _CheckFieldOverflow = [&]() { int tagSize = mDebugTSect.mData.GetPos() - mTTagStartPos; - if (tagSize >= 2000) + if (tagSize >= 0xE000) { int extFieldListTag = mCurTagId++; @@ -1013,8 +1010,7 @@ void BeCOFFObject::DbgGenerateTypeInfo() void BeCOFFObject::DbgStartSection(int sectionNum) { auto& outS = mDebugSSect.mData; - BF_ASSERT(mSectionStartPos == -1); - + BF_ASSERT(mSectionStartPos == -1); outS.Write((int32)sectionNum); outS.Write(0); // Temporary - size mSectionStartPos = outS.GetPos(); @@ -1023,7 +1019,7 @@ void BeCOFFObject::DbgStartSection(int sectionNum) void BeCOFFObject::DbgEndSection() { auto& outS = mDebugSSect.mData; - int totalLen = outS.GetPos() - mSectionStartPos; + int totalLen = outS.GetPos() - mSectionStartPos; *((int32*)&outS.mData[mSectionStartPos - 4]) = totalLen; mSectionStartPos = -1; while ((outS.GetPos() & 3) != 0) @@ -1132,7 +1128,7 @@ void BeCOFFObject::DbgSEndTag() { BF_ASSERT(mSTagStartPos != -1); int tagSize = mDebugSSect.mData.GetPos() - mSTagStartPos; - + BF_ASSERT(tagSize <= 0xFFFF); *((uint16*)&mDebugSSect.mData.mData[mSTagStartPos]) = (uint16)(tagSize - 2); mSTagStartPos = -1; } @@ -2146,7 +2142,7 @@ bool BeCOFFObject::Generate(BeModule* module, const StringImpl& fileName) if (mWriteToLib) { DynMemStream memStream; - + Generate(module); mStream = &memStream; diff --git a/IDEHelper/Compiler/BfAutoComplete.cpp b/IDEHelper/Compiler/BfAutoComplete.cpp index b83b2d46..f15efa6e 100644 --- a/IDEHelper/Compiler/BfAutoComplete.cpp +++ b/IDEHelper/Compiler/BfAutoComplete.cpp @@ -1647,7 +1647,7 @@ void BfAutoComplete::CheckIdentifier(BfAstNode* identifierNode, bool isInExpress { "abstract", "base", "class", "const", "delegate", "extern", "enum", "explicit", "extension", "function", - "interface", "in", "internal", "mixin", "namespace", "new", + "interface", "in", "implicit", "internal", "mixin", "namespace", "new", "operator", "out", "override", "params", "private", "protected", "public", "readonly", "ref", "rettype", "return", "scope", "sealed", "static", "struct", "this", "typealias", "using", "virtual", "volatile", "T", "where" diff --git a/IDEHelper/Compiler/BfCompiler.cpp b/IDEHelper/Compiler/BfCompiler.cpp index f45e4b49..8ce3868c 100644 --- a/IDEHelper/Compiler/BfCompiler.cpp +++ b/IDEHelper/Compiler/BfCompiler.cpp @@ -420,6 +420,7 @@ BfCompiler::BfCompiler(BfSystem* bfSystem, bool isResolveOnly) mInternalTypeDef = NULL; mPlatformTypeDef = NULL; mCompilerTypeDef = NULL; + mCompilerGeneratorTypeDef = NULL; mDiagnosticsDebugTypeDef = NULL; mIDisposableTypeDef = NULL; mIIntegerTypeDef = NULL; @@ -6765,6 +6766,7 @@ bool BfCompiler::DoCompile(const StringImpl& outputDirectory) mInternalTypeDef = _GetRequiredType("System.Internal"); mPlatformTypeDef = _GetRequiredType("System.Platform"); mCompilerTypeDef = _GetRequiredType("System.Compiler"); + mCompilerGeneratorTypeDef = _GetRequiredType("System.Compiler.Generator"); mDiagnosticsDebugTypeDef = _GetRequiredType("System.Diagnostics.Debug"); mIDisposableTypeDef = _GetRequiredType("System.IDisposable"); mIIntegerTypeDef = _GetRequiredType("System.IInteger"); @@ -8107,6 +8109,149 @@ String BfCompiler::GetTypeDefList() return result; } +String BfCompiler::GetGeneratorString(BfTypeDef* typeDef, BfTypeInstance* typeInst, const StringImpl& generatorMethodName, const StringImpl* args) +{ + if (typeInst == NULL) + { + auto type = mContext->mUnreifiedModule->ResolveTypeDef(typeDef, BfPopulateType_BaseType); + if (type != NULL) + typeInst = type->ToTypeInstance(); + if (typeInst == NULL) + return ""; + } + + BfTypeVector typeVector; + typeVector.Add(typeInst); + + auto generatorTypeInst = mContext->mUnreifiedModule->ResolveTypeDef(mCompilerGeneratorTypeDef)->ToTypeInstance(); + auto methodDef = generatorTypeInst->mTypeDef->GetMethodByName(generatorMethodName); + auto moduleMethodInstance = mContext->mUnreifiedModule->GetMethodInstance(generatorTypeInst, methodDef, typeVector); + + SetAndRestoreValue prevMethodInstance(mContext->mUnreifiedModule->mCurMethodInstance, moduleMethodInstance.mMethodInstance); + SetAndRestoreValue prevTypeInstance(mContext->mUnreifiedModule->mCurTypeInstance, typeInst); + + BfExprEvaluator exprEvaluator(mContext->mUnreifiedModule); + exprEvaluator.mBfEvalExprFlags = BfEvalExprFlags_Comptime; + + SizedArray irArgs; + if (args != NULL) + irArgs.Add(mContext->mUnreifiedModule->GetStringObjectValue(*args)); + auto callResult = exprEvaluator.CreateCall(NULL, moduleMethodInstance.mMethodInstance, moduleMethodInstance.mFunc, false, irArgs, NULL, BfCreateCallFlags_None); + + if (callResult.mValue.IsConst()) + { + auto stringPtr = mContext->mUnreifiedModule->GetStringPoolString(callResult.mValue, mContext->mUnreifiedModule->mBfIRBuilder); + if (stringPtr != NULL) + return *stringPtr; + } + return ""; +} + +void BfCompiler::HandleGeneratorErrors(StringImpl& result) +{ + if ((mPassInstance->mErrors.IsEmpty()) && (mPassInstance->mOutStream.IsEmpty())) + return; + + result.Clear(); + + for (auto& msg : mPassInstance->mOutStream) + { + String error = msg; + error.Replace('\n', '\r'); + result += "!error\t"; + result += error; + result += "\n"; + } +} + +String BfCompiler::GetGeneratorTypeDefList() +{ + String result; + + BfProject* curProject = NULL; + Dictionary projectIds; + + BfResolvePassData resolvePassData; + SetAndRestoreValue prevResolvePassData(mResolvePassData, &resolvePassData); + BfPassInstance passInstance(mSystem); + SetAndRestoreValue prevPassInstance(mPassInstance, &passInstance); + + for (auto typeDef : mSystem->mTypeDefs) + { + if (typeDef->mProject->mDisabled) + continue; + + if (typeDef->mIsPartial) + continue; + + auto type = mContext->mUnreifiedModule->ResolveTypeDef(typeDef, BfPopulateType_BaseType); + if ((type != NULL) && (type->IsTypeInstance())) + { + auto typeInst = type->ToTypeInstance(); + if ((typeInst->mBaseType != NULL) && (typeInst->mBaseType->IsInstanceOf(mCompilerGeneratorTypeDef))) + { + result += typeDef->mProject->mName; + result += ":"; + result += BfTypeUtils::TypeToString(typeDef, BfTypeNameFlag_InternalName); + String nameString = GetGeneratorString(typeDef, typeInst, "GetName", NULL); + if (!nameString.IsEmpty()) + result += "\t" + nameString; + result += "\n"; + } + } + } + + HandleGeneratorErrors(result); + + return result; +} + +String BfCompiler::GetGeneratorInitData(const StringImpl& typeName, const StringImpl& args) +{ + BfResolvePassData resolvePassData; + SetAndRestoreValue prevResolvePassData(mResolvePassData, &resolvePassData); + BfPassInstance passInstance(mSystem); + SetAndRestoreValue prevPassInstance(mPassInstance, &passInstance); + + Array typeDefs; + GetTypeDefs(typeName, typeDefs); + + String result; + for (auto typeDef : typeDefs) + { + result += GetGeneratorString(typeDef, NULL, "InitUI", &args); + if (!result.IsEmpty()) + break; + } + + HandleGeneratorErrors(result); + + return result; +} + +String BfCompiler::GetGeneratorGenData(const StringImpl& typeName, const StringImpl& args) +{ + BfResolvePassData resolvePassData; + SetAndRestoreValue prevResolvePassData(mResolvePassData, &resolvePassData); + BfPassInstance passInstance(mSystem); + SetAndRestoreValue prevPassInstance(mPassInstance, &passInstance); + + Array typeDefs; + GetTypeDefs(typeName, typeDefs); + + String result; + for (auto typeDef : typeDefs) + { + result += GetGeneratorString(typeDef, NULL, "Generate", &args); + if (!result.IsEmpty()) + break; + } + + HandleGeneratorErrors(result); + + return result; +} + struct TypeDefMatchHelper { public: @@ -8600,9 +8745,9 @@ String BfCompiler::GetTypeDefMatches(const StringImpl& searchStr) return result; } -String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName) +void BfCompiler::GetTypeDefs(const StringImpl& inTypeName, Array& typeDefs) { - BfProject* project = NULL; + BfProject* project = NULL; int idx = 0; int sep = (int)inTypeName.IndexOf(':'); @@ -8615,7 +8760,7 @@ String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName) String typeName; int genericCount = 0; int pendingGenericCount = 0; - for ( ; idx < (int)inTypeName.length(); idx++) + for (; idx < (int)inTypeName.length(); idx++) { char c = inTypeName[idx]; if (c == '<') @@ -8626,7 +8771,7 @@ String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName) genericCount++; else if (c == '>') { - pendingGenericCount = genericCount; + pendingGenericCount = genericCount; genericCount = 0; } } @@ -8640,10 +8785,10 @@ String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName) typeName += c; } } - + bool isGlobals = false; if (typeName == ":static") - { + { typeName.clear(); isGlobals = true; } @@ -8657,63 +8802,73 @@ String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName) if (typeName[i] == '+') typeName[i] = '.'; - String result; - TypeDefMatchHelper matchHelper(result); - - BfAtomComposite nameComposite; + BfAtomComposite nameComposite; if ((typeName.IsEmpty()) || (mSystem->ParseAtomComposite(typeName, nameComposite))) - { + { auto itr = mSystem->mTypeDefs.TryGet(nameComposite); while (itr) - { + { auto typeDef = *itr; if ((!typeDef->mIsPartial) && (typeDef->mProject == project) && (typeDef->mFullName == nameComposite) && (typeDef->IsGlobalsContainer() == isGlobals) && (typeDef->GetSelfGenericParamCount() == pendingGenericCount)) - { - auto refNode = typeDef->GetRefNode(); - result += "S"; - matchHelper.AddLocation(refNode); - result += "\n"; - - for (auto fieldDef : typeDef->mFields) - { - result += "F"; - result += fieldDef->mName; - matchHelper.AddFieldDef(fieldDef); - } - - for (auto propDef : typeDef->mProperties) - { - if (propDef->GetRefNode() == NULL) - continue; - - result += "P"; - matchHelper.AddPropertyDef(typeDef, propDef); - } - - for (auto methodDef : typeDef->mMethods) - { - if ((methodDef->mMethodType != BfMethodType_Normal) && - (methodDef->mMethodType != BfMethodType_Mixin) && - (methodDef->mMethodType != BfMethodType_Ctor) && - (methodDef->mMethodType != BfMethodType_Dtor)) - continue; - - if (methodDef->mMethodDeclaration == NULL) - continue; - - result += "M"; - matchHelper.AddMethodDef(methodDef); - } + { + typeDefs.Add(typeDef); } itr.MoveToNextHashMatch(); } } +} +String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName) +{ + Array typeDefs; + GetTypeDefs(inTypeName, typeDefs); + + String result; + TypeDefMatchHelper matchHelper(result); + + for (auto typeDef : typeDefs) + { + auto refNode = typeDef->GetRefNode(); + result += "S"; + matchHelper.AddLocation(refNode); + result += "\n"; + + for (auto fieldDef : typeDef->mFields) + { + result += "F"; + result += fieldDef->mName; + matchHelper.AddFieldDef(fieldDef); + } + + for (auto propDef : typeDef->mProperties) + { + if (propDef->GetRefNode() == NULL) + continue; + + result += "P"; + matchHelper.AddPropertyDef(typeDef, propDef); + } + + for (auto methodDef : typeDef->mMethods) + { + if ((methodDef->mMethodType != BfMethodType_Normal) && + (methodDef->mMethodType != BfMethodType_Mixin) && + (methodDef->mMethodType != BfMethodType_Ctor) && + (methodDef->mMethodType != BfMethodType_Dtor)) + continue; + + if (methodDef->mMethodDeclaration == NULL) + continue; + + result += "M"; + matchHelper.AddMethodDef(methodDef); + } + } return result; } @@ -8990,6 +9145,30 @@ BF_EXPORT void BF_CALLTYPE BfCompiler_ProgramDone() #endif } +BF_EXPORT const char* BF_CALLTYPE BfCompiler_GetGeneratorTypeDefList(BfCompiler* bfCompiler) +{ + String& outString = *gTLStrReturn.Get(); + outString.clear(); + outString = bfCompiler->GetGeneratorTypeDefList(); + return outString.c_str(); +} + +BF_EXPORT const char* BF_CALLTYPE BfCompiler_GetGeneratorInitData(BfCompiler* bfCompiler, char* typeDefName, char* args) +{ + String& outString = *gTLStrReturn.Get(); + outString.clear(); + outString = bfCompiler->GetGeneratorInitData(typeDefName, args); + return outString.c_str(); +} + +BF_EXPORT const char* BF_CALLTYPE BfCompiler_GetGeneratorGenData(BfCompiler* bfCompiler, char* typeDefName, char* args) +{ + String& outString = *gTLStrReturn.Get(); + outString.clear(); + outString = bfCompiler->GetGeneratorGenData(typeDefName, args); + return outString.c_str(); +} + BF_EXPORT const char* BF_CALLTYPE BfCompiler_GetTypeDefList(BfCompiler* bfCompiler) { String& outString = *gTLStrReturn.Get(); diff --git a/IDEHelper/Compiler/BfCompiler.h b/IDEHelper/Compiler/BfCompiler.h index d859da0f..9c2dbef6 100644 --- a/IDEHelper/Compiler/BfCompiler.h +++ b/IDEHelper/Compiler/BfCompiler.h @@ -374,6 +374,7 @@ public: BfTypeDef* mInternalTypeDef; BfTypeDef* mPlatformTypeDef; BfTypeDef* mCompilerTypeDef; + BfTypeDef* mCompilerGeneratorTypeDef; BfTypeDef* mDiagnosticsDebugTypeDef; BfTypeDef* mIDisposableTypeDef; BfTypeDef* mIIntegerTypeDef; @@ -511,9 +512,15 @@ public: void ProcessAutocompleteTempType(); void GetSymbolReferences(); void Cancel(); - void RequestFastFinish(); + void RequestFastFinish(); String GetTypeDefList(); - String GetTypeDefMatches(const StringImpl& searchSrc); + String GetGeneratorString(BfTypeDef* typeDef, BfTypeInstance* typeInst, const StringImpl& generatorMethodName, const StringImpl* args); + void HandleGeneratorErrors(StringImpl& result); + String GetGeneratorTypeDefList(); + String GetGeneratorInitData(const StringImpl& typeName, const StringImpl& args); + String GetGeneratorGenData(const StringImpl& typeName, const StringImpl& args); + String GetTypeDefMatches(const StringImpl& searchSrc); + void GetTypeDefs(const StringImpl& typeName, Array& typeDefs); String GetTypeDefInfo(const StringImpl& typeName); int GetEmitSource(const StringImpl& fileName, StringImpl* outBuffer); diff --git a/IDEHelper/Compiler/BfContext.cpp b/IDEHelper/Compiler/BfContext.cpp index 2de7a71a..f338c2f1 100644 --- a/IDEHelper/Compiler/BfContext.cpp +++ b/IDEHelper/Compiler/BfContext.cpp @@ -892,6 +892,9 @@ void BfContext::RebuildType(BfType* type, bool deleteOnDemandTypes, bool rebuild return; } + if ((typeInst->IsBoxed()) && (typeInst->mTypeDef->mEmitParent != NULL)) + typeInst->mTypeDef = typeInst->mTypeDef->mEmitParent; + if (mSystem->mWorkspaceConfigChanged) { typeInst->mTypeOptionsIdx = -2; @@ -1060,8 +1063,12 @@ void BfContext::RebuildType(BfType* type, bool deleteOnDemandTypes, bool rebuild if (typeInst->mTypeDef->mEmitParent != NULL) { auto emitTypeDef = typeInst->mTypeDef; - typeInst->mTypeDef = emitTypeDef->mEmitParent; - delete emitTypeDef; + typeInst->mTypeDef = emitTypeDef->mEmitParent; + BfLogSysM("Type %p queueing delete of typeDef %p, resetting typeDef to %p\n", typeInst, emitTypeDef, typeInst->mTypeDef); + emitTypeDef->mDefState = BfTypeDef::DefState_Deleted; + AutoCrit autoCrit(mSystem->mDataLock); + BF_ASSERT(!mSystem->mTypeDefDeleteQueue.Contains(emitTypeDef)); + mSystem->mTypeDefDeleteQueue.push_back(emitTypeDef); } //typeInst->mTypeDef->ClearEmitted(); @@ -1912,10 +1919,17 @@ void BfContext::UpdateRevisedTypes() if (typeDef->mEmitParent != NULL) { - auto emitTypeDef = typeDef; - typeDef = typeDef->mEmitParent; - if (typeDef->mNextRevision != NULL) - emitTypeDef->mDefState = BfTypeDef::DefState_EmittedDirty; + if (typeDef->mDefState == BfTypeDef::DefState_Deleted) + { + typeInst->mTypeDef = typeDef->mEmitParent; + } + else + { + auto emitTypeDef = typeDef; + typeDef = typeDef->mEmitParent; + if (typeDef->mNextRevision != NULL) + emitTypeDef->mDefState = BfTypeDef::DefState_EmittedDirty; + } } if (typeDef->mProject->mDisabled) diff --git a/IDEHelper/Compiler/BfExprEvaluator.cpp b/IDEHelper/Compiler/BfExprEvaluator.cpp index e6d3ba2b..a7750009 100644 --- a/IDEHelper/Compiler/BfExprEvaluator.cpp +++ b/IDEHelper/Compiler/BfExprEvaluator.cpp @@ -3578,7 +3578,7 @@ void BfExprEvaluator::GetLiteral(BfAstNode* refNode, const BfVariant& variant) if ((mExpectingType != NULL) && (mExpectingType->IsIntegral()) && (mExpectingType->IsChar() == IsCharType(variant.mTypeCode))) { auto primType = (BfPrimitiveType*)mExpectingType; - if (mModule->mSystem->DoesLiteralFit(primType->mTypeDef->mTypeCode, variant.mInt64)) + if (mModule->mSystem->DoesLiteralFit(primType->mTypeDef->mTypeCode, variant.mUInt64)) { mResult = BfTypedValue(mModule->mBfIRBuilder->CreateConst(primType->mTypeDef->mTypeCode, variant.mUInt64), mExpectingType); break; @@ -5090,7 +5090,9 @@ void BfExprEvaluator::ResolveArgValues(BfResolvedArgs& resolvedArgs, BfResolveAr BfExprEvaluator exprEvaluator(mModule); exprEvaluator.mResolveGenericParam = (flags & BfResolveArgsFlag_AllowUnresolvedTypes) == 0; - exprEvaluator.mBfEvalExprFlags = (BfEvalExprFlags)(exprEvaluator.mBfEvalExprFlags | BfEvalExprFlags_AllowRefExpr | BfEvalExprFlags_AllowOutExpr); + exprEvaluator.mBfEvalExprFlags = (BfEvalExprFlags)(exprEvaluator.mBfEvalExprFlags | BfEvalExprFlags_AllowRefExpr | BfEvalExprFlags_AllowOutExpr | + (mBfEvalExprFlags & (BfEvalExprFlags_Comptime))); + bool handled = false; bool evaluated = false; @@ -6373,16 +6375,23 @@ void BfExprEvaluator::FinishDeferredEvals(BfResolvedArgs& argValues) auto variableDeclaration = BfNodeDynCast((*argValues.mArguments)[argIdx]); if ((variableDeclaration != NULL) && (variableDeclaration->mNameNode != NULL)) { - BfLocalVariable* localVar = new BfLocalVariable(); - localVar->mName = variableDeclaration->mNameNode->ToString(); - localVar->mResolvedType = mModule->GetPrimitiveType(BfTypeCode_Var); - localVar->mAddr = mModule->mBfIRBuilder->GetFakeVal(); - localVar->mReadFromId = 0; - localVar->mWrittenToId = 0; - localVar->mAssignedKind = BfLocalVarAssignKind_Unconditional; - mModule->CheckVariableDef(localVar); - localVar->Init(); - mModule->AddLocalVariableDef(localVar, true); + if (mModule->mCurMethodState == NULL) + { + mModule->Fail("Illegal local variable", variableDeclaration); + } + else + { + BfLocalVariable* localVar = new BfLocalVariable(); + localVar->mName = variableDeclaration->mNameNode->ToString(); + localVar->mResolvedType = mModule->GetPrimitiveType(BfTypeCode_Var); + localVar->mAddr = mModule->mBfIRBuilder->GetFakeVal(); + localVar->mReadFromId = 0; + localVar->mWrittenToId = 0; + localVar->mAssignedKind = BfLocalVarAssignKind_Unconditional; + mModule->CheckVariableDef(localVar); + localVar->Init(); + mModule->AddLocalVariableDef(localVar, true); + } } } } @@ -10139,6 +10148,12 @@ void BfExprEvaluator::Visit(BfInitializerExpression* initExpr) for (auto elementExpr : initExpr->mValues) { + if ((mBfEvalExprFlags & BfEvalExprFlags_Comptime) != 0) + { + mModule->Fail("Comptime cannot evaluate initializer expressions", elementExpr); + break; + } + bool wasValidInitKind = false; if (auto assignExpr = BfNodeDynCast(elementExpr)) @@ -11133,8 +11148,7 @@ void BfExprEvaluator::Visit(BfCastExpression* castExpr) bool BfExprEvaluator::IsExactMethodMatch(BfMethodInstance* methodA, BfMethodInstance* methodB, bool ignoreImplicitParams) { if (methodA->mReturnType != methodB->mReturnType) - return false; - + return false; int implicitParamCountA = methodA->GetImplicitParamCount(); if (methodA->HasExplicitThis()) implicitParamCountA++; @@ -11696,7 +11710,11 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr) } else { - if (!IsExactMethodMatch(methodInstance, bindMethodInstance, true)) + bool isExactMethodMatch = IsExactMethodMatch(methodInstance, bindMethodInstance, true); + if ((mExpectingType != NULL) && (mExpectingType->IsFunction()) && (methodInstance->mMethodDef->mIsMutating != bindMethodInstance->mMethodDef->mIsMutating)) + isExactMethodMatch = false; + + if (!isExactMethodMatch) { if (bindResult.mCheckedMultipleMethods) { @@ -11704,8 +11722,8 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr) mModule->TypeToString(delegateTypeInstance).c_str()), delegateBindExpr->mTarget); } else - { - mModule->Fail(StrFormat("Method '%s' does not match %s '%s'", mModule->MethodToString(bindMethodInstance).c_str(), bindTypeName, + { + mModule->Fail(StrFormat("Method '%s' does not match %s '%s'", mModule->MethodToString(bindMethodInstance, (BfMethodNameFlags)(BfMethodNameFlag_ResolveGenericParamNames | BfMethodNameFlag_IncludeReturnType | BfMethodNameFlag_IncludeMut)).c_str(), bindTypeName, mModule->TypeToString(delegateTypeInstance).c_str()), delegateBindExpr->mTarget); } mResult = BfTypedValue(); @@ -11725,7 +11743,7 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr) } bool hasIncompatibleCallingConventions = !mModule->mSystem->IsCompatibleCallingConvention(methodInstance->mCallingConvention, bindMethodInstance->mCallingConvention); - + auto _GetInvokeMethodName = [&]() { String methodName = "Invoke$"; @@ -11789,7 +11807,7 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr) if (mExpectingType->IsFunction()) { BfIRValue result; - if ((hasIncompatibleCallingConventions) && (mModule->HasCompiledOutput())) + if ((hasIncompatibleCallingConventions) && (mModule->HasExecutedOutput())) { // { @@ -11799,11 +11817,14 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr) if (result) { - String methodName = _GetInvokeMethodName(); + String methodName = _GetInvokeMethodName(); SizedArray irParamTypes; BfIRType irReturnType; - bindMethodInstance->GetIRFunctionInfo(mModule, irReturnType, irParamTypes); + methodInstance->GetIRFunctionInfo(mModule, irReturnType, irParamTypes); + + int thisFuncParamIdx = methodInstance->GetThisIdx(); + int thisBindParamIdx = methodInstance->GetThisIdx(); auto prevActiveFunction = mModule->mBfIRBuilder->GetActiveFunction(); auto prevInsertBlock = mModule->mBfIRBuilder->GetInsertBlock(); @@ -11949,7 +11970,7 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr) // Do we need a special delegate type for this? if (((captureThisByValue) || (needsSplat) || (implicitParamCount > 0) /*|| (hasIncompatibleCallingConventions)*/) && - (mModule->HasCompiledOutput())) + (mModule->HasExecutedOutput())) { hasCaptures = true; auto curProject = mModule->mCurTypeInstance->mTypeDef->mProject; @@ -12030,7 +12051,7 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr) // Do we need specialized calling code for this? BfIRValue funcValue; if (((needsSplat) || (implicitParamCount > 0) || (hasIncompatibleCallingConventions)) && - (mModule->HasCompiledOutput())) + (mModule->HasExecutedOutput())) { int fieldIdx = 0; for (int implicitParamIdx = bindMethodInstance->HasThis() ? -1 : 0; implicitParamIdx < implicitParamCount; implicitParamIdx++) @@ -12208,7 +12229,7 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr) // >> delegate.mTarget = bindResult.mTarget BfIRValue valPtr; - if (mModule->HasCompiledOutput()) + if (mModule->HasExecutedOutput()) { if ((implicitParamCount > 0) || (needsSplat)) // Point back to self, it contains capture data valPtr = mModule->mBfIRBuilder->CreateBitCast(mResult.mValue, mModule->mBfIRBuilder->GetPrimitiveType(BfTypeCode_NullPtr)); @@ -12226,7 +12247,7 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr) if (!funcValue) { - if ((mModule->HasCompiledOutput()) && (!mModule->mBfIRBuilder->mIgnoreWrites)) + if ((mModule->HasExecutedOutput()) && (!mModule->mBfIRBuilder->mIgnoreWrites)) mModule->AssertErrorState(); return; } @@ -13188,7 +13209,7 @@ BfLambdaInstance* BfExprEvaluator::GetLambdaInstance(BfLambdaBindExpression* lam mModule->mIncompleteMethodCount++; SetAndRestoreValue prevClosureState(mModule->mCurMethodState->mClosureState, &closureState); - if (mModule->HasCompiledOutput()) + if (mModule->HasExecutedOutput()) mModule->SetupIRMethod(methodInstance, methodInstance->mIRFunction, methodInstance->mAlwaysInline); // This keeps us from giving errors twice. ProcessMethod can give errors when we capture by value but needed to @@ -14415,7 +14436,7 @@ void BfExprEvaluator::CreateObject(BfObjectCreateExpression* objCreateExpr, BfAs { if (!bindResult.mFunc) { - BF_ASSERT((!mModule->HasCompiledOutput()) || (mModule->mBfIRBuilder->mIgnoreWrites)); + BF_ASSERT((!mModule->HasExecutedOutput()) || (mModule->mBfIRBuilder->mIgnoreWrites)); appendSizeValue = mModule->GetConstValue(0); } else @@ -19717,7 +19738,7 @@ void BfExprEvaluator::Visit(BfIndexerExpression* indexerExpr) } } } - else if (((mModule->HasCompiledOutput()) || (mModule->mIsComptimeModule)) && + else if (((mModule->HasExecutedOutput()) || (mModule->mIsComptimeModule)) && (wantsChecks)) { if (checkedKind == BfCheckedKind_NotSet) diff --git a/IDEHelper/Compiler/BfIRBuilder.cpp b/IDEHelper/Compiler/BfIRBuilder.cpp index cc43725c..95307c23 100644 --- a/IDEHelper/Compiler/BfIRBuilder.cpp +++ b/IDEHelper/Compiler/BfIRBuilder.cpp @@ -857,6 +857,27 @@ BfIRValue BfIRConstHolder::CreateConstArrayZero(int count) return irValue; } +BfIRValue BfIRConstHolder::CreateConstBitCast(BfIRValue val, BfIRType type) +{ + auto constVal = GetConstant(val); + + auto bitCast = mTempAlloc.Alloc(); + if ((constVal == NULL) || (constVal->IsNull())) + bitCast->mConstType = BfConstType_BitCastNull; + else + bitCast->mConstType = BfConstType_BitCast; + BF_ASSERT(val.mId != -1); + bitCast->mTarget = val.mId; + bitCast->mToType = type; + + BfIRValue castedVal(BfIRValueFlags_Const, mTempAlloc.GetChunkedId(bitCast)); +#ifdef CHECK_CONSTHOLDER + castedVal.mHolder = this; +#endif + BF_ASSERT((void*)GetConstant(castedVal) == (void*)bitCast); + return castedVal; +} + BfIRValue BfIRConstHolder::CreateTypeOf(BfType* type) { BfTypeOf_Const* typeOf = mTempAlloc.Alloc(); @@ -2970,10 +2991,10 @@ void BfIRBuilder::CreateDbgTypeDefinition(BfType* type) diFieldTypes.push_back(memberType); } - bool isPayloadEnum = (typeInstance->IsEnum()) && (!typeInstance->IsTypedPrimitive()); - for (auto& fieldInstanceRef : typeInstance->mFieldInstances) + bool isPayloadEnum = (typeInstance->IsEnum()) && (!typeInstance->IsTypedPrimitive()); + for (int fieldIdx = 0; fieldIdx < typeInstance->mFieldInstances.mSize; fieldIdx++) { - auto fieldInstance = &fieldInstanceRef; + auto fieldInstance = &typeInstance->mFieldInstances[fieldIdx]; if (!fieldInstance->mFieldIncluded) continue; auto fieldDef = fieldInstance->GetFieldDef(); @@ -3091,18 +3112,15 @@ void BfIRBuilder::CreateDbgTypeDefinition(BfType* type) { staticValue = ConstToMemory(staticValue); wasMadeAddr = true; - } - else if (resolvedFieldType->IsPointer()) + } + else if (constant->mTypeCode == BfTypeCode_StringId) { int stringId = constant->mInt32; const StringImpl& str = mModule->mContext->mStringObjectIdMap[stringId].mString; - staticValue = mModule->GetStringCharPtr(str); - } - else if (constant->mTypeCode == BfTypeCode_StringId) - { - int stringId = constant->mInt32; - const StringImpl& str = mModule->mContext->mStringObjectIdMap[stringId].mString; - staticValue = mModule->GetStringObjectValue(str); + if (resolvedFieldType->IsPointer()) + staticValue = mModule->GetStringCharPtr(str); + else + staticValue = mModule->GetStringObjectValue(str); } else { @@ -4417,25 +4435,7 @@ BfIRValue BfIRBuilder::CreateNot(BfIRValue val) BfIRValue BfIRBuilder::CreateBitCast(BfIRValue val, BfIRType type) { if (val.IsConst()) - { - auto constVal = GetConstant(val); - - auto bitCast = mTempAlloc.Alloc(); - if (constVal->IsNull()) - bitCast->mConstType = BfConstType_BitCastNull; - else - bitCast->mConstType = BfConstType_BitCast; - bitCast->mTarget = val.mId; - bitCast->mToType = type; - - BfIRValue castedVal(BfIRValueFlags_Const, mTempAlloc.GetChunkedId(bitCast)); -#ifdef CHECK_CONSTHOLDER - castedVal.mHolder = this; -#endif - BF_ASSERT((void*)GetConstant(castedVal) == (void*)bitCast); - return castedVal; - } - + return CreateConstBitCast(val, type); auto retVal = WriteCmd(BfIRCmd_BitCast, val, type); NEW_CMD_INSERTED_IRVALUE; return retVal; diff --git a/IDEHelper/Compiler/BfIRBuilder.h b/IDEHelper/Compiler/BfIRBuilder.h index 3d045deb..64c0f55d 100644 --- a/IDEHelper/Compiler/BfIRBuilder.h +++ b/IDEHelper/Compiler/BfIRBuilder.h @@ -934,6 +934,7 @@ public: BfIRValue CreateConstAggCE(BfIRType type, addr_ce ptr); BfIRValue CreateConstArrayZero(BfIRType type, int count); BfIRValue CreateConstArrayZero(int count); + BfIRValue CreateConstBitCast(BfIRValue val, BfIRType type); BfIRValue CreateTypeOf(BfType* type); BfIRValue CreateTypeOf(BfType* type, BfIRValue typeData); BfIRValue GetUndefConstValue(BfIRType type); diff --git a/IDEHelper/Compiler/BfModule.cpp b/IDEHelper/Compiler/BfModule.cpp index f9074614..760f6c68 100644 --- a/IDEHelper/Compiler/BfModule.cpp +++ b/IDEHelper/Compiler/BfModule.cpp @@ -7889,7 +7889,7 @@ bool BfModule::CheckGenericConstraints(const BfGenericParamSource& genericParamS { if (BfIRConstHolder::IsInt(primType->mTypeDef->mTypeCode)) { - if (!mCompiler->mSystem->DoesLiteralFit(primType->mTypeDef->mTypeCode, constExprValueType->mValue.mInt64)) + if (!mCompiler->mSystem->DoesLiteralFit(primType->mTypeDef->mTypeCode, constExprValueType->mValue.mUInt64)) { if ((!ignoreErrors) && (PreFail())) *errorOut = Fail(StrFormat("Const generic argument '%s', declared with const '%lld', does not fit into const constraint '%s' for '%s'", genericParamInst->GetName().c_str(), @@ -9617,6 +9617,11 @@ bool BfModule::HasCompiledOutput() return (!mSystem->mIsResolveOnly) && (mGeneratesCode) && (!mIsComptimeModule); } +bool BfModule::HasExecutedOutput() +{ + return ((!mSystem->mIsResolveOnly) && (mGeneratesCode)) || (mIsComptimeModule); +} + // We will skip the object access check for any occurrences of this value void BfModule::SkipObjectAccessCheck(BfTypedValue typedVal) { @@ -10519,7 +10524,7 @@ bool BfModule::HasMixin(BfTypeInstance* typeInstance, const StringImpl& methodNa } StringT<128> BfModule::MethodToString(BfMethodInstance* methodInst, BfMethodNameFlags methodNameFlags, BfTypeVector* typeGenericArgs, BfTypeVector* methodGenericArgs) -{ +{ auto methodDef = methodInst->mMethodDef; bool allowResolveGenericParamNames = ((methodNameFlags & BfMethodNameFlag_ResolveGenericParamNames) != 0); @@ -10764,8 +10769,16 @@ StringT<128> BfModule::MethodToString(BfMethodInstance* methodInst, BfMethodName if (accessorString.length() != 0) { - methodName += " " + accessorString; + methodName += " "; + methodName += accessorString; } + + if ((methodNameFlags & BfMethodNameFlag_IncludeMut) != 0) + { + if ((methodDef->mIsMutating) && (methodInst->GetOwner()->IsValueType())) + methodName += " mut"; + } + return methodName; } @@ -10860,8 +10873,17 @@ void BfModule::CurrentAddToConstHolder(BfIRValue& irVal) auto origConst = irVal; if ((constant->mConstType == BfConstType_BitCast) || (constant->mConstType == BfConstType_BitCastNull)) { - auto bitcast = (BfConstantBitCast*)constant; - constant = mBfIRBuilder->GetConstantById(bitcast->mTarget); + auto bitcast = (BfConstantBitCast*)constant; + BfIRValue newVal; + if (bitcast->mTarget) + { + newVal = BfIRValue(BfIRValueFlags_Const, bitcast->mTarget); + CurrentAddToConstHolder(newVal); + } + else + newVal = mCurTypeInstance->GetOrCreateConstHolder()->CreateConstNull(); + irVal = mCurTypeInstance->GetOrCreateConstHolder()->CreateConstBitCast(newVal, bitcast->mToType); + return; } irVal = mCurTypeInstance->CreateConst(constant, mBfIRBuilder); @@ -10986,7 +11008,7 @@ BfIRValue BfModule::ConstantToCurrent(BfConstant* constant, BfIRConstHolder* con wantType = mContext->mTypes[constant->mIRType.mId]; if (wantType == NULL) - return constHolder->CreateConstNull(); + return mBfIRBuilder->CreateConstNull(); return GetDefaultValue(wantType); } @@ -11034,8 +11056,21 @@ BfIRValue BfModule::ConstantToCurrent(BfConstant* constant, BfIRConstHolder* con return mBfIRBuilder->CreateIntToPtr(ConstantToCurrent(fromTarget, constHolder, NULL), toIRType); } + if ((constant->mConstType == BfConstType_BitCast) || (constant->mConstType == BfConstType_BitCastNull)) + { + auto bitcast = (BfConstantBitCast*)constant; + auto fromTarget = constHolder->GetConstantById(bitcast->mTarget); + BfIRType toIRType = bitcast->mToType; + if (toIRType.mKind == BfIRTypeData::TypeKind_TypeId) + { + auto toType = mContext->mTypes[toIRType.mId]; + toIRType = mBfIRBuilder->MapType(toType); + } + return mBfIRBuilder->CreateBitCast(ConstantToCurrent(fromTarget, constHolder, NULL), toIRType); + } + if (constant->mConstType == BfConstType_Agg) - { + { auto constArray = (BfConstantAgg*)constant; if ((wantType == NULL) && (constArray->mType.mKind == BfIRTypeData::TypeKind_TypeId)) @@ -14456,7 +14491,7 @@ BfLocalVariable* BfModule::AddLocalVariableDef(BfLocalVariable* localVarDef, boo if ((localVarDef->mNameNode != NULL) && (mCurMethodInstance != NULL)) { bool isClosureProcessing = (mCurMethodState->mClosureState != NULL) && (!mCurMethodState->mClosureState->mCapturing); - if ((!isClosureProcessing) && (mCompiler->mResolvePassData != NULL) && (localVarDef->mNameNode != NULL) && (!mIsComptimeModule)) + if ((!isClosureProcessing) && (mCompiler->mResolvePassData != NULL) && (localVarDef->mNameNode != NULL) && (rootMethodState->mMethodInstance != NULL) && (!mIsComptimeModule)) mCompiler->mResolvePassData->HandleLocalReference(localVarDef->mNameNode, rootMethodState->mMethodInstance->GetOwner()->mTypeDef, rootMethodState->mMethodInstance->mMethodDef, localVarDef->mLocalVarId); } @@ -15273,14 +15308,18 @@ void BfModule::AssertErrorState() { if (mCurTypeInstance->mTypeFailed) return; - if (mCurTypeInstance->mTypeDef->mSource->mParsingFailed) + if ((mCurTypeInstance->mTypeDef->GetDefinition()->mSource != NULL) && (mCurTypeInstance->mTypeDef->GetDefinition()->mSource->mParsingFailed)) return; } if (mCurMethodInstance != NULL) { - if ((mCurMethodInstance->mMethodDef->mDeclaringType != NULL) && (mCurMethodInstance->mMethodDef->mDeclaringType->mSource->mParsingFailed)) + if ((mCurMethodInstance->mMethodDef->mDeclaringType != NULL) && + (mCurMethodInstance->mMethodDef->mDeclaringType->mSource != NULL) && + (mCurMethodInstance->mMethodDef->mDeclaringType->mSource->mParsingFailed)) return; - if ((mCurMethodState != NULL) && (mCurMethodState->mMixinState != NULL) && (mCurMethodState->mMixinState->mMixinMethodInstance->mMethodDef->mDeclaringType->mSource->mParsingFailed)) + if ((mCurMethodState != NULL) && (mCurMethodState->mMixinState != NULL) && + (mCurMethodState->mMixinState->mMixinMethodInstance->mMethodDef->mDeclaringType->mSource != NULL) && + (mCurMethodState->mMixinState->mMixinMethodInstance->mMethodDef->mDeclaringType->mSource->mParsingFailed)) return; } @@ -16203,6 +16242,8 @@ void BfModule::EmitDtorBody() BfIRValue BfModule::CreateDllImportGlobalVar(BfMethodInstance* methodInstance, bool define) { + BF_ASSERT(methodInstance->mIsReified); + auto typeInstance = methodInstance->GetOwner(); bool foundDllImportAttr = false; @@ -16467,7 +16508,7 @@ void BfModule::SetupIRMethod(BfMethodInstance* methodInstance, BfIRFunction func auto elementType = refType->mElementType; PopulateType(elementType, BfPopulateType_Data); addDeref = elementType->mSize; - if ((addDeref <= 0) && (!elementType->IsValuelessType())) + if ((addDeref <= 0) && (!elementType->IsValuelessType()) && (!elementType->IsOpaque())) AssertErrorState(); } if ((resolvedTypeRef->IsComposite()) && (!resolvedTypeRef->IsTypedPrimitive())) @@ -17041,7 +17082,7 @@ void BfModule::EmitCtorBody(bool& skipBody) break; } - if ((HasCompiledOutput()) && (matchedMethod != NULL)) + if ((HasExecutedOutput()) && (matchedMethod != NULL)) { SizedArray args; auto ctorBodyMethodInstance = GetMethodInstance(mCurTypeInstance->mBaseType, matchedMethod, BfTypeVector()); @@ -18486,7 +18527,7 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup) return; } - if (HasCompiledOutput()) + if (HasExecutedOutput()) { BF_ASSERT(mIsModuleMutable); } @@ -19713,7 +19754,7 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup) skipBody = true; skipEndChecks = true; - if ((HasCompiledOutput()) || (mIsComptimeModule)) + if (HasExecutedOutput()) { // Clear out DebugLoc - to mark the ".addr" code as part of prologue mBfIRBuilder->ClearDebugLocation(); @@ -19743,12 +19784,8 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup) mBfIRBuilder->CreateRetVoid(); } else - { - BF_ASSERT(!innerMethodInstance.mMethodInstance->mMethodDef->mDeclaringType->IsEmitted()); + { auto innerMethodDef = innerMethodInstance.mMethodInstance->mMethodDef; - if (innerType->mTypeDef->IsEmitted()) - innerMethodDef = innerType->mTypeDef->mEmitParent->mMethods[innerMethodDef->mIdx]; - BF_ASSERT(innerMethodDef == methodDef); SizedArray innerParams; @@ -19977,7 +20014,7 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup) else if ((mCurTypeInstance->IsEnum()) && (!mCurTypeInstance->IsBoxed()) && (methodDef->mName == BF_METHODNAME_TO_STRING)) { auto enumType = ResolveTypeDef(mCompiler->mEnumTypeDef); - if ((HasCompiledOutput()) || (mIsComptimeModule)) + if (HasExecutedOutput()) { EmitEnumToStringBody(); } @@ -19990,7 +20027,7 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup) else if ((mCurTypeInstance->IsTuple()) && (!mCurTypeInstance->IsBoxed()) && (methodDef->mName == BF_METHODNAME_TO_STRING)) { auto enumType = ResolveTypeDef(mCompiler->mEnumTypeDef); - if ((HasCompiledOutput()) || (mIsComptimeModule)) + if (HasExecutedOutput()) { EmitTupleToStringBody(); } @@ -20032,7 +20069,7 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup) { mBfIRBuilder->CreateRetVoid(); } - else if ((HasCompiledOutput()) || (mIsComptimeModule)) + else if (HasExecutedOutput()) { String autoPropName = typeDef->GetAutoPropertyName(propertyDeclaration); BfFieldInstance* fieldInstance = GetFieldByName(mCurTypeInstance, autoPropName); @@ -22782,18 +22819,6 @@ void BfModule::DoMethodDeclaration(BfMethodDeclaration* methodDeclaration, bool //BF_ASSERT(mCompiler->IsAutocomplete()); BfLogSysM("DoMethodDeclaration isTemporaryFunc bailout\n"); return; // Bail out early for autocomplete pass - } - - if ((methodInstance->GetImportCallKind() != BfImportCallKind_None) && (!mBfIRBuilder->mIgnoreWrites) && (!methodInstance->mIRFunction)) - { - BfLogSysM("DllImportGlobalVar DoMethodDeclaration processing %p\n", methodInstance); - // If this is in an extension then we did create the global variable already in the original obj - bool doDefine = mExtensionCount == 0; - BfIRValue dllImportGlobalVar = CreateDllImportGlobalVar(methodInstance, doDefine); - func = mBfIRBuilder->GetFakeVal(); - methodInstance->mIRFunction = func; - BF_ASSERT(dllImportGlobalVar); - mFuncReferences[mCurMethodInstance] = dllImportGlobalVar; } //TODO: We used to have this (this != mContext->mExternalFuncModule) check, but it caused us to keep around diff --git a/IDEHelper/Compiler/BfModule.h b/IDEHelper/Compiler/BfModule.h index 30a11776..efa5ae8f 100644 --- a/IDEHelper/Compiler/BfModule.h +++ b/IDEHelper/Compiler/BfModule.h @@ -1633,6 +1633,7 @@ public: bool IsTargetingBeefBackend(); bool WantsLifetimes(); bool HasCompiledOutput(); + bool HasExecutedOutput(); void SkipObjectAccessCheck(BfTypedValue typedVal); void EmitObjectAccessCheck(BfTypedValue typedVal); void EmitEnsureInstructionAt(); diff --git a/IDEHelper/Compiler/BfModuleTypeUtils.cpp b/IDEHelper/Compiler/BfModuleTypeUtils.cpp index 81db527c..70fa7133 100644 --- a/IDEHelper/Compiler/BfModuleTypeUtils.cpp +++ b/IDEHelper/Compiler/BfModuleTypeUtils.cpp @@ -2076,6 +2076,9 @@ void BfModule::FinishCEParseContext(BfAstNode* refNode, BfTypeInstance* typeInst void BfModule::UpdateCEEmit(CeEmitContext* ceEmitContext, BfTypeInstance* typeInstance, const StringImpl& ctxString, BfAstNode* refNode) { + for (int ifaceTypeId : ceEmitContext->mInterfaces) + typeInstance->mCeTypeInfo->mPendingInterfaces.Add(ifaceTypeId); + if (ceEmitContext->mEmitData.IsEmpty()) return; @@ -2198,7 +2201,7 @@ void BfModule::HandleCEAttributes(CeEmitContext* ceEmitContext, BfTypeInstance* } } } - else if (!ceEmitContext->mEmitData.IsEmpty()) + else if (ceEmitContext->HasEmissions()) { if (typeInstance->mCeTypeInfo == NULL) typeInstance->mCeTypeInfo = new BfCeTypeInfo(); @@ -2210,7 +2213,7 @@ void BfModule::HandleCEAttributes(CeEmitContext* ceEmitContext, BfTypeInstance* typeInstance->mCeTypeInfo->mNext->mTypeIFaceMap[typeId] = entry; } - if (!ceEmitContext->mEmitData.IsEmpty()) + if (ceEmitContext->HasEmissions()) { String ctxStr = "comptime ApplyToType of "; ctxStr += TypeToString(attrType); @@ -2747,7 +2750,7 @@ void BfModule::DoPopulateType(BfType* resolvedTypeRef, BfPopulateType populateTy resolvedTypeRef->mSize = typeInstance->mAlign = mSystem->mPtrSize; } - BF_ASSERT((typeInstance->mMethodInstanceGroups.size() == 0) || (typeInstance->mMethodInstanceGroups.size() == typeDef->mMethods.size()) || (typeInstance->mCeTypeInfo != NULL)); + BF_ASSERT((typeInstance->mMethodInstanceGroups.size() == 0) || (typeInstance->mMethodInstanceGroups.size() == typeDef->mMethods.size()) || (typeInstance->mCeTypeInfo != NULL) || (typeInstance->IsBoxed())); typeInstance->mMethodInstanceGroups.Resize(typeDef->mMethods.size()); for (int i = 0; i < (int)typeInstance->mMethodInstanceGroups.size(); i++) { @@ -2927,6 +2930,9 @@ void BfModule::DoPopulateType(BfType* resolvedTypeRef, BfPopulateType populateTy { bool hadType = false; + BfAstNode* deferredErrorNode = NULL; + char* deferredError = NULL; + for (auto baseTypeRef : typeDef->mBaseTypes) { SetAndRestoreValue prevTypeRef(mContext->mCurTypeState->mCurBaseTypeRef, baseTypeRef); @@ -2946,12 +2952,14 @@ void BfModule::DoPopulateType(BfType* resolvedTypeRef, BfPopulateType populateTy } else { - Fail("Underlying enum type already specified", baseTypeRef); + deferredError = "Underlying enum type already specified"; + deferredErrorNode = baseTypeRef; } } else { - Fail("Invalid underlying enum type", baseTypeRef); + deferredError = "Invalid underlying enum type"; + deferredErrorNode = baseTypeRef; } } else @@ -2961,6 +2969,9 @@ void BfModule::DoPopulateType(BfType* resolvedTypeRef, BfPopulateType populateTy } } + if (deferredError != NULL) + Fail(deferredError, deferredErrorNode, true); + if (underlyingType == NULL) { underlyingType = GetPrimitiveType(BfTypeCode_Int64); @@ -3305,6 +3316,27 @@ void BfModule::DoPopulateType(BfType* resolvedTypeRef, BfPopulateType populateTy wantPopulateInterfaces = true; } + if ((typeInstance->mCeTypeInfo != NULL) && (!typeInstance->mCeTypeInfo->mPendingInterfaces.IsEmpty())) + { + for (auto ifaceTypeId : typeInstance->mCeTypeInfo->mPendingInterfaces) + { + auto ifaceType = mContext->mTypes[ifaceTypeId]; + if ((ifaceType == NULL) || (!ifaceType->IsInterface())) + continue; + auto ifaceInst = ifaceType->ToTypeInstance(); + + if (ifaceSet.Add(ifaceInst)) + { + // Not base type + BfInterfaceDecl ifaceDecl; + ifaceDecl.mIFaceTypeInst = ifaceInst; + ifaceDecl.mTypeRef = NULL; + ifaceDecl.mDeclaringType = typeDef->GetDefinition(); + interfaces.Add(ifaceDecl); + } + } + } + if (_CheckTypeDone()) return; @@ -3694,6 +3726,25 @@ void BfModule::DoPopulateType(BfType* resolvedTypeRef, BfPopulateType populateTy if (innerType->IsIncomplete()) PopulateType(innerType, BfPopulateType_Data); + auto innerTypeInst = innerType->ToTypeInstance(); + if (innerTypeInst != NULL) + { + if (typeInstance->mTypeDef != innerTypeInst->mTypeDef) + { + // Rebuild with proper typedef (generally from inner type comptime emission) + typeInstance->mTypeDef = innerTypeInst->mTypeDef; + DoPopulateType(resolvedTypeRef, populateType); + return; + } + + while (typeInstance->mInterfaces.mSize < innerTypeInst->mInterfaces.mSize) + { + auto ifaceEntry = innerTypeInst->mInterfaces[typeInstance->mInterfaces.mSize]; + typeInstance->mInterfaces.Add(ifaceEntry); + AddDependency(ifaceEntry.mInterfaceType, typeInstance, BfDependencyMap::DependencyFlag_ImplementsInterface); + } + } + auto baseType = typeInstance->mBaseType; dataPos = baseType->mInstSize; int alignSize = BF_MAX(innerType->mAlign, baseType->mInstAlign); @@ -3997,9 +4048,12 @@ void BfModule::DoPopulateType(BfType* resolvedTypeRef, BfPopulateType populateTy } if ((typeInstance->mDefineState < BfTypeDefineState_CEPostTypeInit) && (tryCE)) - { + { BF_ASSERT(!typeInstance->mTypeDef->IsEmitted()); + if (typeInstance->mCeTypeInfo != NULL) + typeInstance->mCeTypeInfo->mPendingInterfaces.Clear(); + typeInstance->mDefineState = BfTypeDefineState_CETypeInit; bool hadNewMembers = false; DoCEEmit(typeInstance, hadNewMembers); @@ -4054,6 +4108,9 @@ void BfModule::DoPopulateType(BfType* resolvedTypeRef, BfPopulateType populateTy } } + if ((typeInstance->mCeTypeInfo != NULL) && (!typeInstance->mCeTypeInfo->mPendingInterfaces.IsEmpty())) + hadNewMembers = true; + if ((typeInstance->mTypeDef->IsEmitted()) && (typeInstance->mCeTypeInfo == NULL)) { BF_ASSERT(mCompiler->mCanceling); @@ -5658,16 +5715,7 @@ void BfModule::DoTypeInstanceMethodProcessing(BfTypeInstance* typeInstance) } else { - auto matchedMethodDef = matchedMethod->mMethodDef; - if (matchedMethodDef->mDeclaringType->IsEmitted()) - { - Fail("Boxed interface binding error to emitted method", mCurTypeInstance->mTypeDef->GetRefNode()); - continue; - } - - if (underlyingTypeInstance->mTypeDef->IsEmitted()) - matchedMethodDef = underlyingTypeInstance->mTypeDef->mEmitParent->mMethods[matchedMethodDef->mIdx]; - + auto matchedMethodDef = matchedMethod->mMethodDef; if (!matchedMethod->mIsForeignMethodDef) { BfMethodInstanceGroup* boxedMethodInstanceGroup = &typeInstance->mMethodInstanceGroups[matchedMethod->mMethodDef->mIdx]; @@ -5676,7 +5724,7 @@ void BfModule::DoTypeInstanceMethodProcessing(BfTypeInstance* typeInstance) boxedMethodInstanceGroup->mOnDemandKind = BfMethodOnDemandKind_Decl_AwaitingDecl; VerifyOnDemandMethods(); } - } + } auto methodFlags = matchedMethod->mIsForeignMethodDef ? BfGetMethodInstanceFlag_ForeignMethodDef : BfGetMethodInstanceFlag_None; methodFlags = (BfGetMethodInstanceFlags)(methodFlags | BfGetMethodInstanceFlag_MethodInstanceOnly); @@ -11505,29 +11553,37 @@ BfIRValue BfModule::CastToValue(BfAstNode* srcNode, BfTypedValue typedVal, BfTyp { SetAndRestoreValue prevIgnoreWrites(mBfIRBuilder->mIgnoreWrites, true); auto constraintTypeInst = genericParamInst->mTypeConstraint->ToTypeInstance(); - if ((constraintTypeInst != NULL) && (constraintTypeInst->IsInstanceOf(mCompiler->mEnumTypeDef)) && (explicitCast)) + + if ((constraintTypeInst != NULL) && (constraintTypeInst->IsDelegateOrFunction())) { - // Enum->int - if ((explicitCast) && (toType->IsInteger())) - return typedVal.mValue; + // Could be a methodref - can't cast to anything else } - - BfTypedValue fromTypedValue; - if (typedVal.mKind == BfTypedValueKind_GenericConstValue) - fromTypedValue = GetDefaultTypedValue(genericParamInst->mTypeConstraint, false, BfDefaultValueKind_Undef); else - fromTypedValue = BfTypedValue(mBfIRBuilder->GetFakeVal(), genericParamInst->mTypeConstraint, genericParamInst->mTypeConstraint->IsValueType()); - - auto result = CastToValue(srcNode, fromTypedValue, toType, (BfCastFlags)(castFlags | BfCastFlags_SilentFail)); - if (result) { - if ((genericParamInst->mTypeConstraint->IsDelegate()) && (toType->IsDelegate())) + if ((constraintTypeInst != NULL) && (constraintTypeInst->IsInstanceOf(mCompiler->mEnumTypeDef)) && (explicitCast)) { - // Don't allow cast when we are constrained by a delegate type, because BfMethodRefs can match and we require an actual alloc - Fail(StrFormat("Unable to cast '%s' to '%s' because delegate constraints allow valueless direct method references", TypeToString(typedVal.mType).c_str(), TypeToString(toType).c_str()), srcNode); - return BfIRValue(); + // Enum->int + if ((explicitCast) && (toType->IsInteger())) + return typedVal.mValue; + } + + BfTypedValue fromTypedValue; + if (typedVal.mKind == BfTypedValueKind_GenericConstValue) + fromTypedValue = GetDefaultTypedValue(genericParamInst->mTypeConstraint, false, BfDefaultValueKind_Undef); + else + fromTypedValue = BfTypedValue(mBfIRBuilder->GetFakeVal(), genericParamInst->mTypeConstraint, genericParamInst->mTypeConstraint->IsValueType()); + + auto result = CastToValue(srcNode, fromTypedValue, toType, (BfCastFlags)(castFlags | BfCastFlags_SilentFail)); + if (result) + { + if ((genericParamInst->mTypeConstraint->IsDelegate()) && (toType->IsDelegate())) + { + // Don't allow cast when we are constrained by a delegate type, because BfMethodRefs can match and we require an actual alloc + Fail(StrFormat("Unable to cast '%s' to '%s' because delegate constraints allow valueless direct method references", TypeToString(typedVal.mType).c_str(), TypeToString(toType).c_str()), srcNode); + return BfIRValue(); + } + return result; } - return result; } } @@ -11755,7 +11811,7 @@ BfIRValue BfModule::CastToValue(BfAstNode* srcNode, BfTypedValue typedVal, BfTyp if (allowCast) { - if (ignoreWrites) + if ((ignoreWrites) && (!typedVal.mValue.IsConst())) return mBfIRBuilder->GetFakeVal(); return mBfIRBuilder->CreateBitCast(typedVal.mValue, mBfIRBuilder->MapType(toType)); } diff --git a/IDEHelper/Compiler/BfParser.cpp b/IDEHelper/Compiler/BfParser.cpp index dce0e396..e96ba38f 100644 --- a/IDEHelper/Compiler/BfParser.cpp +++ b/IDEHelper/Compiler/BfParser.cpp @@ -2447,10 +2447,9 @@ void BfParser::NextToken(int endIdx, bool outerIsInterpolate) mTokenEnd = mSrcIdx; return; } - - bool wasNeg = false; + bool hadOverflow = false; - int64 val = 0; + uint64 val = 0; int numberBase = 10; int expVal = 0; int expSign = 0; @@ -2460,8 +2459,7 @@ void BfParser::NextToken(int endIdx, bool outerIsInterpolate) int hexDigits = 0; if (c == '-') { - wasNeg = true; //TODO: This never actually gets set any more (eaten as BfToken_Minus above). Move checks that use this to later in pipeline, then remove this - c = mSrc[mSrcIdx++]; + BF_FATAL("Parsing error"); } val = c - '0'; @@ -2641,7 +2639,7 @@ void BfParser::NextToken(int endIdx, bool outerIsInterpolate) // This is actually a integer followed by an Int32 call (like 123.ToString) mSrcIdx -= 2; mTokenEnd = mSrcIdx; - mLiteral.mInt64 = val; + mLiteral.mUInt64 = val; mLiteral.mTypeCode = BfTypeCode_IntUnknown; mSyntaxToken = BfSyntaxToken_Literal; return; @@ -2668,27 +2666,24 @@ void BfParser::NextToken(int endIdx, bool outerIsInterpolate) if (endNumber) { mTokenEnd = mSrcIdx - 1; - mSrcIdx--; - if (wasNeg) - val = -val; + mSrcIdx--; if ((numberBase == 0x10) && ((hexDigits >= 16) || ((hadSeps) && (hexDigits > 8)) || ((hadLeadingHexSep) && (hexDigits == 8)))) { if (hexDigits > 16) mPassInstance->FailAt("Too many hex digits for int64", mSourceData, mTokenStart, mSrcIdx - mTokenStart); - mLiteral.mInt64 = val; - if ((val < 0) && (!wasNeg)) + mLiteral.mUInt64 = val; + if (val >= 0x8000000000000000) mLiteral.mTypeCode = BfTypeCode_UInt64; else mLiteral.mTypeCode = BfTypeCode_Int64; } else { - mLiteral.mInt64 = val; + mLiteral.mUInt64 = val; mLiteral.mTypeCode = BfTypeCode_IntUnknown; - if ((numberBase == 0x10) && (hexDigits == 7)) mLiteral.mWarnType = BfWarning_BF4201_Only7Hex; if ((numberBase == 0x10) && (hexDigits == 9)) @@ -2699,7 +2694,12 @@ void BfParser::NextToken(int endIdx, bool outerIsInterpolate) mPassInstance->FailAt("Value doesn't fit into int64", mSourceData, mTokenStart, mSrcIdx - mTokenStart); mLiteral.mTypeCode = BfTypeCode_Int64; } - else if ((val < -0x80000000LL) || (val > 0xFFFFFFFFLL)) + //else if ((val < -0x80000000LL) || (val > 0xFFFFFFFFLL)) + else if (val >= 0x8000000000000000) + { + mLiteral.mTypeCode = BfTypeCode_UInt64; + } + else if (val > 0xFFFFFFFFLL) { mLiteral.mTypeCode = BfTypeCode_Int64; } @@ -2709,7 +2709,7 @@ void BfParser::NextToken(int endIdx, bool outerIsInterpolate) return; } - int64 prevVal = val; + uint64 prevVal = val; if ((c >= '0') && (c <= '9') && (c < '0' + numberBase)) { if (numberBase == 0x10) @@ -2731,9 +2731,7 @@ void BfParser::NextToken(int endIdx, bool outerIsInterpolate) } else if ((c == 'u') || (c == 'U')) - { - if (wasNeg) - val = -val; + { if ((mSrc[mSrcIdx] == 'l') || (mSrc[mSrcIdx] == 'L')) { if (mSrc[mSrcIdx] == 'l') @@ -2744,7 +2742,7 @@ void BfParser::NextToken(int endIdx, bool outerIsInterpolate) mLiteral.mUInt64 = (uint64)val; if (hexDigits > 16) mPassInstance->FailAt("Too many hex digits for int64", mSourceData, mTokenStart, mSrcIdx - mTokenStart); - else if ((hadOverflow) || (wasNeg)) + else if (hadOverflow) mPassInstance->FailAt("Value doesn't fit into uint64", mSourceData, mTokenStart, mSrcIdx - mTokenStart); mSyntaxToken = BfSyntaxToken_Literal; return; @@ -2752,7 +2750,7 @@ void BfParser::NextToken(int endIdx, bool outerIsInterpolate) mTokenEnd = mSrcIdx; mLiteral.mTypeCode = BfTypeCode_UIntPtr; mLiteral.mUInt32 = (uint32)val; - if ((hadOverflow) || (wasNeg) || ((uint64)val != (uint64)mLiteral.mUInt32)) + if ((hadOverflow) || ((uint64)val != (uint64)mLiteral.mUInt32)) mPassInstance->FailAt("Value doesn't fit into uint32", mSourceData, mTokenStart, mSrcIdx - mTokenStart); mSyntaxToken = BfSyntaxToken_Literal; return; @@ -2760,9 +2758,7 @@ void BfParser::NextToken(int endIdx, bool outerIsInterpolate) else if ((c == 'l') || (c == 'L')) { if (c == 'l') - TokenFail("Uppercase 'L' required for int64"); - if (wasNeg) - val = -val; + TokenFail("Uppercase 'L' required for int64"); if ((mSrc[mSrcIdx] == 'u') || (mSrc[mSrcIdx] == 'U')) { mSrcIdx++; @@ -2771,25 +2767,24 @@ void BfParser::NextToken(int endIdx, bool outerIsInterpolate) mLiteral.mUInt64 = (uint64)val; if (hexDigits > 16) mPassInstance->FailAt("Too many hex digits for int64", mSourceData, mTokenStart, mSrcIdx - mTokenStart); - else if ((hadOverflow) || (wasNeg)) + else if (hadOverflow) mPassInstance->FailAt("Value doesn't fit into uint64", mSourceData, mTokenStart, mSrcIdx - mTokenStart); mSyntaxToken = BfSyntaxToken_Literal; return; - } + } mTokenEnd = mSrcIdx; mLiteral.mTypeCode = BfTypeCode_Int64; mLiteral.mInt64 = (int64)val; - - bool signMatched = true; - if (val != 0) - signMatched = (val < 0) == wasNeg; - + if (val == 0x8000000000000000) + mLiteral.mTypeCode = BfTypeCode_UInt64; + else if (val >= 0x8000000000000000) + hadOverflow = true; if (numberBase == 0x10) { if (hexDigits > 16) mPassInstance->FailAt("Too many hex digits for int64", mSourceData, mTokenStart, mSrcIdx - mTokenStart); } - else if ((hadOverflow) || (!signMatched)) + else if (hadOverflow) mPassInstance->FailAt("Value doesn't fit into int64", mSourceData, mTokenStart, mSrcIdx - mTokenStart); mSyntaxToken = BfSyntaxToken_Literal; return; @@ -2813,17 +2808,14 @@ void BfParser::NextToken(int endIdx, bool outerIsInterpolate) else { mTokenEnd = mSrcIdx - 1; - mSrcIdx--; - if (wasNeg) - val = -val; - mLiteral.mInt64 = val; + mSrcIdx--; + mLiteral.mUInt64 = val; mLiteral.mTypeCode = BfTypeCode_IntUnknown; mSyntaxToken = BfSyntaxToken_Literal; TokenFail("Unexpected character while parsing number", 0); return; } - - //if ((val < 0) && (val != -0x8000000000000000)) + if ((uint64)prevVal > (uint64)val) hadOverflow = true; } diff --git a/IDEHelper/Compiler/BfParser.h b/IDEHelper/Compiler/BfParser.h index cc9a27ed..3c95ed8d 100644 --- a/IDEHelper/Compiler/BfParser.h +++ b/IDEHelper/Compiler/BfParser.h @@ -226,6 +226,7 @@ public: void SetSource(const char* data, int length); void MoveSource(const char* data, int length); // Takes ownership of data ptr void RefSource(const char* data, int length); + void MakeNegative(uint64& val, bool& hadOverflow); void NextToken(int endIdx = -1, bool outerIsInterpolate = false); BfAstNode* CreateNode(); diff --git a/IDEHelper/Compiler/BfPrinter.cpp b/IDEHelper/Compiler/BfPrinter.cpp index ccc2d82e..f30db3a9 100644 --- a/IDEHelper/Compiler/BfPrinter.cpp +++ b/IDEHelper/Compiler/BfPrinter.cpp @@ -300,6 +300,11 @@ int BfPrinter::CalcOrigLineSpacing(BfAstNode* bfAstNode, int* lineStartIdx) void BfPrinter::WriteIgnoredNode(BfAstNode* node) { + if ((!mOutString.IsEmpty()) && (!isspace((uint8)mOutString[mOutString.mLength - 1]))) + { + Write(" "); + } + bool wasExpectingNewLine = mExpectingNewLine; mTriviaIdx = std::max(mTriviaIdx, node->GetTriviaStart()); @@ -1759,9 +1764,14 @@ void BfPrinter::Visit(BfLambdaBindExpression* lambdaBindExpr) else { ExpectSpace(); - VisitChild(lambdaBindExpr->mBody); + VisitChild(lambdaBindExpr->mBody); } - } + } + VisitChild(lambdaBindExpr->mDtor); + mNextStateModify.mExpectingSpace = false; + mVirtualNewLineIdx = mNextStateModify.mWantNewLineIdx; + mCurIndentLevel = mNextStateModify.mWantVirtualIndent; + mVirtualIndentLevel = mNextStateModify.mWantVirtualIndent; } void BfPrinter::Visit(BfObjectCreateExpression* newExpr) @@ -2643,7 +2653,7 @@ void BfPrinter::Visit(BfIndexerDeclaration* indexerDeclaration) } void BfPrinter::Visit(BfFieldDeclaration* fieldDeclaration) -{ +{ bool isEnumDecl = false; if (auto enumEntry = BfNodeDynCast(fieldDeclaration)) @@ -2703,18 +2713,10 @@ void BfPrinter::Visit(BfFieldDeclaration* fieldDeclaration) QueueVisitChild(fieldDeclaration->mInitializer); } - auto fieldDtor = fieldDeclaration->mFieldDtor; - while (fieldDtor != NULL) - { - ExpectSpace(); - QueueVisitChild(fieldDtor->mTildeToken); - ExpectSpace(); - QueueVisitChild(fieldDtor->mBody); - fieldDtor = fieldDtor->mNextFieldDtor; - } - mNextStateModify.mExpectingSpace = false; FlushVisitChild(); + VisitChild(fieldDeclaration->mFieldDtor); + mNextStateModify.mExpectingSpace = false; } void BfPrinter::Visit(BfEnumCaseDeclaration* enumCaseDeclaration) @@ -2763,6 +2765,32 @@ void BfPrinter::Visit(BfTypeAliasDeclaration* typeDeclaration) VisitChild(typeDeclaration->mEndSemicolon); } +void BfPrinter::Visit(BfFieldDtorDeclaration* fieldDtorDeclaration) +{ + ExpectSpace(); + if (fieldDtorDeclaration->mBody != NULL) + { + if (fieldDtorDeclaration->mBody->IsA()) + { + ExpectNewLine(); + ExpectIndent(); + VisitChild(fieldDtorDeclaration->mTildeToken); + VisitChild(fieldDtorDeclaration->mBody); + ExpectUnindent(); + } + else + { + VisitChild(fieldDtorDeclaration->mTildeToken); + ExpectSpace(); + VisitChild(fieldDtorDeclaration->mBody); + } + } + else + VisitChild(fieldDtorDeclaration->mTildeToken); + + VisitChild(fieldDtorDeclaration->mNextFieldDtor); +} + void BfPrinter::Visit(BfTypeDeclaration* typeDeclaration) { SetAndRestoreValue prevTypeDecl(mCurTypeDecl, typeDeclaration); diff --git a/IDEHelper/Compiler/BfPrinter.h b/IDEHelper/Compiler/BfPrinter.h index 815222c5..cf4dc2b6 100644 --- a/IDEHelper/Compiler/BfPrinter.h +++ b/IDEHelper/Compiler/BfPrinter.h @@ -220,6 +220,7 @@ public: virtual void Visit(BfFieldDeclaration* fieldDeclaration) override; virtual void Visit(BfEnumCaseDeclaration* enumCaseDeclaration) override; virtual void Visit(BfTypeAliasDeclaration* typeDeclaration) override; + virtual void Visit(BfFieldDtorDeclaration* fieldDtorDeclaration) override; virtual void Visit(BfTypeDeclaration* typeDeclaration) override; virtual void Visit(BfUsingDirective* usingDirective) override; virtual void Visit(BfUsingModDirective* usingDirective) override; diff --git a/IDEHelper/Compiler/BfReducer.cpp b/IDEHelper/Compiler/BfReducer.cpp index efc75e6d..baa65d07 100644 --- a/IDEHelper/Compiler/BfReducer.cpp +++ b/IDEHelper/Compiler/BfReducer.cpp @@ -5912,12 +5912,9 @@ BfAstNode* BfReducer::ReadTypeMember(BfTokenNode* tokenNode, bool declStarted, i ReplaceNode(tokenNode, operatorDecl); operatorDecl->mOperatorToken = tokenNode; - auto nextIdentifier = ExpectIdentifierAfter(operatorDecl, "type"); - if (nextIdentifier == NULL) - return operatorDecl; - mVisitorPos.mReadPos--; // Backtrack, that's part of our type - auto typeRef = CreateTypeRefAfter(operatorDecl); + if (typeRef == NULL) + return operatorDecl; MEMBER_SET_CHECKED(operatorDecl, mReturnType, typeRef); operatorDecl->mIsConvOperator = true; diff --git a/IDEHelper/Compiler/BfResolvedTypeUtils.cpp b/IDEHelper/Compiler/BfResolvedTypeUtils.cpp index 7b85a000..26ba94bc 100644 --- a/IDEHelper/Compiler/BfResolvedTypeUtils.cpp +++ b/IDEHelper/Compiler/BfResolvedTypeUtils.cpp @@ -1298,15 +1298,6 @@ void BfMethodInstance::GetIRFunctionInfo(BfModule* module, BfIRType& returnType, } } -// if ((paramIdx == 0) && (GetParamName(0) == "this") && (checkType->IsPointer())) -// { -// // We don't actually pass a this pointer for mut methods in valueless structs -// auto underlyingType = checkType->GetUnderlyingType(); -// module->PopulateType(underlyingType, BfPopulateType_Data); -// if (underlyingType->IsValuelessType()) -// continue; -// } - if (checkType->CanBeValuelessType()) module->PopulateType(checkType, BfPopulateType_Data); if ((checkType->IsValuelessType()) && (!checkType->IsMethodRef())) @@ -1563,6 +1554,7 @@ BfTypeInstance::~BfTypeInstance() if ((mTypeDef != NULL) && (mTypeDef->mEmitParent != NULL)) { mMethodInstanceGroups.Clear(); + BfLogSys(mModule->mSystem, "Type %p dtor deleting typeDef %p\n", this, mTypeDef); delete mTypeDef; } } @@ -2636,6 +2628,12 @@ void BfTupleType::Finish() ////////////////////////////////////////////////////////////////////////// +BfBoxedType::~BfBoxedType() +{ + if ((mTypeDef != NULL) && (mTypeDef->mEmitParent != NULL)) + mTypeDef = NULL; +} + BfType* BfBoxedType::GetModifiedElementType() { if ((mBoxedFlags & BoxedFlags_StructPtr) != 0) diff --git a/IDEHelper/Compiler/BfResolvedTypeUtils.h b/IDEHelper/Compiler/BfResolvedTypeUtils.h index 0f7a6fa9..9b9a1321 100644 --- a/IDEHelper/Compiler/BfResolvedTypeUtils.h +++ b/IDEHelper/Compiler/BfResolvedTypeUtils.h @@ -61,7 +61,8 @@ enum BfMethodNameFlags : uint8 BfMethodNameFlag_ResolveGenericParamNames = 1, BfMethodNameFlag_OmitTypeName = 2, BfMethodNameFlag_IncludeReturnType = 4, - BfMethodNameFlag_OmitParams = 8 + BfMethodNameFlag_OmitParams = 8, + BfMethodNameFlag_IncludeMut = 0x10 }; enum BfGetMethodInstanceFlags : uint16 @@ -1827,6 +1828,7 @@ class BfCeTypeInfo public: Dictionary mOnCompileMap; Dictionary mTypeIFaceMap; + Array mPendingInterfaces; Val128 mHash; bool mFailed; BfCeTypeInfo* mNext; @@ -2109,6 +2111,7 @@ public: mBoxedBaseType = NULL; mBoxedFlags = BoxedFlags_None; } + ~BfBoxedType(); virtual bool IsBoxed() override { return true; } diff --git a/IDEHelper/Compiler/BfSystem.cpp b/IDEHelper/Compiler/BfSystem.cpp index 4ff7e3d1..de407542 100644 --- a/IDEHelper/Compiler/BfSystem.cpp +++ b/IDEHelper/Compiler/BfSystem.cpp @@ -793,11 +793,6 @@ BfTypeDef::~BfTypeDef() { BfLogSysM("BfTypeDef::~BfTypeDef %p\n", this); - if ((mHash == -1330357811) && (IsEmitted())) - { - NOP; - } - delete mNextRevision; FreeMembers(); @@ -2282,6 +2277,41 @@ bool BfSystem::DoesLiteralFit(BfTypeCode typeCode, int64 value) return false; } +bool BfSystem::DoesLiteralFit(BfTypeCode typeCode, uint64 value) +{ + if (typeCode == BfTypeCode_IntPtr) + typeCode = (mPtrSize == 4) ? BfTypeCode_Int32 : BfTypeCode_Int64; + if (typeCode == BfTypeCode_UIntPtr) + typeCode = (mPtrSize == 4) ? BfTypeCode_UInt32 : BfTypeCode_UInt64; + + if (value >= 0x8000000000000000) + return typeCode == BfTypeCode_UInt64; + + switch (typeCode) + { + case BfTypeCode_Int8: + return (value < 0x80); + case BfTypeCode_Int16: + return (value < 0x8000); + case BfTypeCode_Int32: + return (value < 0x80000000LL); + case BfTypeCode_Int64: + return true; + + case BfTypeCode_UInt8: + return (value < 0x100); + case BfTypeCode_UInt16: + return (value < 0x10000); + case BfTypeCode_UInt32: + return (value < 0x100000000LL); + case BfTypeCode_UInt64: + return true; + default: break; + } + + return false; +} + BfParser* BfSystem::CreateParser(BfProject* bfProject) { AutoCrit crit(mDataLock); @@ -3693,10 +3723,14 @@ void BfSystem::RemoveOldData() { AutoCrit autoCrit(mDataLock); - for (auto typeDef : mTypeDefDeleteQueue) + for (int i = 0; i < (int)mTypeDefDeleteQueue.size(); i++) + { + auto typeDef = mTypeDefDeleteQueue[i]; + mTypeDefDeleteQueue[i] = NULL; + BfLogSys(this, "RemoveOldData deleting from mTypeDefDeleteQueue %p\n", typeDef); delete typeDef; + } mTypeDefDeleteQueue.Clear(); - if (!mProjectDeleteQueue.IsEmpty()) { @@ -3729,6 +3763,7 @@ void BfSystem::RemoveOldData() void BfSystem::VerifyTypeDef(BfTypeDef* typeDef) { +#if defined _DEBUG && false auto _FindTypeDef = [&](BfTypeReference* typeRef) { if (auto directStrTypeRef = BfNodeDynCast(typeRef)) @@ -3762,6 +3797,7 @@ void BfSystem::VerifyTypeDef(BfTypeDef* typeDef) _FindTypeDef(paramDef->mTypeRef); } } +#endif } BfTypeOptions* BfSystem::GetTypeOptions(int optionsIdx) diff --git a/IDEHelper/Compiler/BfSystem.h b/IDEHelper/Compiler/BfSystem.h index 42a35340..1a4fbd93 100644 --- a/IDEHelper/Compiler/BfSystem.h +++ b/IDEHelper/Compiler/BfSystem.h @@ -1610,6 +1610,7 @@ public: void CreateBasicTypes(); bool DoesLiteralFit(BfTypeCode typeCode, int64 value); + bool DoesLiteralFit(BfTypeCode typeCode, uint64 value); BfParser* CreateParser(BfProject* bfProject); BfCompiler* CreateCompiler(bool isResolveOnly); BfProject* GetProject(const StringImpl& projName); diff --git a/IDEHelper/Compiler/CeMachine.cpp b/IDEHelper/Compiler/CeMachine.cpp index 0b9384c3..b8054687 100644 --- a/IDEHelper/Compiler/CeMachine.cpp +++ b/IDEHelper/Compiler/CeMachine.cpp @@ -1288,7 +1288,7 @@ void CeBuilder::Build() auto methodInstance = mCeFunction->mMethodInstance; if (methodInstance != NULL) - { + { BfMethodInstance dupMethodInstance; dupMethodInstance.CopyFrom(methodInstance); auto methodDef = methodInstance->mMethodDef; @@ -1638,10 +1638,10 @@ void CeBuilder::Build() EmitBinaryOp(CeOp_Shl_I8, CeOp_InvalidOp, ceLHS, ceRHS, result); break; case BeBinaryOpKind_RightShift: - EmitBinaryOp(CeOp_Shr_I8, CeOp_InvalidOp, ceLHS, ceRHS, result); + EmitBinaryOp(CeOp_Shr_U8, CeOp_InvalidOp, ceLHS, ceRHS, result); break; case BeBinaryOpKind_ARightShift: - EmitBinaryOp(CeOp_Shr_U8, CeOp_InvalidOp, ceLHS, ceRHS, result); + EmitBinaryOp(CeOp_Shr_I8, CeOp_InvalidOp, ceLHS, ceRHS, result); break; default: Fail("Invalid binary op"); @@ -2476,7 +2476,18 @@ void CeBuilder::Build() EmitFrameOffset(ceSize); } break; + case BfIRIntrinsic_MemSet: + { + CeOperand ceDestPtr = GetOperand(castedInst->mArgs[0].mValue); + CeOperand ceValue = GetOperand(castedInst->mArgs[1].mValue); + CeOperand ceSize = GetOperand(castedInst->mArgs[2].mValue); + Emit(CeOp_MemSet); + EmitFrameOffset(ceDestPtr); + EmitFrameOffset(ceValue); + EmitFrameOffset(ceSize); + } + break; case BfIRIntrinsic_AtomicFence: // Nothing to do @@ -4729,6 +4740,17 @@ bool CeContext::Execute(CeFunction* startFunction, uint8* startStackPtr, uint8* return false; } } + else if (checkFunction->mFunctionKind == CeFunctionKind_EmitAddInterface) + { + int32 typeId = *(int32*)((uint8*)stackPtr); + int32 ifaceTypeId = *(int32*)((uint8*)stackPtr + sizeof(int32)); + if ((mCurEmitContext == NULL) || (mCurEmitContext->mType->mTypeId != typeId)) + { + _Fail("Code cannot be emitted for this type in this context"); + return false; + } + mCurEmitContext->mInterfaces.Add(ifaceTypeId); + } else if (checkFunction->mFunctionKind == CeFunctionKind_EmitMethodEntry) { int64 methodHandle = *(int64*)((uint8*)stackPtr); @@ -4972,7 +4994,12 @@ bool CeContext::Execute(CeFunction* startFunction, uint8* startStackPtr, uint8* if (*fastFinishPtr) { if (*cancelingPtr) - _Fail("Comptime evaluation canceled"); + { + if ((mCurModule != NULL) && (mCurModule->mCurTypeInstance != NULL)) + mCurModule->DeferRebuildType(mCurModule->mCurTypeInstance); + else + _Fail("Comptime evaluation canceled"); + } return false; } @@ -6817,6 +6844,10 @@ void CeMachine::CheckFunctionKind(CeFunction* ceFunction) { ceFunction->mFunctionKind = CeFunctionKind_EmitTypeBody; } + if (methodDef->mName == "Comptime_EmitAddInterface") + { + ceFunction->mFunctionKind = CeFunctionKind_EmitAddInterface; + } else if (methodDef->mName == "Comptime_EmitMethodEntry") { ceFunction->mFunctionKind = CeFunctionKind_EmitMethodEntry; diff --git a/IDEHelper/Compiler/CeMachine.h b/IDEHelper/Compiler/CeMachine.h index 071b1e4a..ca36f4b0 100644 --- a/IDEHelper/Compiler/CeMachine.h +++ b/IDEHelper/Compiler/CeMachine.h @@ -289,6 +289,7 @@ enum CeFunctionKind CeFunctionKind_Method_GetParamInfo, CeFunctionKind_EmitTypeBody, + CeFunctionKind_EmitAddInterface, CeFunctionKind_EmitMethodEntry, CeFunctionKind_EmitMethodExit, CeFunctionKind_EmitMixin, @@ -687,6 +688,7 @@ class CeEmitContext public: BfType* mType; BfMethodInstance* mMethodInstance; + Array mInterfaces; String mEmitData; String mExitEmitData; bool mFailed; @@ -697,6 +699,11 @@ public: mMethodInstance = NULL; mFailed = false; } + + bool HasEmissions() + { + return !mEmitData.IsEmpty() || !mInterfaces.IsEmpty(); + } }; class CeContext diff --git a/IDEHelper/Tests/src/Comptime.bf b/IDEHelper/Tests/src/Comptime.bf index 385b6fa7..453e39c7 100644 --- a/IDEHelper/Tests/src/Comptime.bf +++ b/IDEHelper/Tests/src/Comptime.bf @@ -174,7 +174,61 @@ namespace Tests public float mY; public float mZ; } + + class SerializationContext + { + public String mStr = new String() ~ delete _; + public void Serialize(String name, T val) where T : struct + { + mStr.AppendF($"{name} {val}\n"); + } + } + interface ISerializable + { + void Serialize(SerializationContext ctx); + } + + [AttributeUsage(.Enum | .Struct | .Class, .NotInherited | .ReflectAttribute | .DisallowAllowMultiple)] + struct SerializableAttribute : Attribute, IComptimeTypeApply + { + [Comptime] + public void ApplyToType(Type type) + { + const String SERIALIZE_NAME = "void ISerializable.Serialize(SerializationContext ctx)\n"; + + String serializeBuffer = new .(); + + Compiler.Assert(!type.IsUnion); + + for (let field in type.GetFields()) + { + if (!field.IsInstanceField || field.DeclaringType != type) + continue; + + serializeBuffer.AppendF($"\n\tctx.Serialize(\"{field.Name}\", {field.Name});"); + } + + Compiler.EmitTypeBody(type, scope $"{SERIALIZE_NAME}{{{serializeBuffer}\n}}\n"); + Compiler.EmitAddInterface(type, typeof(ISerializable)); + } + } + + [Serializable] + struct Foo : this(float x, float y) + { + } + + public class ComponentHandler + where T : struct + { + uint8* data; + protected override void GCMarkMembers() + { + T* ptr = (T*)data; + GC.Mark!((*ptr)); + } + } [Test] public static void TestBasics() { @@ -209,6 +263,12 @@ namespace Tests 4 mY 8 mZ """); + + Foo bar = .(10, 2); + ISerializable iSer = bar; + SerializationContext serCtx = scope .(); + iSer.Serialize(serCtx); + Test.Assert(serCtx.mStr == "x 10\ny 2\n"); } } } diff --git a/IDEHelper/WinDebugger.cpp b/IDEHelper/WinDebugger.cpp index c2d98fa6..247f57e9 100644 --- a/IDEHelper/WinDebugger.cpp +++ b/IDEHelper/WinDebugger.cpp @@ -8415,7 +8415,7 @@ void WinDebugger::HandleCustomExpandedItems(String& retVal, DbgCompileUnit* dbgC evalStr += ", refid=\"" + referenceId + ".[]\""; if (isReadOnly) evalStr += ", ne"; - retVal += "\n:repeat" + StrFormat("\t%d\t%d\t%d", 0, (int)sizeValue.GetInt64() / dimSize1, 50000) + + retVal += "\n:repeat" + StrFormat("\t%d\t%lld\t%d", 0, sizeValue.GetInt64() / dimSize1, 50000) + "\t[{0}]\t" + evalStr; } else if (lowerDimSizes.size() == 2) @@ -8431,7 +8431,7 @@ void WinDebugger::HandleCustomExpandedItems(String& retVal, DbgCompileUnit* dbgC evalStr += ", refid=\"" + referenceId + ".[]\""; if (isReadOnly) evalStr += ", ne"; - retVal += "\n:repeat" + StrFormat("\t%d\t%d\t%d", 0, (int)sizeValue.GetInt64() / dimSize1 / dimSize2, 50000) + + retVal += "\n:repeat" + StrFormat("\t%d\t%lld\t%d", 0, sizeValue.GetInt64() / dimSize1 / dimSize2, 50000) + "\t[{0}]\t" + evalStr; } } @@ -8449,7 +8449,7 @@ void WinDebugger::HandleCustomExpandedItems(String& retVal, DbgCompileUnit* dbgC evalStr += ", refid=\"" + referenceId + ".[]\""; if (isReadOnly) evalStr += ", ne"; - retVal += "\n:repeat" + StrFormat("\t%d\t%d\t%d", 0, (int)sizeValue.GetInt64() / dimSize1 / dimSize2 / dimSize3, 50000) + + retVal += "\n:repeat" + StrFormat("\t%d\t%lld\t%d", 0, sizeValue.GetInt64() / dimSize1 / dimSize2 / dimSize3, 50000) + "\t[{0}]\t" + evalStr; } } @@ -8459,7 +8459,7 @@ void WinDebugger::HandleCustomExpandedItems(String& retVal, DbgCompileUnit* dbgC evalStr += ", refid=\"" + referenceId + ".[]\""; if (isReadOnly) evalStr += ", ne"; - retVal += "\n:repeat" + StrFormat("\t%d\t%d\t%d", 0, (int)sizeValue.GetInt64(), 50000) + + retVal += "\n:repeat" + StrFormat("\t%d\t%lld\t%d", 0, sizeValue.GetInt64(), 50000) + "\t[{0}]\t" + evalStr; } } @@ -8476,7 +8476,7 @@ void WinDebugger::HandleCustomExpandedItems(String& retVal, DbgCompileUnit* dbgC evalStr += ", refid=\"" + referenceId + ".[]\""; if (isReadOnly) evalStr += ", ne"; - retVal += "\n:repeat" + StrFormat("\t%d\t%d\t%d", 0, (int)sizeValue.GetInt64(), 50000) + + retVal += "\n:repeat" + StrFormat("\t%d\t%lld\t%d", 0, sizeValue.GetInt64(), 50000) + "\t[{0}]\t" + evalStr; } } From 04888b8f1012aa01d00cf1bc0072cf0dc71d7243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Sat, 18 Dec 2021 22:28:44 +0100 Subject: [PATCH 6/8] Improved performance of fuzzy string matching --- IDE/src/ui/AutoComplete.bf | 2 +- IDEHelper/Compiler/BfAutoComplete.cpp | 109 +++++++++++++------------- IDEHelper/Compiler/BfAutoComplete.h | 13 ++- IDEHelper/Compiler/BfCompiler.cpp | 25 +++--- 4 files changed, 79 insertions(+), 70 deletions(-) diff --git a/IDE/src/ui/AutoComplete.bf b/IDE/src/ui/AutoComplete.bf index 77c242fa..aace0a59 100644 --- a/IDE/src/ui/AutoComplete.bf +++ b/IDE/src/ui/AutoComplete.bf @@ -409,7 +409,7 @@ namespace IDE.ui for(char32 c in mEntryDisplay.DecodedChars) loop: { - if(mMatchIndices.Contains((uint8)index)) + if(mMatchIndices?.Contains((uint8)index) == true) { g.PushColor(DarkTheme.COLOR_MENU_FOCUSED); defer:loop g.PopColor(); diff --git a/IDEHelper/Compiler/BfAutoComplete.cpp b/IDEHelper/Compiler/BfAutoComplete.cpp index f15efa6e..288e1b8f 100644 --- a/IDEHelper/Compiler/BfAutoComplete.cpp +++ b/IDEHelper/Compiler/BfAutoComplete.cpp @@ -30,20 +30,68 @@ AutoCompleteBase::~AutoCompleteBase() AutoCompleteEntry* AutoCompleteBase::AddEntry(AutoCompleteEntry& entry, const StringImpl& filter) { - if ((!DoesFilterMatch(entry.mDisplay, filter.c_str(), entry.mScore, entry.mMatches, sizeof(entry.mMatches))) || (entry.mNamePrefixCount < 0)) + uint8 matches[256]; + + if (!DoesFilterMatch(entry.mDisplay, filter.c_str(), entry.mScore, matches, 256) || (entry.mNamePrefixCount < 0)) return NULL; + + if (matches[0] != UINT8_MAX) + { + for (uint8 i = 0;; i++) + { + uint8 matchIndex = matches[i]; + + if ((matchIndex == 0 && i != 0) || i == UINT8_MAX) + { + entry.mMatchesLength = i; + break; + } + } + + if (entry.mMatches != nullptr) + delete entry.mMatches; + + entry.mMatches = new uint8[entry.mMatchesLength]; + + memcpy(entry.mMatches, matches, entry.mMatchesLength); + } + return AddEntry(entry); } AutoCompleteEntry* AutoCompleteBase::AddEntry(AutoCompleteEntry& entry, const char* filter) { - if ((!DoesFilterMatch(entry.mDisplay, filter, entry.mScore, entry.mMatches, sizeof(entry.mMatches))) || (entry.mNamePrefixCount < 0)) + uint8 matches[256]; + + 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]; + + if ((matchIndex == 0 && i != 0) || i == UINT8_MAX) + { + entry.mMatchesLength = i; + break; + } + } + + if (entry.mMatches != nullptr) + delete entry.mMatches; + + entry.mMatches = new uint8[entry.mMatchesLength]; + + memcpy(entry.mMatches, matches, entry.mMatchesLength); + } + return AddEntry(entry); } AutoCompleteEntry* AutoCompleteBase::AddEntry(const AutoCompleteEntry& entry) -{ +{ if (mEntriesSet.mAllocSize == 0) { mEntriesSet.Reserve(128); @@ -58,13 +106,16 @@ AutoCompleteEntry* AutoCompleteBase::AddEntry(const AutoCompleteEntry& entry) int size = (int)strlen(display) + 1; insertedEntry->mDisplay = (char*)mAlloc.AllocBytes(size); memcpy((char*)insertedEntry->mDisplay, display, size); + + insertedEntry->mMatches = (uint8*)mAlloc.AllocBytes(insertedEntry->mMatchesLength); + memcpy((char*)insertedEntry->mMatches, entry.mMatches, insertedEntry->mMatchesLength); } return insertedEntry; } bool AutoCompleteBase::DoesFilterMatch(const char* entry, const char* filter, int& score, uint8* matches, int maxMatches) -{ +{ if (mIsGetDefinition) { int entryLen = (int)strlen(entry); @@ -95,57 +146,7 @@ bool AutoCompleteBase::DoesFilterMatch(const char* entry, const char* filter, in return false; } - // TODO: also do matches (but probably optimize them) return fts::fuzzy_match(filter, entry, score, matches, maxMatches); - /* - 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() diff --git a/IDEHelper/Compiler/BfAutoComplete.h b/IDEHelper/Compiler/BfAutoComplete.h index 881a2c90..93b973a6 100644 --- a/IDEHelper/Compiler/BfAutoComplete.h +++ b/IDEHelper/Compiler/BfAutoComplete.h @@ -17,12 +17,15 @@ public: const char* mDocumentation; int8 mNamePrefixCount; int mScore; - uint8 mMatches[256]; + uint8* mMatches; + uint8 mMatchesLength; public: AutoCompleteEntry() { mNamePrefixCount = 0; + mMatches = nullptr; + mMatchesLength = 0; } AutoCompleteEntry(const char* entryType, const char* display) @@ -32,6 +35,8 @@ public: mDocumentation = NULL; mNamePrefixCount = 0; mScore = 0; + mMatches = nullptr; + mMatchesLength = 0; } AutoCompleteEntry(const char* entryType, const StringImpl& display) @@ -41,6 +46,8 @@ public: mDocumentation = NULL; mNamePrefixCount = 0; mScore = 0; + mMatches = nullptr; + mMatchesLength = 0; } AutoCompleteEntry(const char* entryType, const StringImpl& display, int namePrefixCount) @@ -50,8 +57,10 @@ public: mDocumentation = NULL; mNamePrefixCount = (int8)namePrefixCount; mScore = 0; + mMatches = nullptr; + mMatchesLength = 0; } - + bool operator==(const AutoCompleteEntry& other) const { return strcmp(mDisplay, other.mDisplay) == 0; diff --git a/IDEHelper/Compiler/BfCompiler.cpp b/IDEHelper/Compiler/BfCompiler.cpp index 8ce3868c..cc540e80 100644 --- a/IDEHelper/Compiler/BfCompiler.cpp +++ b/IDEHelper/Compiler/BfCompiler.cpp @@ -8024,26 +8024,25 @@ void BfCompiler::GenerateAutocompleteInfo() autoCompleteResultString += '@'; autoCompleteResultString += String(entry->mDisplay); - autoCompleteResultString += "\x02"; - for (int i = 0; i < 256; i++) + if (entry->mMatchesLength > 0) { - int match = entry->mMatches[i]; + autoCompleteResultString += "\x02"; + for (int i = 0; i < entry->mMatchesLength; i++) + { + int match = entry->mMatches[i]; - // no more matches after this - if (match == 0 && i != 0) - break; + // Need max 3 chars (largest Hex (FF) + '\0') + char buffer[3]; - // Need max 3 chars (largest Hex (FF) + '\0') - char buffer[3]; + _itoa_s(match, buffer, 16); - _itoa_s(match, buffer, 16); + autoCompleteResultString += String(buffer); + autoCompleteResultString += ","; + } - autoCompleteResultString += String(buffer); - autoCompleteResultString += ","; + autoCompleteResultString += "X"; } - autoCompleteResultString += "X"; - if (entry->mDocumentation != NULL) { autoCompleteResultString += '\x03'; From ac99191487509e3eada9b431d0e87b1b1fa5a345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Mon, 20 Dec 2021 13:35:58 +0100 Subject: [PATCH 7/8] Sort updated autocomplete results --- IDE/src/ui/AutoComplete.bf | 80 +++++++++++++++++++++++---- IDEHelper/Compiler/BfAutoComplete.cpp | 69 ++++++++++++----------- IDEHelper/Compiler/BfCompiler.cpp | 4 ++ 3 files changed, 106 insertions(+), 47 deletions(-) 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; }); From 8f0502972fdcd12f44a125d1f871257d6bbc3de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Sat, 25 Dec 2021 20:14:23 +0100 Subject: [PATCH 8/8] Added option to toggle between classic and fuzzy autocomplete --- IDE/src/Compiler/BfParser.bf | 8 +- IDE/src/Compiler/BfResolvePassData.bf | 4 +- IDE/src/Settings.bf | 5 +- IDE/src/ui/AutoComplete.bf | 107 ++++++++++++++++++++++---- IDE/src/ui/SettingsDialog.bf | 1 + IDE/src/ui/SourceViewPanel.bf | 5 +- IDEHelper/Compiler/BfAutoComplete.cpp | 71 ++++++++++++++--- IDEHelper/Compiler/BfAutoComplete.h | 3 +- IDEHelper/Compiler/BfParser.cpp | 4 +- 9 files changed, 172 insertions(+), 36 deletions(-) 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; }