Skip to content

Commit e49af66

Browse files
committed
Addressing all feedback.
1 parent a0e3edf commit e49af66

6 files changed

Lines changed: 111 additions & 39 deletions

File tree

binaryninjaapi.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18905,9 +18905,9 @@ namespace BinaryNinja {
1890518905
static void SetCurrentAddressCallback(void* ctxt, uint64_t addr);
1890618906
static void SetCurrentSelectionCallback(void* ctxt, uint64_t begin, uint64_t end);
1890718907
static char* CompleteInputCallback(void* ctxt, const char* text, uint64_t state);
18908+
static void StopCallback(void* ctxt);
1890818909
static bool CanCompleteArgumentsCallback(void* ctxt, const char* text);
1890918910
static char* CompleteArgumentsCallback(void* ctx, const char* text, uint64_t* argumentStart);
18910-
static void StopCallback(void* ctxt);
1891118911

1891218912
virtual void DestroyInstance();
1891318913

@@ -18922,9 +18922,9 @@ namespace BinaryNinja {
1892218922
virtual void SetCurrentAddress(uint64_t addr);
1892318923
virtual void SetCurrentSelection(uint64_t begin, uint64_t end);
1892418924
virtual std::string CompleteInput(const std::string& text, uint64_t state);
18925+
virtual void Stop();
1892518926
virtual bool CanCompleteArguments(const std::string& text);
1892618927
virtual std::string CompleteArguments(const std::string& text, uint64_t* argumentStart);
18927-
virtual void Stop();
1892818928

1892918929
void Output(const std::string& text);
1893018930
void Warning(const std::string& text);
@@ -18958,9 +18958,9 @@ namespace BinaryNinja {
1895818958
virtual void SetCurrentAddress(uint64_t addr) override;
1895918959
virtual void SetCurrentSelection(uint64_t begin, uint64_t end) override;
1896018960
virtual std::string CompleteInput(const std::string& text, uint64_t state) override;
18961+
virtual void Stop() override;
1896118962
virtual bool CanCompleteArguments(const std::string& text) override;
1896218963
virtual std::string CompleteArguments(const std::string& text, uint64_t* argumentStart) override;
18963-
virtual void Stop() override;
1896418964
};
1896518965

1896618966
/*!

binaryninjacore.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3121,9 +3121,9 @@ extern "C"
31213121
void (*setCurrentAddress)(void* ctxt, uint64_t addr);
31223122
void (*setCurrentSelection)(void* ctxt, uint64_t begin, uint64_t end);
31233123
char* (*completeInput)(void* ctxt, const char* text, uint64_t state);
3124+
void (*stop)(void* ctxt);
31243125
bool (*canCompleteArguments)(void* ctx, const char* text);
31253126
char* (*completeArguments)(void* ctxt, const char* text, uint64_t* argumentStart);
3126-
void (*stop)(void* ctxt);
31273127
} BNScriptingInstanceCallbacks;
31283128

31293129
typedef struct BNScriptingProviderCallbacks

python/bncompleter.py

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ def fnsignature(obj):
7272
return sig
7373

7474

75+
def _format_annotation(annotation):
76+
if isinstance(annotation, str):
77+
return annotation
78+
if isinstance(annotation, typing.ForwardRef):
79+
return annotation.__forward_arg__
80+
try:
81+
return inspect.formatannotation(annotation)
82+
except Exception:
83+
return repr(annotation)
84+
85+
7586
class Completer:
7687
def __init__(self, namespace=None):
7788
"""Create a new completer for the command line.
@@ -101,9 +112,9 @@ def __init__(self, namespace=None):
101112
self.namespace = namespace
102113

103114
def _resolve_callable(self, callable_path: str):
104-
namespace = self.namespace
105115
if self.use_main_ns:
106116
self.namespace = __main__.__dict__
117+
namespace = self.namespace
107118
parts = callable_path.split(".")
108119

109120
if not parts:
@@ -184,6 +195,41 @@ def _is_keyword_unpack_argument(
184195
and argument_tokens[0].string == "**"
185196
)
186197

198+
@staticmethod
199+
def _is_iterable_unpack_argument(
200+
argument_tokens: typing.List[tokenize.TokenInfo]
201+
) -> bool:
202+
return (
203+
len(argument_tokens) > 0
204+
and argument_tokens[0].type == tokenize.OP
205+
and argument_tokens[0].string == "*"
206+
)
207+
208+
@staticmethod
209+
def _var_positional_parameter_index(
210+
parameters: typing.List[inspect.Parameter]
211+
) -> Optional[int]:
212+
return next(
213+
(
214+
index for index, parameter in enumerate(parameters)
215+
if parameter.kind == inspect.Parameter.VAR_POSITIONAL
216+
),
217+
None,
218+
)
219+
220+
@staticmethod
221+
def _keyword_only_parameter_index(
222+
parameters: typing.List[inspect.Parameter], used_keyword_parameters: typing.Set[str]
223+
) -> Optional[int]:
224+
return next(
225+
(
226+
index for index, parameter in enumerate(parameters)
227+
if parameter.kind == inspect.Parameter.KEYWORD_ONLY
228+
and parameter.name not in used_keyword_parameters
229+
),
230+
None,
231+
)
232+
187233
@staticmethod
188234
def _keyword_parameter_index(
189235
parameters: typing.List[inspect.Parameter], keyword_name: str
@@ -241,13 +287,26 @@ def _current_argument_index(
241287
) -> Optional[int]:
242288
positional_argument_count = 0
243289
used_keyword_parameters = set()
290+
var_keyword_index = next(
291+
(
292+
index for index, parameter in enumerate(parameters)
293+
if parameter.kind == inspect.Parameter.VAR_KEYWORD
294+
),
295+
None,
296+
)
297+
seen_var_keyword_argument = False
298+
seen_iterable_unpack_argument = False
244299

245300
for argument_tokens in arguments[:-1]:
246301
keyword_name = cls._keyword_argument_name(argument_tokens)
247302
if keyword_name is not None:
248303
parameter_index = cls._keyword_parameter_index(parameters, keyword_name)
249304
if parameter_index is not None:
250305
used_keyword_parameters.add(parameters[parameter_index].name)
306+
if parameter_index == var_keyword_index:
307+
seen_var_keyword_argument = True
308+
elif cls._is_iterable_unpack_argument(argument_tokens):
309+
seen_iterable_unpack_argument = True
251310
elif not cls._is_keyword_unpack_argument(argument_tokens):
252311
positional_argument_count += 1
253312

@@ -257,14 +316,22 @@ def _current_argument_index(
257316
return cls._keyword_parameter_index(parameters, keyword_name)
258317

259318
if cls._is_keyword_unpack_argument(current_argument):
260-
for index, parameter in enumerate(parameters):
261-
if parameter.kind == inspect.Parameter.VAR_KEYWORD:
262-
return index
263-
return None
319+
return var_keyword_index
320+
if cls._is_iterable_unpack_argument(current_argument):
321+
return cls._var_positional_parameter_index(parameters)
322+
323+
if seen_var_keyword_argument:
324+
return var_keyword_index
325+
if seen_iterable_unpack_argument:
326+
return cls._var_positional_parameter_index(parameters)
264327

265-
return cls._positional_parameter_index(
328+
parameter_index = cls._positional_parameter_index(
266329
parameters, positional_argument_count, used_keyword_parameters
267330
)
331+
if parameter_index is not None:
332+
return parameter_index
333+
334+
return cls._keyword_only_parameter_index(parameters, used_keyword_parameters)
268335

269336
def _get_argument_completion_context(
270337
self, text: str
@@ -276,9 +343,9 @@ def _get_argument_completion_context(
276343
for line in text.splitlines(keepends=True):
277344
line_offsets.append(line_offsets[-1] + len(line))
278345

279-
def absolute_index(position: typing.Tuple[int, int]) -> int:
346+
def absolute_byte_index(position: typing.Tuple[int, int]) -> int:
280347
line, column = position
281-
return line_offsets[line - 1] + column
348+
return len(text[:line_offsets[line - 1] + column].encode("utf-8"))
282349

283350
reader = io.StringIO(text).readline
284351
stream = tokenize.generate_tokens(reader)
@@ -360,16 +427,14 @@ def absolute_index(position: typing.Tuple[int, int]) -> int:
360427
return None
361428

362429
parameters = list(signature.parameters.values())
363-
if not parameters:
364-
return None
365430

366431
arguments = self._split_call_arguments(tokens, call_context["token_index"])
367432

368433
return {
369434
"signature": signature,
370435
"parameters": parameters,
371436
"current_argument_index": self._current_argument_index(parameters, arguments),
372-
"start_index": absolute_index(tokens[call_context["token_index"]].end),
437+
"start_index": absolute_byte_index(tokens[call_context["token_index"]].end),
373438
}
374439

375440
def can_complete_arguments(self, text: str) -> bool:
@@ -395,23 +460,19 @@ def complete_arguments(self, text: str) -> typing.Tuple[Optional[str], int]:
395460
parameters = context["parameters"]
396461
current_argument_index = context["current_argument_index"]
397462

398-
399463
return_args = []
400464
positional_only_count = sum(
401465
1 for p in parameters if p.kind == inspect.Parameter.POSITIONAL_ONLY
402466
)
403467

404468
for i, parameter in enumerate(parameters):
405-
if positional_only_count > 0 and i == positional_only_count:
406-
return_args.append("/")
407-
408469
if (
409-
parameter.kind == inspect.Parameter.KEYWORD_ONLY
410-
and "*" not in return_args
411-
and not any(
412-
p.kind == inspect.Parameter.VAR_POSITIONAL
413-
for p in parameters[:i]
414-
)
470+
parameter.kind == inspect.Parameter.KEYWORD_ONLY
471+
and "*" not in return_args
472+
and not any(
473+
p.kind == inspect.Parameter.VAR_POSITIONAL
474+
for p in parameters[:i]
475+
)
415476
):
416477
return_args.append("*")
417478

@@ -425,10 +486,7 @@ def complete_arguments(self, text: str) -> typing.Tuple[Optional[str], int]:
425486
if i == current_argument_index:
426487
arg_postfix = ''
427488
if parameter.annotation is not inspect.Signature.empty:
428-
try:
429-
annotation = inspect.formatannotation(parameter.annotation)
430-
except Exception:
431-
annotation = repr(parameter.annotation)
489+
annotation = _format_annotation(parameter.annotation)
432490
arg_postfix += f": {annotation}"
433491

434492
if parameter.default is not inspect.Signature.empty:
@@ -441,13 +499,13 @@ def complete_arguments(self, text: str) -> typing.Tuple[Optional[str], int]:
441499

442500
return_args.append(arg)
443501

502+
if positional_only_count > 0 and i + 1 == positional_only_count:
503+
return_args.append("/")
504+
444505
result = ", ".join(return_args)
445506

446507
if signature.return_annotation is not inspect.Signature.empty:
447-
try:
448-
return_annotation = inspect.formatannotation(signature.return_annotation)
449-
except Exception:
450-
return_annotation = repr(signature.return_annotation)
508+
return_annotation = _format_annotation(signature.return_annotation)
451509
result += f'<span class="returnType"> -&gt; {html.escape(return_annotation)}</span>'
452510

453511
return result, context["start_index"]

python/scriptingprovider.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ def _can_complete_arguments(self, ctx, text):
271271
if not isinstance(text, str):
272272
text = text.decode("utf-8")
273273
return self.perform_can_complete_arguments(text)
274-
except:
274+
except Exception:
275275
logger.log_error_for_exception("Unhandled Python exception in ScriptingInstance._can_complete_arguments")
276276
return False
277277

@@ -284,10 +284,11 @@ def _complete_arguments(self, ctx, text, argument_start):
284284
if result is None:
285285
return None
286286
return core.BNAllocString(result)
287-
except:
287+
except Exception:
288288
logger.log_error_for_exception("Unhandled Python exception in ScriptingInstance._complete_arguments")
289289
argument_start[0] = 0
290290
return None
291+
291292
def _stop(self, ctxt):
292293
try:
293294
self.perform_stop()
@@ -1083,15 +1084,15 @@ def perform_complete_input(self, text, state):
10831084
def perform_can_complete_arguments(self, text: str) -> bool:
10841085
try:
10851086
self.interpreter.update_locals()
1086-
except:
1087+
except Exception:
10871088
traceback.print_exc()
10881089
return self.interpreter.completer.can_complete_arguments(text)
10891090

10901091
@abc.abstractmethod
10911092
def perform_complete_arguments(self, text: str) -> Tuple[Optional[str], int]:
10921093
try:
10931094
self.interpreter.update_locals()
1094-
except:
1095+
except Exception:
10951096
traceback.print_exc()
10961097
result = self.interpreter.completer.complete_arguments(text)
10971098
if result[0] is None:

scriptingprovider.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,13 @@ void ScriptingOutputListener::NotifyInputReadyStateChanged(BNScriptingProviderIn
5656

5757
ScriptingInstance::ScriptingInstance(ScriptingProvider* provider)
5858
{
59-
BNScriptingInstanceCallbacks cb;
59+
BNScriptingInstanceCallbacks cb = {};
6060
cb.context = this;
6161
cb.destroyInstance = DestroyInstanceCallback;
6262
cb.externalRefTaken = nullptr;
6363
cb.externalRefReleased = nullptr;
6464
cb.executeScriptInput = ExecuteScriptInputCallback;
65+
cb.executeScriptInputFromFilename = ExecuteScriptFromFilenameCallback;
6566
cb.cancelScriptInput = CancelScriptInputCallback;
6667
cb.releaseBinaryView = ReleaseBinaryViewCallback;
6768
cb.setCurrentBinaryView = SetCurrentBinaryViewCallback;
@@ -70,9 +71,9 @@ ScriptingInstance::ScriptingInstance(ScriptingProvider* provider)
7071
cb.setCurrentAddress = SetCurrentAddressCallback;
7172
cb.setCurrentSelection = SetCurrentSelectionCallback;
7273
cb.completeInput = CompleteInputCallback;
74+
cb.stop = StopCallback;
7375
cb.canCompleteArguments = CanCompleteArgumentsCallback;
7476
cb.completeArguments = CompleteArgumentsCallback;
75-
cb.stop = StopCallback;
7677
AddRefForRegistration();
7778
m_object = BNInitScriptingInstance(provider->GetObject(), &cb);
7879
}
@@ -98,6 +99,13 @@ BNScriptingProviderExecuteResult ScriptingInstance::ExecuteScriptInputCallback(v
9899
}
99100

100101

102+
BNScriptingProviderExecuteResult ScriptingInstance::ExecuteScriptFromFilenameCallback(void* ctxt, const char* filename)
103+
{
104+
CallbackRef<ScriptingInstance> instance(ctxt);
105+
return instance->ExecuteScriptInputFromFilename(filename);
106+
}
107+
108+
101109
void ScriptingInstance::CancelScriptInputCallback(void* ctxt)
102110
{
103111
CallbackRef<ScriptingInstance> instance(ctxt);

ui/scriptingconsole.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ class BINARYNINJAUIAPI ScriptingConsoleEdit : public QTextEdit
126126
std::atomic<uint64_t> latestRequest {0};
127127
std::mutex callbackMutex;
128128
};
129+
static constexpr int ArgumentAssistDebounceInterval = 100;
129130

130131
ScriptingConsole* m_console;
131132
int m_charHeight;
@@ -139,7 +140,10 @@ class BINARYNINJAUIAPI ScriptingConsoleEdit : public QTextEdit
139140
CompleteArgumentsCallback m_completeArgumentsCallback;
140141

141142
ScriptingArgumentAssistPopup* m_argumentAssistPopup;
143+
QTimer* m_argumentAssistUpdateTimer;
142144
std::shared_ptr<ArgumentAssistRequestState> m_argumentAssistState;
145+
bool m_argumentAssistRequestRunning;
146+
bool m_argumentAssistRequestPending;
143147
uint64_t m_argumentAssistPopupPosition;
144148

145149
uint64_t m_completionRegionStart;
@@ -148,6 +152,7 @@ class BINARYNINJAUIAPI ScriptingConsoleEdit : public QTextEdit
148152

149153
bool canShowArgumentAssistPopup() const;
150154
void hideArgumentAssistPopup();
155+
void scheduleArgumentAssistPopupUpdate();
151156
void updateArgumentAssistPopup();
152157
void applyArgumentAssistPopup(const QString& text, uint64_t popupPosition);
153158

0 commit comments

Comments
 (0)