|
48 | 48 | SUBSTRING = 'substring' |
49 | 49 | FUZZY = 'fuzzy' |
50 | 50 |
|
51 | | -def complete(text, namespace=None, config=None): |
| 51 | +def attr_complete(text, namespace=None, config=None): |
52 | 52 | """Return list of matches """ |
53 | 53 | if namespace is None: |
54 | 54 | namespace = __main__.__dict__ #TODO figure out if this __main__ still makes sense |
@@ -177,49 +177,124 @@ def filename_matches(cs): |
177 | 177 | matches.append(filename) |
178 | 178 | return matches |
179 | 179 |
|
180 | | -def find_matches(cursor_offset, current_line, locals_, argspec, config, magic_methods): |
181 | | - """Returns a list of matches and function to use for replacing words on tab""" |
| 180 | +def last_part_of_filename(filename): |
| 181 | + filename.rstrip(os.sep).rsplit(os.sep)[-1] |
| 182 | + if os.sep in filename[:-1]: |
| 183 | + return filename[filename.rindex(os.sep, 0, -1)+1:] |
| 184 | + else: |
| 185 | + return filename |
| 186 | + |
| 187 | +def after_last_dot(name): |
| 188 | + return name.rstrip('.').rsplit('.')[-1] |
| 189 | + |
| 190 | +def dict_key_format(filename): |
| 191 | + # dictionary key suggestions |
| 192 | + #items = [x.rstrip(']') for x in items] |
| 193 | + #if current_item: |
| 194 | + # current_item = current_item.rstrip(']') |
| 195 | + pass |
| 196 | + |
| 197 | +def get_completer(cursor_offset, current_line, locals_, argspec, config, magic_methods): |
| 198 | + """Returns a list of matches and a class for what kind of completion is happening |
| 199 | +
|
| 200 | + If no completion type is relevant, returns None, None""" |
182 | 201 |
|
183 | 202 | #TODO use the smarter current_string() in Repl that knows about the buffer |
184 | 203 | #TODO don't pass in config, pass in the settings themselves |
185 | | - #TODO if importcompletion returns None, that means short circuit return, not |
186 | | - # try something else |
187 | | - if line.current_string(cursor_offset, current_line): |
188 | | - matches = filename_matches(line.current_string(cursor_offset, current_line)[2]) |
189 | | - return matches, line.current_string |
190 | | - |
191 | | - if line.current_word(cursor_offset, current_line) is None: |
192 | | - return [], None |
193 | | - |
194 | | - matches = importcompletion.complete(cursor_offset, current_line) |
195 | | - if matches: |
196 | | - return matches, line.current_word |
197 | | - |
198 | | - cw = line.current_word(cursor_offset, current_line)[2] |
199 | | - |
200 | | - try: |
201 | | - matches = complete(cw, namespace=locals_, config=config) |
202 | | - except Exception: |
203 | | - # This sucks, but it's either that or list all the exceptions that could |
204 | | - # possibly be raised here, so if anyone wants to do that, feel free to send me |
205 | | - # a patch. XXX: Make sure you raise here if you're debugging the completion |
206 | | - # stuff ! |
207 | | - e = True |
208 | | - raise |
209 | | - else: |
210 | | - e = False |
| 204 | + |
| 205 | + matches = ImportCompletion.matches(cursor_offset, current_line) |
| 206 | + if matches is not None: |
| 207 | + return sorted(set(matches)), ImportCompletion |
| 208 | + |
| 209 | + matches = FilenameCompletion.matches(cursor_offset, current_line) |
| 210 | + if matches is not None: |
| 211 | + return sorted(set(matches)), FilenameCompletion |
| 212 | + |
| 213 | + matches = AttrCompletion.matches(cursor_offset, current_line, locals_=locals_, config=config) |
| 214 | + if matches is not None: |
| 215 | + cw = AttrCompletion.locate(cursor_offset, current_line)[2] |
211 | 216 | matches.extend(magic_methods(cw)) |
| 217 | + if argspec: |
| 218 | + matches.extend(name + '=' for name in argspec[1][0] |
| 219 | + if isinstance(name, basestring) and name.startswith(cw)) |
| 220 | + if py3: |
| 221 | + matches.extend(name + '=' for name in argspec[1][4] |
| 222 | + if name.startswith(cw)) |
212 | 223 |
|
213 | | - if not e and argspec: |
214 | | - matches.extend(name + '=' for name in argspec[1][0] |
215 | | - if isinstance(name, basestring) and name.startswith(cw)) |
216 | | - if py3: |
217 | | - matches.extend(name + '=' for name in argspec[1][4] |
218 | | - if name.startswith(cw)) |
| 224 | + # unless the first character is a _ filter out all attributes starting with a _ |
| 225 | + if not cw.split('.')[-1].startswith('_'): |
| 226 | + matches = [match for match in matches |
| 227 | + if not match.split('.')[-1].startswith('_')] |
219 | 228 |
|
220 | | - # unless the first character is a _ filter out all attributes starting with a _ |
221 | | - if not e and not cw.split('.')[-1].startswith('_'): |
222 | | - matches = [match for match in matches |
223 | | - if not match.split('.')[-1].startswith('_')] |
| 229 | + return sorted(set(matches)), AttrCompletion |
224 | 230 |
|
225 | | - return sorted(set(matches)), line.current_word |
| 231 | + return None, None |
| 232 | + |
| 233 | + |
| 234 | +class BaseCompletionType(object): |
| 235 | + """Describes different completion types""" |
| 236 | + def matches(cls, cursor_offset, line): |
| 237 | + """Returns a list of possible matches given a line and cursor, or None |
| 238 | + if this completion type isn't applicable. |
| 239 | +
|
| 240 | + ie, import completion doesn't make sense if there cursor isn't after |
| 241 | + an import or from statement |
| 242 | +
|
| 243 | + Completion types are used to: |
| 244 | + * `locate(cur, line)` their target word to replace given a line and cursor |
| 245 | + * find `matches(cur, line)` that might replace that word |
| 246 | + * `format(match)` matches to be displayed to the user |
| 247 | + * determine whether suggestions should be `shown_before_tab` |
| 248 | + * `substitute(cur, line, match)` in a match for what's found with `target` |
| 249 | + """ |
| 250 | + raise NotImplementedError |
| 251 | + def locate(cls, cursor_offset, line): |
| 252 | + """Returns a start, stop, and word given a line and cursor, or None |
| 253 | + if no target for this type of completion is found under the cursor""" |
| 254 | + raise NotImplementedError |
| 255 | + def format(cls, word): |
| 256 | + return word |
| 257 | + shown_before_tab = True # whether suggestions should be shown before the |
| 258 | + # user hits tab, or only once that has happened |
| 259 | + def substitute(cls, cursor_offset, line, match): |
| 260 | + """Returns a cursor offset and line with match swapped in""" |
| 261 | + start, end, word = cls.locate(cursor_offset, line) |
| 262 | + result = start + len(match), line[:start] + match + line[end:] |
| 263 | + return result |
| 264 | + |
| 265 | +class ImportCompletion(BaseCompletionType): |
| 266 | + matches = staticmethod(importcompletion.complete) |
| 267 | + locate = staticmethod(line.current_word) |
| 268 | + format = staticmethod(after_last_dot) |
| 269 | + |
| 270 | +class FilenameCompletion(BaseCompletionType): |
| 271 | + shown_before_tab = False |
| 272 | + @classmethod |
| 273 | + def matches(cls, cursor_offset, current_line): |
| 274 | + cs = line.current_string(cursor_offset, current_line) |
| 275 | + if cs is None: |
| 276 | + return None |
| 277 | + return filename_matches(cs[2]) |
| 278 | + locate = staticmethod(line.current_string) |
| 279 | + format = staticmethod(last_part_of_filename) |
| 280 | + |
| 281 | +class AttrCompletion(BaseCompletionType): |
| 282 | + @classmethod |
| 283 | + def matches(cls, cursor_offset, line, locals_, config): |
| 284 | + r = cls.locate(cursor_offset, line) |
| 285 | + if r is None: |
| 286 | + return None |
| 287 | + cw = r[2] |
| 288 | + try: |
| 289 | + return attr_complete(cw, namespace=locals_, config=config) |
| 290 | + except Exception: |
| 291 | + # This sucks, but it's either that or list all the exceptions that could |
| 292 | + # possibly be raised here, so if anyone wants to do that, feel free to send me |
| 293 | + # a patch. XXX: Make sure you raise here if you're debugging the completion |
| 294 | + # stuff ! |
| 295 | + e = True |
| 296 | + raise |
| 297 | + else: |
| 298 | + e = False |
| 299 | + locate = staticmethod(line.current_word) |
| 300 | + format = staticmethod(after_last_dot) |
0 commit comments