diff --git a/mode_python.py b/mode_python.py index 5123eef..f5617f5 100644 --- a/mode_python.py +++ b/mode_python.py @@ -28,15 +28,16 @@ class PythonGrammar(Grammar): PatternRule(name=r"integer", pattern=r"(?"""|\'\'\')', grammar=Grammar(), end=r'%(tag)s'), - RegionRule(name=r'string', start=r'(?P"""|\'\'\')', grammar=StringGrammar(), end=r'%(tag)s'), - RegionRule(name=r'string', start=r'(?P"|\')', grammar=StringGrammar(), end=r'%(tag)s'), + RegionRule(name=r'string', start=r'"""', grammar=StringGrammar(), end=r'"""'), + RegionRule(name=r'string', start=r"'''", grammar=StringGrammar(), end=r"'''"), + RegionRule(name=r'string', start=r'"', grammar=StringGrammar(), end=r'"'), + RegionRule(name=r'string', start=r"'", grammar=StringGrammar(), end=r"'"), PatternRule(name=r'comment', pattern=r'#.*$'), PatternRule(name=r'continuation', pattern=r'\\$'), ] class PythonTabber(tab2.StackTabber): - unanchored_names = ('null', 'string', 'docstring', 'comment') + unanchored_names = ('null', 'string', 'comment') endlevel_names = ('pass', 'return', 'yield', 'raise', 'break', 'continue') startlevel_names = ('if', 'try', 'class', 'def', 'for', 'while', 'try') def __init__(self, m): @@ -45,13 +46,18 @@ class PythonTabber(tab2.StackTabber): def is_base(self, y): if y == 0: + # we always know that line 0 is indented at the 0 level return True tokens = self.get_tokens(y) if not tokens: + # if a line has no tokens, we don't know much about its indentation return False elif tokens[0].name not in self.unanchored_names: + # if a line has no whitespace and beings with something like + # 'while','class','def','if',etc. then we can start at it return True else: + # otherwise, we can't be sure that its level is correct return False def get_level(self, y): @@ -59,15 +65,25 @@ class PythonTabber(tab2.StackTabber): return self.lines.get(y) def _calc_level(self, y): + # ok, so first remember where we are going, and find our starting point target = y while not self.is_base(y) and y > 0: y -= 1 + # ok, so clear out our stack and then loop over each line self.markers = [] while y <= target: - self.popped = False - tokens = self.get_tokens(y) - currlvl = self.get_curr_level() + self.continued = False + self.popped = False + tokens = self.get_tokens(y) + currlvl = self.get_curr_level() + # if we were continuing, let's pop that previous continuation token + # and note that we're continuing + if self.markers and self.markers[-1].name == 'cont': + self.continued = True + self._pop() + # if we haven't reached the target-line yet, we can detect how many + # levels of unindention, if any, the user chose on previous lines if y < target and tokens: if self.token_is_whitespace(y, 0): l = len(tokens[0].string) @@ -77,8 +93,10 @@ class PythonTabber(tab2.StackTabber): self._pop() currlvl = self.get_curr_level() self.popped = True + # ok, having done all that, we can now process each token on the line for i in range(0, len(tokens)): currlvl = self._handle_token(currlvl, y, i) + # so let's store the level for this line, as well as some debugging self.lines[y] = currlvl self.record[y] = tuple(self.markers) y += 1 @@ -86,40 +104,50 @@ class PythonTabber(tab2.StackTabber): def _handle_other_token(self, currlvl, y, i): token = self.get_token(y, i) fqname = token.fqname() - if fqname == 'string.start': + if fqname == 'continuation': + # we need to pop the indentation level over, unless last line was + # also a continued line + if self.continued: + self._opt_append('cont', currlvl) + else: + self._opt_append('cont', currlvl + 4) + elif fqname == 'string.start': + # while inside of a string, there is no indention leve self._opt_append('string', None) elif fqname == 'string.end': + # since we're done with the string, resume our indentation level self._opt_pop('string') - elif fqname == 'docstring.start': - self._opt_append('docstring', None) - elif fqname == 'docstring.end': - self._opt_pop('docstring') elif fqname == 'delimiter': - if token.string == ':' and self.markers and self.markers[-1].name in ('[', '{'): - # we are in a list range [:] or dictionary key/value {:} - pass - elif self.is_rightmost_token(y, i): - # we are at the end of a block - pass - else: - # we are doing a one-liner - self._pop() + # we only reall care about a colon as part of a one-line statement, + # i.e. "while ok: foo()" or "if True: print 3" + if token.string == ':': + if self.markers and self.markers[-1].name in ('[', '{'): + pass + elif self.is_rightmost_token(y, i): + pass + else: + self._pop() elif fqname == 'keyword': if token.string in self.endlevel_names: + # we know we'll unindent at least once self._pop() elif token.string in self.startlevel_names and self.is_leftmost_token(y, i): + # we know we will indent exactly once self._append(token.string, currlvl + 4) elif token.string in ('elif', 'else') and self.is_leftmost_token(y, i): + # we know we'll unindent at least to the first if/elif if not self.popped: self._pop_until('if', 'elif') currlvl = self.get_curr_level() self._append(token.string, currlvl + 4) elif token.string == 'except' and self.is_leftmost_token(y, i): + # we know we'll unindent at least to the first try if not self.popped: self._pop_until('try') currlvl = self.get_curr_level() self._append(token.string, currlvl + 4) elif token.string == 'finally' and self.is_leftmost_token(y, i): + # we know we'll unindent at least to the first try/except if not self.popped: self._pop_until('try', 'except') currlvl = self.get_curr_level() @@ -127,53 +155,43 @@ class PythonTabber(tab2.StackTabber): return currlvl class Python(mode2.Fundamental): - tabbercls = PythonTabber - grammar = PythonGrammar() + tabbercls = PythonTabber + grammar = PythonGrammar() opentoken = 'delimiter' opentags = {'(': ')', '[': ']', '{': '}'} closetoken = 'delimiter' closetags = {')': '(', ']': '[', '}': '{'} def __init__(self, w): mode2.Fundamental.__init__(self, w) - + # tag matching + self.add_bindings('close-paren', (')',)) + self.add_bindings('close-brace', ('}',)) + self.add_bindings('close-bracket', (']',)) # add python-specific methods self.add_action_and_bindings(PythonCheckSyntax(), ('C-c s',)) self.add_action_and_bindings(PythonDictCleanup(), ('C-c h',)) self.add_action_and_bindings(PythonUpdateTags(), ('C-c t',)) self.add_action_and_bindings(PythonTagComplete(), ('C-c k',)) - - # we want to do these kinds of tag matching - self.add_bindings('close-paren', (')',)) - self.add_bindings('close-brace', ('}',)) - self.add_bindings('close-bracket', (']',)) - + # highlighting self.colors = { - 'keyword': color.build('cyan', 'default'), - 'reserved': color.build('cyan', 'default'), - 'builtin': color.build('cyan', 'default'), - 'functionname': color.build('blue', 'default'), - 'classname': color.build('green', 'default'), - - 'string.start': color.build('green', 'default'), - 'string.null': color.build('green', 'default'), - 'string.escaped': color.build('magenta', 'default'), - 'string.octal': color.build('magenta', 'default'), - 'string.format': color.build('yellow', 'default'), - 'string.end': color.build('green', 'default'), - - 'integer': color.build('default', 'default'), - 'float': color.build('default', 'default'), - 'imaginary': color.build('default', 'default'), - - 'docstring.start': color.build('green', 'default'), - 'docstring.null': color.build('green', 'default'), - 'docstring.end': color.build('green', 'default'), - + 'keyword': color.build('cyan', 'default'), + 'reserved': color.build('cyan', 'default'), + 'builtin': color.build('cyan', 'default'), + 'functionname': color.build('blue', 'default'), + 'classname': color.build('green', 'default'), + 'string.start': color.build('green', 'default'), + 'string.null': color.build('green', 'default'), + 'string.octal': color.build('magenta', 'default'), + 'string.escaped': color.build('magenta', 'default'), + 'string.format': color.build('yellow', 'default'), + 'string.end': color.build('green', 'default'), + 'integer': color.build('default', 'default'), + 'float': color.build('default', 'default'), + 'imaginary': color.build('default', 'default'), 'comment': color.build('red', 'default'), 'continuation': color.build('red', 'default'), 'system_identifier': color.build('cyan', 'default'), } - def name(self): return "Python"