From 97a98053363aaf73bf6c4d782ae8cc31c977da2e Mon Sep 17 00:00:00 2001 From: "elias.bachaalany" Date: Mon, 19 Jul 2010 13:00:33 +0000 Subject: [PATCH] added command completion --- examples/ex_cli.py | 2 +- python.cpp | 33 +++++++++++++++++++++++++++-- pywraps.hpp | 1 + swig/idaapi.i | 53 ++++++++++++++++++++++++++++++++++++++++++++++ swig/kernwin.i | 2 +- 5 files changed, 87 insertions(+), 4 deletions(-) diff --git a/examples/ex_cli.py b/examples/ex_cli.py index 3b31424..438d7c0 100644 --- a/examples/ex_cli.py +++ b/examples/ex_cli.py @@ -50,7 +50,7 @@ class mycli_t(cli_t): This callback is optional. - @param prefix: Line prefix at x (string) + @param prefix: Line prefix at prefix_start (string) @param n: completion number (int) @param line: the current line (string) @param prefix_start: the index where PREFIX starts in LINE (int) diff --git a/python.cpp b/python.cpp index dc42ee1..635f549 100644 --- a/python.cpp +++ b/python.cpp @@ -1,7 +1,7 @@ //--------------------------------------------------------------------- // IDAPython - Python plugin for Interactive Disassembler Pro // -// Copyright (c) 2004-2010 Gergely Erdelyi +// Copyright (c) The IDAPython Team // // All rights reserved. // @@ -1021,6 +1021,35 @@ bool idaapi IDAPython_cli_execute_line(const char *line) return true; } +//------------------------------------------------------------------------- +bool idaapi IDAPYthon_cli_complete_line( + qstring *completion, + const char *prefix, + int n, + const char *line, + int x) +{ + PyObject *py_complete = get_idaapi_attr(S_IDAAPI_COMPLETION); + if ( py_complete == NULL ) + return false; + + PyObject *py_ret = PyObject_CallFunction(py_complete, "sisi", prefix, n, line, x); + + Py_DECREF(py_complete); + + // Swallow the error + PyW_GetError(completion); + + bool ok = py_ret != NULL && PyString_Check(py_ret) != 0; + + if ( ok ) + *completion = PyString_AS_STRING(py_ret); + + Py_XDECREF(py_ret); + + return ok; +} + //------------------------------------------------------------------------- static const cli_t cli_python = { @@ -1030,7 +1059,7 @@ static const cli_t cli_python = "Python - IDAPython plugin", "Enter any Python expression", IDAPython_cli_execute_line, - NULL, + IDAPYthon_cli_complete_line, NULL }; diff --git a/pywraps.hpp b/pywraps.hpp index d559596..35c91d5 100644 --- a/pywraps.hpp +++ b/pywraps.hpp @@ -25,6 +25,7 @@ #define S_IDAAPI_MODNAME "idaapi" #define S_IDC_MODNAME "idc" #define S_IDAAPI_EXECSCRIPT "IDAPython_ExecScript" +#define S_IDAAPI_COMPLETION "IDAPython_Completion" // Vector of PyObject* typedef qvector ppyobject_vec_t; diff --git a/swig/idaapi.i b/swig/idaapi.i index 7a5f675..0397d11 100644 --- a/swig/idaapi.i +++ b/swig/idaapi.i @@ -1635,10 +1635,13 @@ typedef int error_t; %pythoncode %{ # + import struct import traceback import os import sys +import __builtin__ + # ----------------------------------------------------------------------- # Seek constants @@ -1935,6 +1938,56 @@ def IDAPython_ExecScript(script, g): return PY_COMPILE_ERR +# ---------------------------------------------------------------------- +class __IDAPython_Completion_Util(object): + """Internal utility class for auto-completion support""" + def __init__(self): + self.n = 0 + self.completion = None + + @staticmethod + def parse_identifier(line, prefix, prefix_start): + """Parse a line and extracts""" + id_start = prefix_start + while id_start > 0: + ch = line[id_start] + if not ch.isalpha() and ch != '.' and ch != '_': + id_start += 1 + break + id_start -= 1 + + return line[id_start:prefix_start + len(prefix)] + + @staticmethod + def get_completion(id, prefix): + try: + parts = id.split('.') + m = sys.modules['__main__'] + c = len(parts) + for i in xrange(0, c-1): + m = getattr(m, parts[i]) + except Exception, e: + return None + else: + # search in the module + completion = [x for x in dir(m) if x.startswith(prefix)] + + # no completion found? looking from the global scope? then try the builtins + if not completion and c == 1: + completion = [x for x in dir(__builtin__) if x.startswith(prefix)] + + return completion if len(completion) else None + + def __call__(self, prefix, n, line, prefix_start): + if n == 0: + self.n = n + id = self.parse_identifier(line, prefix, prefix_start) + self.completion = self.get_completion(id, prefix) + return None if self.completion is None or n >= len(self.completion) else self.completion[n] + + +IDAPython_Completion = __IDAPython_Completion_Util() + # The general callback format of notify_when() is: diff --git a/swig/kernwin.i b/swig/kernwin.i index 0659747..572fd71 100644 --- a/swig/kernwin.i +++ b/swig/kernwin.i @@ -2715,7 +2715,7 @@ class cli_t(pyidc_opaque_object_t): # # This callback is optional. # -# @param prefix: Line prefix at x (string) +# @param prefix: Line prefix at prefix_start (string) # @param n: completion number (int) # @param line: the current line (string) # @param prefix_start: the index where PREFIX starts in LINE (int)