summaryrefslogtreecommitdiffstats
path: root/Tools
diff options
context:
space:
mode:
Diffstat (limited to 'Tools')
-rw-r--r--Tools/idle/PyParse.py94
1 files changed, 75 insertions, 19 deletions
diff --git a/Tools/idle/PyParse.py b/Tools/idle/PyParse.py
index 43e10b3..9c3ae5c 100644
--- a/Tools/idle/PyParse.py
+++ b/Tools/idle/PyParse.py
@@ -14,7 +14,22 @@ if 0: # for throwaway debugging output
_synchre = re.compile(r"""
^
[ \t]*
- (?: if | else | elif | while | def | class )
+ (?: if
+ | for
+ | while
+ | else
+ | def
+ | return
+ | assert
+ | break
+ | class
+ | continue
+ | elif
+ | try
+ | except
+ | raise
+ | import
+ )
\b
""", re.VERBOSE | re.MULTILINE).search
@@ -105,11 +120,14 @@ class Parser:
# Return index of a good place to begin parsing, as close to the
# end of the string as possible. This will be the start of some
- # popular stmt like "if" or "def". Return None if none found.
+ # popular stmt like "if" or "def". Return None if none found:
+ # the caller should pass more prior context then, if possible, or
+ # if not (the entire program text up until the point of interest
+ # has already been tried) pass 0 to set_lo.
#
# This will be reliable iff given a reliable is_char_in_string
- # function, meaning that when it says "no", it's absolutely guaranteed
- # that the char is not in a string.
+ # function, meaning that when it says "no", it's absolutely
+ # guaranteed that the char is not in a string.
#
# Ack, hack: in the shell window this kills us, because there's
# no way to tell the differences between output, >>> etc and
@@ -117,28 +135,66 @@ class Parser:
# look like it's in an unclosed paren!:
# Python 1.5.2 (#0, Apr 13 1999, ...
- def find_good_parse_start(self, use_ps1,
- is_char_in_string=None,
+ def find_good_parse_start(self, use_ps1, is_char_in_string=None,
+ _rfind=string.rfind,
_synchre=_synchre):
str, pos = self.str, None
if use_ps1:
- # hack for shell window
+ # shell window
ps1 = '\n' + sys.ps1
- i = string.rfind(str, ps1)
+ i = _rfind(str, ps1)
if i >= 0:
pos = i + len(ps1)
+ # make it look like there's a newline instead
+ # of ps1 at the start -- hacking here once avoids
+ # repeated hackery later
self.str = str[:pos-1] + '\n' + str[pos:]
- elif is_char_in_string:
- # otherwise we can't be sure, so leave pos at None
- i = 0
- while 1:
- m = _synchre(str, i)
- if m:
- s, i = m.span()
- if not is_char_in_string(s):
- pos = s
- else:
- break
+ return pos
+
+ # File window -- real work.
+ if not is_char_in_string:
+ # no clue -- make the caller pass everything
+ return None
+
+ # Peek back from the end for a good place to start,
+ # but don't try too often; pos will be left None, or
+ # bumped to a legitimate synch point.
+ limit = len(str)
+ for tries in range(5):
+ i = _rfind(str, ":\n", 0, limit)
+ if i < 0:
+ break
+ i = _rfind(str, '\n', 0, i) + 1 # start of colon line
+ m = _synchre(str, i, limit)
+ if m and not is_char_in_string(m.start()):
+ pos = m.start()
+ break
+ limit = i
+ if pos is None:
+ # Nothing looks like a block-opener, or stuff does
+ # but is_char_in_string keeps returning true; most likely
+ # we're in or near a giant string, the colorizer hasn't
+ # caught up enough to be helpful, or there simply *aren't*
+ # any interesting stmts. In any of these cases we're
+ # going to have to parse the whole thing to be sure, so
+ # give it one last try from the start, but stop wasting
+ # time here regardless of the outcome.
+ m = _synchre(str)
+ if m and not is_char_in_string(m.start()):
+ pos = m.start()
+ return pos
+
+ # Peeking back worked; look forward until _synchre no longer
+ # matches.
+ i = pos + 1
+ while 1:
+ m = _synchre(str, i)
+ if m:
+ s, i = m.span()
+ if not is_char_in_string(s):
+ pos = s
+ else:
+ break
return pos
# Throw away the start of the string. Intended to be called with