diff --git a/README.md b/README.md index c8f0a15..a0df036 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,21 @@ You know the drill. To install this plugin, you have two options: ```bash $ git clone https://github.com/rubyruy/SuperPython ``` + +## Config + +You can add some settings in your Sublime's project file: + +* `superpython.prefix` - any string which will be trigger for completion. Default is `super` +* `superpython.style` - support only two values: `full` and `short`. Default is `short`. "Short"-style is Python 3's style: `super().func(*args, **kwargs)` + +Example: +```json +{ + "settings": + { + "superpython.prefix": "foo", + "superpython.style": "full", + } +} +``` diff --git a/SuperPython.py b/SuperPython.py index 5dfda73..7a17db8 100644 --- a/SuperPython.py +++ b/SuperPython.py @@ -4,61 +4,100 @@ class PythonSuperComplete(sublime_plugin.EventListener): + def find_closest_scope(self, view, scope, target, min_indent, max_row): + """Search closest scope upper than current.""" + matches = view.find_by_selector(scope) + matches = [m for m in matches + if (self.get_indent(view, m) < min_indent + and max_row > self.get_row(view, m))] + return matches[-1] + + def get_indent(self, view, region): + """Get size of indentation for 'region'. + + Returns: + int: Size of indentation for 'region' + + """ + line = view.substr(view.line(region)) + return sum(self.tab_size if space == '\t' else 1 + for space in line.replace(line.lstrip(), '')) + + def get_row(self, view, region): + """Get number of first row for 'region'. + + Returns: + int: Number of first row for 'region' + + """ + return view.rowcol(region.begin())[0] + def on_activated(self, view): settings = view.settings() + self.prefix = settings.get('superpython.prefix', 'super') + self.style = settings.get('superpython.style', 'short') self.tab_size = settings.get('tab_size') def on_query_completions(self, view, prefix, locations): - if 'source.python' not in view.scope_name(locations[0]): - # Work only in python source + if prefix != self.prefix: return - if prefix not in ('super', 'supe'): + + point = locations[0] + if not view.match_selector(point, 'source.python'): + # Work only in python source return - target = sublime.Region(locations[0], locations[0]) - indent = self._get_indent(view, target) + target = sublime.Region(point, point) + indent = self.get_indent(view, target) if indent == 0: # We are on the top of indent hierarchy level. Do nothing. return - current_row = self._get_row(view, target) + + row = self.get_row(view, target) try: - fn_region = self._find_closest_scope(view, 'entity.name.function.python', target, indent, current_row) - args_region = self._find_closest_scope(view, 'meta.function.parameters.python', target, indent, current_row) - fn_indent = self._get_indent(view, fn_region) - # search class scope with lower indentation level than founded function - cls_region = self._find_closest_scope(view, 'entity.name.type.class.python', target, fn_indent, current_row) + fn_args_region = self.find_closest_scope( + view, 'meta.function.parameters.python', target, indent, row) + fn_region = self.find_closest_scope( + view, 'entity.name.function.python', target, indent, row) + + fn_indent = self.get_indent(view, fn_region) + cls_region = self.find_closest_scope( + view, 'entity.name.class.python', target, fn_indent, row) except IndexError: # We could't find some scope return - cls_row = self._get_row(view, cls_region) - fn_row = self._get_row(view, fn_region) - if fn_row < cls_row: + cls_row = self.get_row(view, cls_region) + fn_row = self.get_row(view, fn_region) + if cls_row > fn_row: # Our current position in class, but not in function return - fn_name = view.substr(fn_region) cls_name = view.substr(cls_region) - args = view.substr(args_region) - - if ',' in args: - self_name, other_args = args.split(',', 1) - else: - self_name = args - other_args = '' + fn_args = view.substr(fn_args_region) + fn_args = fn_args.strip('()') + fn_name = view.substr(fn_region) - return [('auto-super()', 'super(%s, %s).%s(${1:%s})' % ( - cls_name, self_name, fn_name, other_args.strip()))] + args_self = fn_args + args_other = '' + if ',' in fn_args: + args_self, args_other = fn_args.split(',', 1) - def _find_closest_scope(self, view, scope, target, min_indent, max_row): - # use last found scope upper than current line and with smaller indentation level - matches = view.find_by_selector(scope) - matches = [m for m in matches if self._get_indent(view, m) < min_indent and max_row > self._get_row(view, m)] - return matches[-1] + if args_self not in ('cls', 'self'): + # our function is staticmethod + args_other = fn_args + args_self = cls_name - def _get_indent(self, view, region): - line = view.substr(view.line(region)) - return sum([self.tab_size if s == '\t' else 1 for s in line.replace(line.lstrip(), '')]) + args_other = args_other.strip() + args_other_index = 2 + replacement = 'super(${{1:{cls_name}}}, {args_self})' + if self.style == 'short': + replacement = 'super()' + args_other_index = 1 + replacement += '.{fn_name}(${{{args_other_index}:{args_other}}})' + replacement = replacement.format(cls_name=cls_name, + args_self=args_self, fn_name=fn_name, + args_other=args_other, + args_other_index=args_other_index) - def _get_row(self, view, region): - return view.rowcol(region.begin())[0] + return [(self.prefix, replacement)]