summaryrefslogtreecommitdiffstats
path: root/Tools/idle
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/idle')
-rw-r--r--Tools/idle/AutoIndent.py235
1 files changed, 69 insertions, 166 deletions
diff --git a/Tools/idle/AutoIndent.py b/Tools/idle/AutoIndent.py
index f860898..b1a2167 100644
--- a/Tools/idle/AutoIndent.py
+++ b/Tools/idle/AutoIndent.py
@@ -38,6 +38,8 @@ TK_TABWIDTH_DEFAULT = 8
###$ win <Alt-Key-6>
###$ unix <Alt-Key-6>
+import PyParse
+
class AutoIndent:
menudefs = [
@@ -99,6 +101,20 @@ class AutoIndent:
indentwidth = 4
tabwidth = TK_TABWIDTH_DEFAULT
+ # When searching backwards for the closest preceding def or class,
+ # first start num_context_lines[0] lines back, then
+ # num_context_lines[1] lines back if that didn't work, and so on.
+ # The last value should be huge (larger than the # of lines in a
+ # conceivable file).
+ # Making the initial values larger slows things down more often.
+ # OTOH, if you happen to find a line that looks like a def or class
+ # in a multiline string, and the start of the string isn't in the
+ # chunk, the parsing is utterly hosed. Can't think of a way to
+ # stop that without always reparsing from the start of the file.
+ # doctest.py is a killer example of this (IDLE is useless for
+ # editing that!).
+ num_context_lines = 50, 500, 5000000
+
def __init__(self, editwin):
self.text = editwin.text
@@ -218,47 +234,69 @@ class AutoIndent:
i, n = 0, len(line)
while i < n and line[i] in " \t":
i = i+1
+ if i == n:
+ # the cursor is in or at leading indentation; just inject
+ # an empty line at the start
+ text.insert("insert linestart", '\n')
+ return "break"
indent = line[:i]
# strip trailing whitespace
i = 0
while line and line[-1] in " \t":
line = line[:-1]
- i = i + 1
+ i = i+1
if i:
text.delete("insert - %d chars" % i, "insert")
- # XXX this reproduces the current line's indentation,
- # without regard for usetabs etc; could instead insert
- # "\n" + self._make_blanks(classifyws(indent)[1]).
- text.insert("insert", "\n" + indent)
-
+ text.insert("insert", '\n')
# adjust indentation for continuations and block open/close
- x = LineStudier(line)
- if x.is_block_opener():
- self.smart_indent_event(event)
- elif x.is_bracket_continued():
- # if there's something interesting after the last open
- # bracket, line up with it; else just indent one level
- i = x.last_open_bracket_index() + 1
- while i < n and line[i] in " \t":
- i = i + 1
- if i < n and line[i] not in "#\n\\":
- effective = len(string.expandtabs(line[:i],
- self.tabwidth))
+ lno = index2line(text.index('insert'))
+ y = PyParse.Parser(self.indentwidth, self.tabwidth)
+ for context in self.num_context_lines:
+ startat = max(lno - context, 1)
+ rawtext = text.get(`startat` + ".0", "insert")
+ y.set_str(rawtext)
+ bod = y.find_last_def_or_class()
+ if bod is not None or startat == 1:
+ break
+ y.set_lo(bod or 0)
+ c = y.get_continuation_type()
+ if c != PyParse.C_NONE:
+ # The current stmt hasn't ended yet.
+ if c == PyParse.C_STRING:
+ # inside a string; just mimic the current indent
+ text.insert("insert", indent)
+ elif c == PyParse.C_BRACKET:
+ # line up with the first (if any) element of the
+ # last open bracket structure; else indent one
+ # level beyond the indent of the line with the last
+ # open bracket
+ self.reindent_to(y.compute_bracket_indent())
+ elif c == PyParse.C_BACKSLASH:
+ # if more than one line in this stmt already, just
+ # mimic the current indent; else if initial line has
+ # a start on an assignment stmt, indent to beyond
+ # leftmost =; else to beyond first chunk of non-
+ # whitespace on initial line
+ if y.get_num_lines_in_stmt() > 1:
+ text.insert("insert", indent)
+ else:
+ self.reindent_to(y.compute_backslash_indent())
else:
- raw, effective = classifyws(indent, self.tabwidth)
- effective = effective + self.indentwidth
- self.reindent_to(effective)
- elif x.is_backslash_continued():
- # local info isn't enough to do anything intelligent here;
- # e.g., if it's the 2nd line a backslash block we want to
- # indent extra, but if it's the 3rd we don't want to indent
- # at all; rather than make endless mistakes, leave it alone
- pass
- elif indent and x.is_block_closer():
+ assert 0, "bogus continuation type " + `c`
+ return "break"
+
+ # This line starts a brand new stmt; indent relative to
+ # indentation of initial line of closest preceding interesting
+ # stmt.
+ indent = y.get_base_indent_string()
+ text.insert("insert", indent)
+ if y.is_block_opener():
+ self.smart_indent_event(event)
+ elif indent and y.is_block_closer():
self.smart_backspace_event(event)
- text.see("insert")
return "break"
finally:
+ text.see("insert")
text.undo_block_stop()
auto_indent = newline_and_indent_event
@@ -392,7 +430,8 @@ class AutoIndent:
def reindent_to(self, column):
text = self.text
text.undo_block_start()
- text.delete("insert linestart", "insert")
+ if text.compare("insert linestart", "!=", "insert"):
+ text.delete("insert linestart", "insert")
if column:
text.insert("insert", self._make_blanks(column))
text.undo_block_stop()
@@ -442,142 +481,6 @@ def classifyws(s, tabwidth):
break
return raw, effective
-class LineStudier:
-
- # set to false by self.study(); the other vars retain default values
- # until then
- needstudying = 1
-
- # line ends with an unescaped backslash not in string or comment?
- backslash_continued = 0
-
- # line ends with an unterminated string?
- string_continued = 0
-
- # the last "interesting" character on a line: the last non-ws char
- # before an optional trailing comment; if backslash_continued, lastch
- # precedes the final backslash; if string_continued, the required
- # string-closer (", """, ', ''')
- lastch = ""
-
- # index of rightmost unmatched ([{ not in a string or comment
- lastopenbrackpos = -1
-
- import re
- _is_block_closer_re = re.compile(r"""
- \s*
- ( return
- | break
- | continue
- | raise
- | pass
- )
- \b
- """, re.VERBOSE).match
-
- # colon followed by optional comment
- _looks_like_opener_re = re.compile(r":\s*(#.*)?$").search
- del re
-
-
- def __init__(self, line):
- if line[-1:] == '\n':
- line = line[:-1]
- self.line = line
- self.stack = []
-
- def is_continued(self):
- return self.is_block_opener() or \
- self.is_backslash_continued() or \
- self.is_bracket_continued() or \
- self.is_string_continued()
-
- def is_block_opener(self):
- if not self._looks_like_opener_re(self.line):
- return 0
- # Looks like an opener, but possible we're in a comment
- # x = 3 # and then:
- # or a string
- # x = ":#"
- # If no comment character, we're not in a comment <duh>, and the
- # colon is the last non-ws char on the line so it's not in a
- # (single-line) string either.
- if string.find(self.line, '#') < 0:
- return 1
- self.study()
- return self.lastch == ":"
-
- def is_backslash_continued(self):
- self.study()
- return self.backslash_continued
-
- def is_bracket_continued(self):
- self.study()
- return self.lastopenbrackpos >= 0
-
- def is_string_continued(self):
- self.study()
- return self.string_continued
-
- def is_block_closer(self):
- return self._is_block_closer_re(self.line)
-
- def last_open_bracket_index(self):
- assert self.stack
- return self.lastopenbrackpos
-
- def study(self):
- if not self.needstudying:
- return
- self.needstudying = 0
- line = self.line
- i, n = 0, len(line)
- while i < n:
- ch = line[i]
- if ch == '\\':
- i = i+1
- if i == n:
- self.backslash_continued = 1
- else:
- self.lastch = ch + line[i]
- i = i+1
-
- elif ch in "\"'":
- # consume string
- w = 1 # width of string quote
- if line[i:i+3] in ('"""', "'''"):
- w = 3
- ch = ch * 3
- i = i+w
- self.lastch = ch
- while i < n:
- if line[i] == '\\':
- i = i+2
- elif line[i:i+w] == ch:
- i = i+w
- break
- else:
- i = i+1
- else:
- self.string_continued = 1
-
- elif ch == '#':
- break
-
- else:
- if ch not in string.whitespace:
- self.lastch = ch
- if ch in "([(":
- self.stack.append(i)
- elif ch in ")]}" and self.stack:
- if line[self.stack[-1]] + ch in ("()", "[]", "{}"):
- del self.stack[-1]
- i = i+1
- # end while i < n:
-
- if self.stack:
- self.lastopenbrackpos = self.stack[-1]
-
import tokenize
_tokenize = tokenize
del tokenize