incorrectly had CMake storing directory names as filepaths
[blender-staging.git] / release / scripts / modules / console / intellisense.py
1 # Copyright (c) 2009 www.stani.be (GPL license)
2
3 # ##### BEGIN GPL LICENSE BLOCK #####
4 #
5 #  This program is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU General Public License
7 #  as published by the Free Software Foundation; either version 2
8 #  of the License, or (at your option) any later version.
9 #
10 #  This program is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #  GNU General Public License for more details.
14 #
15 #  You should have received a copy of the GNU General Public License
16 #  along with this program; if not, write to the Free Software Foundation,
17 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 #
19 # ##### END GPL LICENSE BLOCK #####
20
21 # <pep8-80 compliant>
22
23 """This module provides intellisense features such as:
24
25 * autocompletion
26 * calltips
27
28 It unifies all completion plugins and only loads them on demand.
29 """
30
31 # TODO: file complete if startswith quotes
32 import os
33 import re
34
35 # regular expressions to find out which completer we need
36
37 # line which starts with an import statement
38 RE_MODULE = re.compile('^import(\s|$)|from.+')
39
40 # The following regular expression means an 'unquoted' word
41 RE_UNQUOTED_WORD = re.compile(
42     # don't start with a quote
43     '''(?:^|[^"'a-zA-Z0-9_])'''
44     # start with a \w = [a-zA-Z0-9_]
45     '''((?:\w+'''
46     # allow also dots and closed bracket pairs []
47     '''(?:\w|[.]|\[.+?\])*'''
48     # allow empty string
49     '''|)'''
50     # allow an unfinished index at the end (including quotes)
51     '''(?:\[[^\]]*$)?)$''',
52     # allow unicode as theoretically this is possible
53     re.UNICODE)
54
55
56 def complete(line, cursor, namespace, private):
57     """Returns a list of possible completions:
58
59     * name completion
60     * attribute completion (obj.attr)
61     * index completion for lists and dictionaries
62     * module completion (from/import)
63
64     :param line: incomplete text line
65     :type line: str
66     :param cursor: current character position
67     :type cursor: int
68     :param namespace: namespace
69     :type namespace: dict
70     :param private: whether private variables should be listed
71     :type private: bool
72     :returns: list of completions, word
73     :rtype: list, str
74
75     >>> complete('re.sr', 5, {'re': re})
76     (['re.sre_compile', 're.sre_parse'], 're.sr')
77     """
78     re_unquoted_word = RE_UNQUOTED_WORD.search(line[:cursor])
79     if re_unquoted_word:
80         # unquoted word -> module or attribute completion
81         word = re_unquoted_word.group(1)
82         if RE_MODULE.match(line):
83             from . import complete_import
84             matches = complete_import.complete(line)
85             if not private:
86                 matches[:] = [m for m in matches if m[:1] != "_"]
87             matches.sort()
88         else:
89             from . import complete_namespace
90             matches = complete_namespace.complete(word, namespace, private)
91     else:
92         # for now we don't have completers for strings
93         # TODO: add file auto completer for strings
94         word = ''
95         matches = []
96     return matches, word
97
98
99 def expand(line, cursor, namespace, private=True):
100     """This method is invoked when the user asks autocompletion,
101     e.g. when Ctrl+Space is clicked.
102
103     :param line: incomplete text line
104     :type line: str
105     :param cursor: current character position
106     :type cursor: int
107     :param namespace: namespace
108     :type namespace: dict
109     :param private: whether private variables should be listed
110     :type private: bool
111     :returns:
112
113         current expanded line, updated cursor position and scrollback
114
115     :rtype: str, int, str
116
117     >>> expand('os.path.isdir(', 14, {'os': os})[-1]
118     'isdir(s)\\nReturn true if the pathname refers to an existing directory.'
119     >>> expand('abs(', 4, {})[-1]
120     'abs(number) -> number\\nReturn the absolute value of the argument.'
121     """
122     if line[:cursor].strip().endswith('('):
123         from . import complete_calltip
124         matches, word, scrollback = complete_calltip.complete(line,
125             cursor, namespace)
126         prefix = os.path.commonprefix(matches)[len(word):]
127         no_calltip = False
128     else:
129         matches, word = complete(line, cursor, namespace, private)
130         prefix = os.path.commonprefix(matches)[len(word):]
131         if len(matches) == 1:
132             scrollback = ''
133         else:
134             # causes blender bug [#27495] since string keys may contain '.'
135             # scrollback = '  '.join([m.split('.')[-1] for m in matches])
136
137             # add white space to align with the cursor
138             white_space = "    " + (" " * (cursor + len(prefix)))
139             word_prefix = word + prefix
140             scrollback = '\n'.join(
141                     [white_space + m[len(word_prefix):]
142                      if (word_prefix and m.startswith(word_prefix))
143                      else
144                      white_space + m.split('.')[-1]
145                      for m in matches])
146
147         no_calltip = True
148
149     if prefix:
150         line = line[:cursor] + prefix + line[cursor:]
151         cursor += len(prefix)
152         if no_calltip and prefix.endswith('('):
153             return expand(line, cursor, namespace, private)
154     return line, cursor, scrollback