diff options
| -rw-r--r-- | binaryninjaapi.cpp | 11 | ||||
| -rw-r--r-- | binaryninjaapi.h | 2 | ||||
| -rw-r--r-- | binaryninjacore.h | 3 | ||||
| -rw-r--r-- | docs/about/open-source.md | 2 | ||||
| -rw-r--r-- | python/__init__.py | 15 | ||||
| -rw-r--r-- | python/bncompleter.py | 33 |
6 files changed, 57 insertions, 9 deletions
diff --git a/binaryninjaapi.cpp b/binaryninjaapi.cpp index 6610782d..202019ed 100644 --- a/binaryninjaapi.cpp +++ b/binaryninjaapi.cpp @@ -585,3 +585,14 @@ fmt::format_context::iterator fmt::formatter<BinaryNinja::NameList>::format(cons { return fmt::format_to(ctx.out(), "{}", obj.GetString()); } + + +std::optional<size_t> BinaryNinja::FuzzyMatchSingle(const std::string& target, const std::string& query) +{ + size_t result = BNFuzzyMatchSingle(target.c_str(), query.c_str()); + if (result == 0) + { + return std::nullopt; + } + return result; +} diff --git a/binaryninjaapi.h b/binaryninjaapi.h index 0a1fab7c..f7362240 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -1215,6 +1215,8 @@ namespace BinaryNinja { return result; } + std::optional<size_t> FuzzyMatchSingle(const std::string& target, const std::string& query); + /*! @} */ diff --git a/binaryninjacore.h b/binaryninjacore.h index 081f0d4b..6c83924a 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -37,7 +37,7 @@ // Current ABI version for linking to the core. This is incremented any time // there are changes to the API that affect linking, including new functions, // new types, or modifications to existing functions or types. -#define BN_CURRENT_CORE_ABI_VERSION 65 +#define BN_CURRENT_CORE_ABI_VERSION 66 // Minimum ABI version that is supported for loading of plugins. Plugins that // are linked to an ABI version less than this will not be able to load and @@ -6586,6 +6586,7 @@ extern "C" BINARYNINJACOREAPI char* BNScriptingInstanceCompleteInput( BNScriptingInstance* instance, const char* text, uint64_t state); BINARYNINJACOREAPI void BNStopScriptingInstance(BNScriptingInstance* instance); + BINARYNINJACOREAPI size_t BNFuzzyMatchSingle(const char* target, const char* query); // Main thread actions BINARYNINJACOREAPI void BNRegisterMainThread(BNMainThreadCallbacks* callbacks); diff --git a/docs/about/open-source.md b/docs/about/open-source.md index 58bf4d05..5edd31f9 100644 --- a/docs/about/open-source.md +++ b/docs/about/open-source.md @@ -68,6 +68,7 @@ The previous tools are used in the generation of our documentation, but are not - [cipher] ([cipher license] - APACHE 2.0 / MIT) - [clang] ([clang license] - APACHE 2.0) - [clang-sys] ([clang-sys license] - APACHE 2.0) + - [code_fuzzy_match] ([code_fuzzy_match license] - MIT) - [core-foundation-sys] ([core-foundation-sys license] - APACHE 2.0 / MIT) - [core-foundation] ([core-foundation license] - APACHE 2.0 / MIT) - [cpp_demangle] ([cpp_demangle license] - APACHE 2.0 / MIT) @@ -417,6 +418,7 @@ Please note that we offer no support for running Binary Ninja with modified Qt l [clang license]: https://github.com/KyleMayes/clang-rs/blob/master/LICENSE.txt [clang-sys]: https://github.com/KyleMayes/clang-sys [clang-sys license]: https://github.com/KyleMayes/clang-sys/blob/master/LICENSE.txt +[code_fuzzy_match license]: https://github.com/D0ntPanic/code-fuzzy-match/blob/master/LICENSE [core-foundation-sys]: https://github.com/servo/core-foundation-rs [core-foundation-sys license]: https://github.com/servo/core-foundation-rs/blob/main/LICENSE-MIT [core-foundation]: https://github.com/servo/core-foundation-rs diff --git a/python/__init__.py b/python/__init__.py index ec218d38..144e2643 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -461,6 +461,21 @@ class UIPluginInHeadlessError(Exception): Exception.__init__(self, *args, **kwargs) +def fuzzy_match_single(target, query) -> Optional[int]: + """ + Fuzzy match a string against a query string. Returns a score that is higher for + a more confident match, or None if the query does not match the target string. + + :param target: Target (larger) string + :param query: Query (smaller) string + :return: Confidence of match, or None if the string doesn't match + """ + result = core.BNFuzzyMatchSingle(target, query) + if result == 0: + return None + return result + + # Load Collaboration scripts from Enterprise (they are bundled in shipping builds) try: from . import collaboration diff --git a/python/bncompleter.py b/python/bncompleter.py index 3b632aab..4fe9ed73 100644 --- a/python/bncompleter.py +++ b/python/bncompleter.py @@ -41,6 +41,7 @@ Notes: """ import atexit +import binaryninja import __main__ import inspect import sys @@ -49,6 +50,15 @@ from typing import Optional __all__ = ["Completer"] +def fuzzy_match(target, query): + if query == "": + return 1 + if binaryninja.Settings().get_bool("ui.scripting.fuzzySearch"): + return binaryninja.fuzzy_match_single(target, query) + else: + return query in target + + def fnsignature(obj): try: sig = str(inspect.signature(obj)) @@ -128,24 +138,29 @@ class Completer: seen = {"__builtins__"} n = len(text) for word in keyword.kwlist: - if word[:n] == text: + score = fuzzy_match(word, text) + if score is not None: seen.add(word) if word in {'finally', 'try'}: word = word + ':' elif word not in {'False', 'None', 'True', 'break', 'continue', 'pass', 'else'}: word = word + ' ' - matches.append(word) + matches.append((-score, word)) #Not sure why in the console builtins becomes a dict but this works for now. if hasattr(__builtins__, '__dict__'): # type: ignore # remove this ignore > pyright 1.1.149 builtins = __builtins__.__dict__ # type: ignore # remove this ignore > pyright 1.1.149 else: builtins = __builtins__ # type: ignore # remove this ignore > pyright 1.1.149 + for nspace in [self.namespace, builtins]: for word, val in nspace.items(): - if word[:n] == text and word not in seen: + score = fuzzy_match(word, text) + if score is not None and word not in seen: + # if word[:n] == text and word not in seen: seen.add(word) - matches.append(self._callable_postfix(val, word)) - return matches + matches.append((-score, self._callable_postfix(val, word))) + matches.sort() + return [match for (_, match) in matches] def attr_matches(self, text): """Compute matches when text contains a dot. @@ -186,7 +201,9 @@ class Completer: noprefix = None while True: for word in words: - if (word[:n] == attr and not (noprefix and word[:n + 1] == noprefix)): + score = fuzzy_match(word, attr) + if score is not None: + # if (word[:n] == attr and not (noprefix and word[:n + 1] == noprefix)): match = f"{expr}.{word}" try: val = inspect.getattr_static(thisobject, word) @@ -194,7 +211,7 @@ class Completer: pass # Include even if attribute not set else: match = self._callable_postfix(val, match) - matches.append(match) + matches.append((-score, match)) if matches or not noprefix: break if noprefix == '_': @@ -202,7 +219,7 @@ class Completer: else: noprefix = None matches.sort() - return matches + return [match for (_, match) in matches] def get_class_members(klass): |
