diff options
Diffstat (limited to 'release/scripts/modules/bl_console_utils/autocomplete/intellisense.py')
-rw-r--r-- | release/scripts/modules/bl_console_utils/autocomplete/intellisense.py | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/release/scripts/modules/bl_console_utils/autocomplete/intellisense.py b/release/scripts/modules/bl_console_utils/autocomplete/intellisense.py new file mode 100644 index 00000000000..e53e38dbc53 --- /dev/null +++ b/release/scripts/modules/bl_console_utils/autocomplete/intellisense.py @@ -0,0 +1,136 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# Copyright (c) 2009 www.stani.be + +"""This module provides intellisense features such as: + +* autocompletion +* calltips + +It unifies all completion plugins and only loads them on demand. +""" + +# TODO: file complete if startswith quotes +import os +import re + +# regular expressions to find out which completer we need + +# line which starts with an import statement +RE_MODULE = re.compile(r'''^import(\s|$)|from.+''') + +# The following regular expression means an 'unquoted' word +RE_UNQUOTED_WORD = re.compile( + # don't start with a quote + r'''(?:^|[^"'a-zA-Z0-9_])''' + # start with a \w = [a-zA-Z0-9_] + r'''((?:\w+''' + # allow also dots and closed bracket pairs [] + r'''(?:\w|[.]|\[.+?\])*''' + # allow empty string + r'''|)''' + # allow an unfinished index at the end (including quotes) + r'''(?:\[[^\]]*$)?)$''', + # allow unicode as theoretically this is possible + re.UNICODE) + + +def complete(line, cursor, namespace, private): + """Returns a list of possible completions: + + * name completion + * attribute completion (obj.attr) + * index completion for lists and dictionaries + * module completion (from/import) + + :param line: incomplete text line + :type line: str + :param cursor: current character position + :type cursor: int + :param namespace: namespace + :type namespace: dict + :param private: whether private variables should be listed + :type private: bool + :returns: list of completions, word + :rtype: list, str + + >>> complete('re.sr', 5, {'re': re}) + (['re.sre_compile', 're.sre_parse'], 're.sr') + """ + re_unquoted_word = RE_UNQUOTED_WORD.search(line[:cursor]) + if re_unquoted_word: + # unquoted word -> module or attribute completion + word = re_unquoted_word.group(1) + if RE_MODULE.match(line): + from . import complete_import + matches = complete_import.complete(line) + if not private: + matches[:] = [m for m in matches if m[:1] != "_"] + matches.sort() + else: + from . import complete_namespace + matches = complete_namespace.complete(word, namespace, private=private) + else: + # for now we don't have completers for strings + # TODO: add file auto completer for strings + word = '' + matches = [] + return matches, word + + +def expand(line, cursor, namespace, *, private=True): + """This method is invoked when the user asks autocompletion, + e.g. when Ctrl+Space is clicked. + + :param line: incomplete text line + :type line: str + :param cursor: current character position + :type cursor: int + :param namespace: namespace + :type namespace: dict + :param private: whether private variables should be listed + :type private: bool + :returns: + + current expanded line, updated cursor position and scrollback + + :rtype: str, int, str + + >>> expand('os.path.isdir(', 14, {'os': os})[-1] + 'isdir(s)\\nReturn true if the pathname refers to an existing directory.' + >>> expand('abs(', 4, {})[-1] + 'abs(number) -> number\\nReturn the absolute value of the argument.' + """ + if line[:cursor].strip().endswith('('): + from . import complete_calltip + matches, word, scrollback = complete_calltip.complete( + line, cursor, namespace) + prefix = os.path.commonprefix(matches)[len(word):] + no_calltip = False + else: + matches, word = complete(line, cursor, namespace, private) + prefix = os.path.commonprefix(matches)[len(word):] + if len(matches) == 1: + scrollback = '' + else: + # causes blender bug T27495 since string keys may contain '.' + # scrollback = ' '.join([m.split('.')[-1] for m in matches]) + + # add white space to align with the cursor + white_space = " " + (" " * (cursor + len(prefix))) + word_prefix = word + prefix + scrollback = '\n'.join( + [white_space + m[len(word_prefix):] + if (word_prefix and m.startswith(word_prefix)) + else + white_space + m.split('.')[-1] + for m in matches]) + + no_calltip = True + + if prefix: + line = line[:cursor] + prefix + line[cursor:] + cursor += len(prefix.encode('utf-8')) + if no_calltip and prefix.endswith('('): + return expand(line, cursor, namespace, private=private) + return line, cursor, scrollback |