Uploaded Test files
This commit is contained in:
parent
f584ad9d97
commit
2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions
92
venv/Lib/site-packages/pythonwin/pywin/idle/AutoExpand.py
Normal file
92
venv/Lib/site-packages/pythonwin/pywin/idle/AutoExpand.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
import string
|
||||
import re
|
||||
|
||||
###$ event <<expand-word>>
|
||||
###$ win <Alt-slash>
|
||||
###$ unix <Alt-slash>
|
||||
|
||||
class AutoExpand:
|
||||
|
||||
keydefs = {
|
||||
'<<expand-word>>': ['<Alt-slash>'],
|
||||
}
|
||||
|
||||
unix_keydefs = {
|
||||
'<<expand-word>>': ['<Meta-slash>'],
|
||||
}
|
||||
|
||||
menudefs = [
|
||||
('edit', [
|
||||
('E_xpand word', '<<expand-word>>'),
|
||||
]),
|
||||
]
|
||||
|
||||
wordchars = string.ascii_letters + string.digits + "_"
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.text = editwin.text
|
||||
self.text.wordlist = None # XXX what is this?
|
||||
self.state = None
|
||||
|
||||
def expand_word_event(self, event):
|
||||
curinsert = self.text.index("insert")
|
||||
curline = self.text.get("insert linestart", "insert lineend")
|
||||
if not self.state:
|
||||
words = self.getwords()
|
||||
index = 0
|
||||
else:
|
||||
words, index, insert, line = self.state
|
||||
if insert != curinsert or line != curline:
|
||||
words = self.getwords()
|
||||
index = 0
|
||||
if not words:
|
||||
self.text.bell()
|
||||
return "break"
|
||||
word = self.getprevword()
|
||||
self.text.delete("insert - %d chars" % len(word), "insert")
|
||||
newword = words[index]
|
||||
index = (index + 1) % len(words)
|
||||
if index == 0:
|
||||
self.text.bell() # Warn we cycled around
|
||||
self.text.insert("insert", newword)
|
||||
curinsert = self.text.index("insert")
|
||||
curline = self.text.get("insert linestart", "insert lineend")
|
||||
self.state = words, index, curinsert, curline
|
||||
return "break"
|
||||
|
||||
def getwords(self):
|
||||
word = self.getprevword()
|
||||
if not word:
|
||||
return []
|
||||
before = self.text.get("1.0", "insert wordstart")
|
||||
wbefore = re.findall(r"\b" + word + r"\w+\b", before)
|
||||
del before
|
||||
after = self.text.get("insert wordend", "end")
|
||||
wafter = re.findall(r"\b" + word + r"\w+\b", after)
|
||||
del after
|
||||
if not wbefore and not wafter:
|
||||
return []
|
||||
words = []
|
||||
dict = {}
|
||||
# search backwards through words before
|
||||
wbefore.reverse()
|
||||
for w in wbefore:
|
||||
if dict.get(w):
|
||||
continue
|
||||
words.append(w)
|
||||
dict[w] = w
|
||||
# search onwards through words after
|
||||
for w in wafter:
|
||||
if dict.get(w):
|
||||
continue
|
||||
words.append(w)
|
||||
dict[w] = w
|
||||
words.append(word)
|
||||
return words
|
||||
|
||||
def getprevword(self):
|
||||
line = self.text.get("insert linestart", "insert")
|
||||
i = len(line)
|
||||
while i > 0 and line[i-1] in self.wordchars:
|
||||
i = i-1
|
||||
return line[i:]
|
529
venv/Lib/site-packages/pythonwin/pywin/idle/AutoIndent.py
Normal file
529
venv/Lib/site-packages/pythonwin/pywin/idle/AutoIndent.py
Normal file
|
@ -0,0 +1,529 @@
|
|||
import sys
|
||||
import string, tokenize
|
||||
from . import PyParse
|
||||
from pywin import default_scintilla_encoding
|
||||
|
||||
if sys.version_info < (3,):
|
||||
# in py2k, tokenize() takes a 'token eater' callback, while
|
||||
# generate_tokens is a generator that works with str objects.
|
||||
token_generator = tokenize.generate_tokens
|
||||
else:
|
||||
# in py3k tokenize() is the generator working with 'byte' objects, and
|
||||
# token_generator is the 'undocumented b/w compat' function that
|
||||
# theoretically works with str objects - but actually seems to fail)
|
||||
token_generator = tokenize.tokenize
|
||||
|
||||
class AutoIndent:
|
||||
|
||||
menudefs = [
|
||||
('edit', [
|
||||
None,
|
||||
('_Indent region', '<<indent-region>>'),
|
||||
('_Dedent region', '<<dedent-region>>'),
|
||||
('Comment _out region', '<<comment-region>>'),
|
||||
('U_ncomment region', '<<uncomment-region>>'),
|
||||
('Tabify region', '<<tabify-region>>'),
|
||||
('Untabify region', '<<untabify-region>>'),
|
||||
('Toggle tabs', '<<toggle-tabs>>'),
|
||||
('New indent width', '<<change-indentwidth>>'),
|
||||
]),
|
||||
]
|
||||
|
||||
keydefs = {
|
||||
'<<smart-backspace>>': ['<Key-BackSpace>'],
|
||||
'<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'],
|
||||
'<<smart-indent>>': ['<Key-Tab>']
|
||||
}
|
||||
|
||||
windows_keydefs = {
|
||||
'<<indent-region>>': ['<Control-bracketright>'],
|
||||
'<<dedent-region>>': ['<Control-bracketleft>'],
|
||||
'<<comment-region>>': ['<Alt-Key-3>'],
|
||||
'<<uncomment-region>>': ['<Alt-Key-4>'],
|
||||
'<<tabify-region>>': ['<Alt-Key-5>'],
|
||||
'<<untabify-region>>': ['<Alt-Key-6>'],
|
||||
'<<toggle-tabs>>': ['<Alt-Key-t>'],
|
||||
'<<change-indentwidth>>': ['<Alt-Key-u>'],
|
||||
}
|
||||
|
||||
unix_keydefs = {
|
||||
'<<indent-region>>': ['<Alt-bracketright>',
|
||||
'<Meta-bracketright>',
|
||||
'<Control-bracketright>'],
|
||||
'<<dedent-region>>': ['<Alt-bracketleft>',
|
||||
'<Meta-bracketleft>',
|
||||
'<Control-bracketleft>'],
|
||||
'<<comment-region>>': ['<Alt-Key-3>', '<Meta-Key-3>'],
|
||||
'<<uncomment-region>>': ['<Alt-Key-4>', '<Meta-Key-4>'],
|
||||
'<<tabify-region>>': ['<Alt-Key-5>', '<Meta-Key-5>'],
|
||||
'<<untabify-region>>': ['<Alt-Key-6>', '<Meta-Key-6>'],
|
||||
'<<toggle-tabs>>': ['<Alt-Key-t>'],
|
||||
'<<change-indentwidth>>': ['<Alt-Key-u>'],
|
||||
}
|
||||
|
||||
# usetabs true -> literal tab characters are used by indent and
|
||||
# dedent cmds, possibly mixed with spaces if
|
||||
# indentwidth is not a multiple of tabwidth
|
||||
# false -> tab characters are converted to spaces by indent
|
||||
# and dedent cmds, and ditto TAB keystrokes
|
||||
# indentwidth is the number of characters per logical indent level.
|
||||
# tabwidth is the display width of a literal tab character.
|
||||
# CAUTION: telling Tk to use anything other than its default
|
||||
# tab setting causes it to use an entirely different tabbing algorithm,
|
||||
# treating tab stops as fixed distances from the left margin.
|
||||
# Nobody expects this, so for now tabwidth should never be changed.
|
||||
usetabs = 1
|
||||
indentwidth = 4
|
||||
tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
|
||||
|
||||
# If context_use_ps1 is true, parsing searches back for a ps1 line;
|
||||
# else searches for a popular (if, def, ...) Python stmt.
|
||||
context_use_ps1 = 0
|
||||
|
||||
# When searching backwards for a reliable place to begin parsing,
|
||||
# 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.
|
||||
num_context_lines = 50, 500, 5000000
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
self.text = editwin.text
|
||||
|
||||
def config(self, **options):
|
||||
for key, value in options.items():
|
||||
if key == 'usetabs':
|
||||
self.usetabs = value
|
||||
elif key == 'indentwidth':
|
||||
self.indentwidth = value
|
||||
elif key == 'tabwidth':
|
||||
self.tabwidth = value
|
||||
elif key == 'context_use_ps1':
|
||||
self.context_use_ps1 = value
|
||||
else:
|
||||
raise KeyError("bad option name: %s" % repr(key))
|
||||
|
||||
# If ispythonsource and guess are true, guess a good value for
|
||||
# indentwidth based on file content (if possible), and if
|
||||
# indentwidth != tabwidth set usetabs false.
|
||||
# In any case, adjust the Text widget's view of what a tab
|
||||
# character means.
|
||||
|
||||
def set_indentation_params(self, ispythonsource, guess=1):
|
||||
if guess and ispythonsource:
|
||||
i = self.guess_indent()
|
||||
if 2 <= i <= 8:
|
||||
self.indentwidth = i
|
||||
if self.indentwidth != self.tabwidth:
|
||||
self.usetabs = 0
|
||||
|
||||
self.editwin.set_tabwidth(self.tabwidth)
|
||||
|
||||
def smart_backspace_event(self, event):
|
||||
text = self.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
if first and last:
|
||||
text.delete(first, last)
|
||||
text.mark_set("insert", first)
|
||||
return "break"
|
||||
# Delete whitespace left, until hitting a real char or closest
|
||||
# preceding virtual tab stop.
|
||||
chars = text.get("insert linestart", "insert")
|
||||
if chars == '':
|
||||
if text.compare("insert", ">", "1.0"):
|
||||
# easy: delete preceding newline
|
||||
text.delete("insert-1c")
|
||||
else:
|
||||
text.bell() # at start of buffer
|
||||
return "break"
|
||||
if chars[-1] not in " \t":
|
||||
# easy: delete preceding real char
|
||||
text.delete("insert-1c")
|
||||
return "break"
|
||||
# Ick. It may require *inserting* spaces if we back up over a
|
||||
# tab character! This is written to be clear, not fast.
|
||||
have = len(chars.expandtabs(self.tabwidth))
|
||||
assert have > 0
|
||||
want = int((have - 1) / self.indentwidth) * self.indentwidth
|
||||
ncharsdeleted = 0
|
||||
while 1:
|
||||
chars = chars[:-1]
|
||||
ncharsdeleted = ncharsdeleted + 1
|
||||
have = len(chars.expandtabs(self.tabwidth))
|
||||
if have <= want or chars[-1] not in " \t":
|
||||
break
|
||||
text.undo_block_start()
|
||||
text.delete("insert-%dc" % ncharsdeleted, "insert")
|
||||
if have < want:
|
||||
text.insert("insert", ' ' * (want - have))
|
||||
text.undo_block_stop()
|
||||
return "break"
|
||||
|
||||
def smart_indent_event(self, event):
|
||||
# if intraline selection:
|
||||
# delete it
|
||||
# elif multiline selection:
|
||||
# do indent-region & return
|
||||
# indent one level
|
||||
text = self.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
text.undo_block_start()
|
||||
try:
|
||||
if first and last:
|
||||
if index2line(first) != index2line(last):
|
||||
return self.indent_region_event(event)
|
||||
text.delete(first, last)
|
||||
text.mark_set("insert", first)
|
||||
prefix = text.get("insert linestart", "insert")
|
||||
raw, effective = classifyws(prefix, self.tabwidth)
|
||||
if raw == len(prefix):
|
||||
# only whitespace to the left
|
||||
self.reindent_to(effective + self.indentwidth)
|
||||
else:
|
||||
if self.usetabs:
|
||||
pad = '\t'
|
||||
else:
|
||||
effective = len(prefix.expandtabs(self.tabwidth))
|
||||
n = self.indentwidth
|
||||
pad = ' ' * (n - effective % n)
|
||||
text.insert("insert", pad)
|
||||
text.see("insert")
|
||||
return "break"
|
||||
finally:
|
||||
text.undo_block_stop()
|
||||
|
||||
def newline_and_indent_event(self, event):
|
||||
text = self.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
text.undo_block_start()
|
||||
try:
|
||||
if first and last:
|
||||
text.delete(first, last)
|
||||
text.mark_set("insert", first)
|
||||
line = text.get("insert linestart", "insert")
|
||||
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 and strip space from current line
|
||||
text.delete("insert - %d chars" % i, "insert")
|
||||
text.insert("insert linestart", '\n')
|
||||
return "break"
|
||||
indent = line[:i]
|
||||
# strip whitespace before insert point
|
||||
i = 0
|
||||
while line and line[-1] in " \t":
|
||||
line = line[:-1]
|
||||
i = i+1
|
||||
if i:
|
||||
text.delete("insert - %d chars" % i, "insert")
|
||||
# strip whitespace after insert point
|
||||
while text.get("insert") in " \t":
|
||||
text.delete("insert")
|
||||
# start new line
|
||||
text.insert("insert", '\n')
|
||||
|
||||
# adjust indentation for continuations and block
|
||||
# open/close first need to find the last stmt
|
||||
lno = index2line(text.index('insert'))
|
||||
y = PyParse.Parser(self.indentwidth, self.tabwidth)
|
||||
for context in self.num_context_lines:
|
||||
startat = max(lno - context, 1)
|
||||
startatindex = repr(startat) + ".0"
|
||||
rawtext = text.get(startatindex, "insert")
|
||||
y.set_str(rawtext)
|
||||
bod = y.find_good_parse_start(
|
||||
self.context_use_ps1,
|
||||
self._build_char_in_string_func(startatindex))
|
||||
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:
|
||||
assert 0, "bogus continuation type " + repr(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)
|
||||
return "break"
|
||||
finally:
|
||||
text.see("insert")
|
||||
text.undo_block_stop()
|
||||
|
||||
auto_indent = newline_and_indent_event
|
||||
|
||||
# Our editwin provides a is_char_in_string function that works
|
||||
# with a Tk text index, but PyParse only knows about offsets into
|
||||
# a string. This builds a function for PyParse that accepts an
|
||||
# offset.
|
||||
|
||||
def _build_char_in_string_func(self, startindex):
|
||||
def inner(offset, _startindex=startindex,
|
||||
_icis=self.editwin.is_char_in_string):
|
||||
return _icis(_startindex + "+%dc" % offset)
|
||||
return inner
|
||||
|
||||
def indent_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if line:
|
||||
raw, effective = classifyws(line, self.tabwidth)
|
||||
effective = effective + self.indentwidth
|
||||
lines[pos] = self._make_blanks(effective) + line[raw:]
|
||||
self.set_region(head, tail, chars, lines)
|
||||
return "break"
|
||||
|
||||
def dedent_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if line:
|
||||
raw, effective = classifyws(line, self.tabwidth)
|
||||
effective = max(effective - self.indentwidth, 0)
|
||||
lines[pos] = self._make_blanks(effective) + line[raw:]
|
||||
self.set_region(head, tail, chars, lines)
|
||||
return "break"
|
||||
|
||||
def comment_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines) - 1):
|
||||
line = lines[pos]
|
||||
lines[pos] = '##' + line
|
||||
self.set_region(head, tail, chars, lines)
|
||||
|
||||
def uncomment_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if not line:
|
||||
continue
|
||||
if line[:2] == '##':
|
||||
line = line[2:]
|
||||
elif line[:1] == '#':
|
||||
line = line[1:]
|
||||
lines[pos] = line
|
||||
self.set_region(head, tail, chars, lines)
|
||||
|
||||
def tabify_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
tabwidth = self._asktabwidth()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if line:
|
||||
raw, effective = classifyws(line, tabwidth)
|
||||
ntabs, nspaces = divmod(effective, tabwidth)
|
||||
lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
|
||||
self.set_region(head, tail, chars, lines)
|
||||
|
||||
def untabify_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
tabwidth = self._asktabwidth()
|
||||
for pos in range(len(lines)):
|
||||
lines[pos] = lines[pos].expandtabs(tabwidth)
|
||||
self.set_region(head, tail, chars, lines)
|
||||
|
||||
def toggle_tabs_event(self, event):
|
||||
if self.editwin.askyesno(
|
||||
"Toggle tabs",
|
||||
"Turn tabs " + ("on", "off")[self.usetabs] + "?",
|
||||
parent=self.text):
|
||||
self.usetabs = not self.usetabs
|
||||
return "break"
|
||||
|
||||
# XXX this isn't bound to anything -- see class tabwidth comments
|
||||
def change_tabwidth_event(self, event):
|
||||
new = self._asktabwidth()
|
||||
if new != self.tabwidth:
|
||||
self.tabwidth = new
|
||||
self.set_indentation_params(0, guess=0)
|
||||
return "break"
|
||||
|
||||
def change_indentwidth_event(self, event):
|
||||
new = self.editwin.askinteger(
|
||||
"Indent width",
|
||||
"New indent width (1-16)",
|
||||
parent=self.text,
|
||||
initialvalue=self.indentwidth,
|
||||
minvalue=1,
|
||||
maxvalue=16)
|
||||
if new and new != self.indentwidth:
|
||||
self.indentwidth = new
|
||||
return "break"
|
||||
|
||||
def get_region(self):
|
||||
text = self.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
if first and last:
|
||||
head = text.index(first + " linestart")
|
||||
tail = text.index(last + "-1c lineend +1c")
|
||||
else:
|
||||
head = text.index("insert linestart")
|
||||
tail = text.index("insert lineend +1c")
|
||||
chars = text.get(head, tail)
|
||||
lines = chars.split("\n")
|
||||
return head, tail, chars, lines
|
||||
|
||||
def set_region(self, head, tail, chars, lines):
|
||||
text = self.text
|
||||
newchars = "\n".join(lines)
|
||||
if newchars == chars:
|
||||
text.bell()
|
||||
return
|
||||
text.tag_remove("sel", "1.0", "end")
|
||||
text.mark_set("insert", head)
|
||||
text.undo_block_start()
|
||||
text.delete(head, tail)
|
||||
text.insert(head, newchars)
|
||||
text.undo_block_stop()
|
||||
text.tag_add("sel", head, "insert")
|
||||
|
||||
# Make string that displays as n leading blanks.
|
||||
|
||||
def _make_blanks(self, n):
|
||||
if self.usetabs:
|
||||
ntabs, nspaces = divmod(n, self.tabwidth)
|
||||
return '\t' * ntabs + ' ' * nspaces
|
||||
else:
|
||||
return ' ' * n
|
||||
|
||||
# Delete from beginning of line to insert point, then reinsert
|
||||
# column logical (meaning use tabs if appropriate) spaces.
|
||||
|
||||
def reindent_to(self, column):
|
||||
text = self.text
|
||||
text.undo_block_start()
|
||||
if text.compare("insert linestart", "!=", "insert"):
|
||||
text.delete("insert linestart", "insert")
|
||||
if column:
|
||||
text.insert("insert", self._make_blanks(column))
|
||||
text.undo_block_stop()
|
||||
|
||||
def _asktabwidth(self):
|
||||
return self.editwin.askinteger(
|
||||
"Tab width",
|
||||
"Spaces per tab?",
|
||||
parent=self.text,
|
||||
initialvalue=self.tabwidth,
|
||||
minvalue=1,
|
||||
maxvalue=16) or self.tabwidth
|
||||
|
||||
# Guess indentwidth from text content.
|
||||
# Return guessed indentwidth. This should not be believed unless
|
||||
# it's in a reasonable range (e.g., it will be 0 if no indented
|
||||
# blocks are found).
|
||||
|
||||
def guess_indent(self):
|
||||
opener, indented = IndentSearcher(self.text, self.tabwidth).run()
|
||||
if opener and indented:
|
||||
raw, indentsmall = classifyws(opener, self.tabwidth)
|
||||
raw, indentlarge = classifyws(indented, self.tabwidth)
|
||||
else:
|
||||
indentsmall = indentlarge = 0
|
||||
return indentlarge - indentsmall
|
||||
|
||||
# "line.col" -> line, as an int
|
||||
def index2line(index):
|
||||
return int(float(index))
|
||||
|
||||
# Look at the leading whitespace in s.
|
||||
# Return pair (# of leading ws characters,
|
||||
# effective # of leading blanks after expanding
|
||||
# tabs to width tabwidth)
|
||||
|
||||
def classifyws(s, tabwidth):
|
||||
raw = effective = 0
|
||||
for ch in s:
|
||||
if ch == ' ':
|
||||
raw = raw + 1
|
||||
effective = effective + 1
|
||||
elif ch == '\t':
|
||||
raw = raw + 1
|
||||
effective = (effective // tabwidth + 1) * tabwidth
|
||||
else:
|
||||
break
|
||||
return raw, effective
|
||||
|
||||
class IndentSearcher:
|
||||
|
||||
# .run() chews over the Text widget, looking for a block opener
|
||||
# and the stmt following it. Returns a pair,
|
||||
# (line containing block opener, line containing stmt)
|
||||
# Either or both may be None.
|
||||
|
||||
def __init__(self, text, tabwidth):
|
||||
self.text = text
|
||||
self.tabwidth = tabwidth
|
||||
self.i = self.finished = 0
|
||||
self.blkopenline = self.indentedline = None
|
||||
|
||||
def readline(self):
|
||||
if self.finished:
|
||||
val = ""
|
||||
else:
|
||||
i = self.i = self.i + 1
|
||||
mark = repr(i) + ".0"
|
||||
if self.text.compare(mark, ">=", "end"):
|
||||
val = ""
|
||||
else:
|
||||
val = self.text.get(mark, mark + " lineend+1c")
|
||||
# hrm - not sure this is correct in py3k - the source code may have
|
||||
# an encoding declared, but the data will *always* be in
|
||||
# default_scintilla_encoding - so if anyone looks at the encoding decl
|
||||
# in the source they will be wrong. I think. Maybe. Or something...
|
||||
return val.encode(default_scintilla_encoding)
|
||||
|
||||
def run(self):
|
||||
OPENERS=('class', 'def', 'for', 'if', 'try', 'while')
|
||||
INDENT=tokenize.INDENT
|
||||
NAME=tokenize.NAME
|
||||
|
||||
save_tabsize = tokenize.tabsize
|
||||
tokenize.tabsize = self.tabwidth
|
||||
try:
|
||||
try:
|
||||
for (typ, token, start, end, line) in token_generator(self.readline):
|
||||
if typ == NAME and token in OPENERS:
|
||||
self.blkopenline = line
|
||||
elif typ == INDENT and self.blkopenline:
|
||||
self.indentedline = line
|
||||
break
|
||||
|
||||
except (tokenize.TokenError, IndentationError):
|
||||
# since we cut off the tokenizer early, we can trigger
|
||||
# spurious errors
|
||||
pass
|
||||
finally:
|
||||
tokenize.tabsize = save_tabsize
|
||||
return self.blkopenline, self.indentedline
|
188
venv/Lib/site-packages/pythonwin/pywin/idle/CallTips.py
Normal file
188
venv/Lib/site-packages/pythonwin/pywin/idle/CallTips.py
Normal file
|
@ -0,0 +1,188 @@
|
|||
# CallTips.py - An IDLE extension that provides "Call Tips" - ie, a floating window that
|
||||
# displays parameter information as you open parens.
|
||||
|
||||
import string
|
||||
import sys
|
||||
import inspect
|
||||
import traceback
|
||||
|
||||
class CallTips:
|
||||
|
||||
menudefs = [
|
||||
]
|
||||
|
||||
keydefs = {
|
||||
'<<paren-open>>': ['<Key-parenleft>'],
|
||||
'<<paren-close>>': ['<Key-parenright>'],
|
||||
'<<check-calltip-cancel>>': ['<KeyRelease>'],
|
||||
'<<calltip-cancel>>': ['<ButtonPress>', '<Key-Escape>'],
|
||||
}
|
||||
|
||||
windows_keydefs = {
|
||||
}
|
||||
|
||||
unix_keydefs = {
|
||||
}
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
self.text = editwin.text
|
||||
self.calltip = None
|
||||
if hasattr(self.text, "make_calltip_window"):
|
||||
self._make_calltip_window = self.text.make_calltip_window
|
||||
else:
|
||||
self._make_calltip_window = self._make_tk_calltip_window
|
||||
|
||||
def close(self):
|
||||
self._make_calltip_window = None
|
||||
|
||||
# Makes a Tk based calltip window. Used by IDLE, but not Pythonwin.
|
||||
# See __init__ above for how this is used.
|
||||
def _make_tk_calltip_window(self):
|
||||
import CallTipWindow
|
||||
return CallTipWindow.CallTip(self.text)
|
||||
|
||||
def _remove_calltip_window(self):
|
||||
if self.calltip:
|
||||
self.calltip.hidetip()
|
||||
self.calltip = None
|
||||
|
||||
def paren_open_event(self, event):
|
||||
self._remove_calltip_window()
|
||||
arg_text = get_arg_text(self.get_object_at_cursor())
|
||||
if arg_text:
|
||||
self.calltip_start = self.text.index("insert")
|
||||
self.calltip = self._make_calltip_window()
|
||||
self.calltip.showtip(arg_text)
|
||||
return "" #so the event is handled normally.
|
||||
|
||||
def paren_close_event(self, event):
|
||||
# Now just hides, but later we should check if other
|
||||
# paren'd expressions remain open.
|
||||
self._remove_calltip_window()
|
||||
return "" #so the event is handled normally.
|
||||
|
||||
def check_calltip_cancel_event(self, event):
|
||||
if self.calltip:
|
||||
# If we have moved before the start of the calltip,
|
||||
# or off the calltip line, then cancel the tip.
|
||||
# (Later need to be smarter about multi-line, etc)
|
||||
if self.text.compare("insert", "<=", self.calltip_start) or \
|
||||
self.text.compare("insert", ">", self.calltip_start + " lineend"):
|
||||
self._remove_calltip_window()
|
||||
return "" #so the event is handled normally.
|
||||
|
||||
def calltip_cancel_event(self, event):
|
||||
self._remove_calltip_window()
|
||||
return "" #so the event is handled normally.
|
||||
|
||||
def get_object_at_cursor(self,
|
||||
wordchars="._" + string.ascii_uppercase + string.ascii_lowercase + string.digits):
|
||||
# XXX - This needs to be moved to a better place
|
||||
# so the "." attribute lookup code can also use it.
|
||||
text = self.text
|
||||
chars = text.get("insert linestart", "insert")
|
||||
i = len(chars)
|
||||
while i and chars[i-1] in wordchars:
|
||||
i = i-1
|
||||
word = chars[i:]
|
||||
if word:
|
||||
# How is this for a hack!
|
||||
import sys, __main__
|
||||
namespace = sys.modules.copy()
|
||||
namespace.update(__main__.__dict__)
|
||||
try:
|
||||
return eval(word, namespace)
|
||||
except:
|
||||
pass
|
||||
return None # Can't find an object.
|
||||
|
||||
def _find_constructor(class_ob):
|
||||
# Given a class object, return a function object used for the
|
||||
# constructor (ie, __init__() ) or None if we can't find one.
|
||||
try:
|
||||
if sys.version_info < (3,):
|
||||
return class_ob.__init__.im_func
|
||||
else:
|
||||
return class_ob.__init__.__func__
|
||||
except AttributeError:
|
||||
for base in class_ob.__bases__:
|
||||
rc = _find_constructor(base)
|
||||
if rc is not None: return rc
|
||||
return None
|
||||
|
||||
def get_arg_text(ob):
|
||||
# Get a string describing the arguments for the given object.
|
||||
argText = ""
|
||||
if ob is not None:
|
||||
argOffset = 0
|
||||
if inspect.isclass(ob):
|
||||
# Look for the highest __init__ in the class chain.
|
||||
fob = _find_constructor(ob)
|
||||
if fob is None:
|
||||
fob = lambda: None
|
||||
else:
|
||||
fob = ob
|
||||
if inspect.isfunction(fob) or inspect.ismethod(fob):
|
||||
try:
|
||||
# py3k has a 'getfullargspec' which can handle py3k specific things.
|
||||
arg_getter = getattr(inspect, "getfullargspec", inspect.getargspec)
|
||||
argText = inspect.formatargspec(*arg_getter(fob))
|
||||
except:
|
||||
print("Failed to format the args")
|
||||
traceback.print_exc()
|
||||
# See if we can use the docstring
|
||||
if hasattr(ob, "__doc__"):
|
||||
doc=ob.__doc__
|
||||
try:
|
||||
doc = doc.strip()
|
||||
pos = doc.find("\n")
|
||||
except AttributeError:
|
||||
## New style classes may have __doc__ slot without actually
|
||||
## having a string assigned to it
|
||||
pass
|
||||
else:
|
||||
if pos<0 or pos>70: pos=70
|
||||
if argText: argText = argText + "\n"
|
||||
argText = argText + doc[:pos]
|
||||
|
||||
return argText
|
||||
|
||||
#################################################
|
||||
#
|
||||
# Test code
|
||||
#
|
||||
if __name__=='__main__':
|
||||
|
||||
def t1(): "()"
|
||||
def t2(a, b=None): "(a, b=None)"
|
||||
def t3(a, *args): "(a, *args)"
|
||||
def t4(*args): "(*args)"
|
||||
def t5(a, *args): "(a, *args)"
|
||||
def t6(a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
|
||||
|
||||
class TC:
|
||||
"(self, a=None, *b)"
|
||||
def __init__(self, a=None, *b): "(self, a=None, *b)"
|
||||
def t1(self): "(self)"
|
||||
def t2(self, a, b=None): "(self, a, b=None)"
|
||||
def t3(self, a, *args): "(self, a, *args)"
|
||||
def t4(self, *args): "(self, *args)"
|
||||
def t5(self, a, *args): "(self, a, *args)"
|
||||
def t6(self, a, b=None, *args, **kw): "(self, a, b=None, *args, **kw)"
|
||||
|
||||
def test( tests ):
|
||||
failed=[]
|
||||
for t in tests:
|
||||
expected = t.__doc__ + "\n" + t.__doc__
|
||||
if get_arg_text(t) != expected:
|
||||
failed.append(t)
|
||||
print("%s - expected %s, but got %s" % (t, repr(expected), repr(get_arg_text(t))))
|
||||
print("%d of %d tests failed" % (len(failed), len(tests)))
|
||||
|
||||
tc = TC()
|
||||
tests = t1, t2, t3, t4, t5, t6, \
|
||||
TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6
|
||||
|
||||
test(tests)
|
||||
|
155
venv/Lib/site-packages/pythonwin/pywin/idle/FormatParagraph.py
Normal file
155
venv/Lib/site-packages/pythonwin/pywin/idle/FormatParagraph.py
Normal file
|
@ -0,0 +1,155 @@
|
|||
# Extension to format a paragraph
|
||||
|
||||
# Does basic, standard text formatting, and also understands Python
|
||||
# comment blocks. Thus, for editing Python source code, this
|
||||
# extension is really only suitable for reformatting these comment
|
||||
# blocks or triple-quoted strings.
|
||||
|
||||
# Known problems with comment reformatting:
|
||||
# * If there is a selection marked, and the first line of the
|
||||
# selection is not complete, the block will probably not be detected
|
||||
# as comments, and will have the normal "text formatting" rules
|
||||
# applied.
|
||||
# * If a comment block has leading whitespace that mixes tabs and
|
||||
# spaces, they will not be considered part of the same block.
|
||||
# * Fancy comments, like this bulleted list, arent handled :-)
|
||||
|
||||
import string
|
||||
import re
|
||||
|
||||
class FormatParagraph:
|
||||
|
||||
menudefs = [
|
||||
('edit', [
|
||||
('Format Paragraph', '<<format-paragraph>>'),
|
||||
])
|
||||
]
|
||||
|
||||
keydefs = {
|
||||
'<<format-paragraph>>': ['<Alt-q>'],
|
||||
}
|
||||
|
||||
unix_keydefs = {
|
||||
'<<format-paragraph>>': ['<Meta-q>'],
|
||||
}
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
|
||||
def close(self):
|
||||
self.editwin = None
|
||||
|
||||
def format_paragraph_event(self, event):
|
||||
text = self.editwin.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
if first and last:
|
||||
data = text.get(first, last)
|
||||
comment_header = ''
|
||||
else:
|
||||
first, last, comment_header, data = \
|
||||
find_paragraph(text, text.index("insert"))
|
||||
if comment_header:
|
||||
# Reformat the comment lines - convert to text sans header.
|
||||
lines = data.split("\n")
|
||||
lines = map(lambda st, l=len(comment_header): st[l:], lines)
|
||||
data = "\n".join(lines)
|
||||
# Reformat to 70 chars or a 20 char width, whichever is greater.
|
||||
format_width = max(70-len(comment_header), 20)
|
||||
newdata = reformat_paragraph(data, format_width)
|
||||
# re-split and re-insert the comment header.
|
||||
newdata = newdata.split("\n")
|
||||
# If the block ends in a \n, we dont want the comment
|
||||
# prefix inserted after it. (Im not sure it makes sense to
|
||||
# reformat a comment block that isnt made of complete
|
||||
# lines, but whatever!) Can't think of a clean soltution,
|
||||
# so we hack away
|
||||
block_suffix = ""
|
||||
if not newdata[-1]:
|
||||
block_suffix = "\n"
|
||||
newdata = newdata[:-1]
|
||||
builder = lambda item, prefix=comment_header: prefix+item
|
||||
newdata = '\n'.join([builder(d) for d in newdata]) + block_suffix
|
||||
else:
|
||||
# Just a normal text format
|
||||
newdata = reformat_paragraph(data)
|
||||
text.tag_remove("sel", "1.0", "end")
|
||||
if newdata != data:
|
||||
text.mark_set("insert", first)
|
||||
text.undo_block_start()
|
||||
text.delete(first, last)
|
||||
text.insert(first, newdata)
|
||||
text.undo_block_stop()
|
||||
else:
|
||||
text.mark_set("insert", last)
|
||||
text.see("insert")
|
||||
|
||||
def find_paragraph(text, mark):
|
||||
lineno, col = list(map(int, mark.split(".")))
|
||||
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
|
||||
while text.compare("%d.0" % lineno, "<", "end") and is_all_white(line):
|
||||
lineno = lineno + 1
|
||||
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
|
||||
first_lineno = lineno
|
||||
comment_header = get_comment_header(line)
|
||||
comment_header_len = len(comment_header)
|
||||
while get_comment_header(line)==comment_header and \
|
||||
not is_all_white(line[comment_header_len:]):
|
||||
lineno = lineno + 1
|
||||
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
|
||||
last = "%d.0" % lineno
|
||||
# Search back to beginning of paragraph
|
||||
lineno = first_lineno - 1
|
||||
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
|
||||
while lineno > 0 and \
|
||||
get_comment_header(line)==comment_header and \
|
||||
not is_all_white(line[comment_header_len:]):
|
||||
lineno = lineno - 1
|
||||
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
|
||||
first = "%d.0" % (lineno+1)
|
||||
return first, last, comment_header, text.get(first, last)
|
||||
|
||||
def reformat_paragraph(data, limit=70):
|
||||
lines = data.split("\n")
|
||||
i = 0
|
||||
n = len(lines)
|
||||
while i < n and is_all_white(lines[i]):
|
||||
i = i+1
|
||||
if i >= n:
|
||||
return data
|
||||
indent1 = get_indent(lines[i])
|
||||
if i+1 < n and not is_all_white(lines[i+1]):
|
||||
indent2 = get_indent(lines[i+1])
|
||||
else:
|
||||
indent2 = indent1
|
||||
new = lines[:i]
|
||||
partial = indent1
|
||||
while i < n and not is_all_white(lines[i]):
|
||||
# XXX Should take double space after period (etc.) into account
|
||||
words = re.split("(\s+)", lines[i])
|
||||
for j in range(0, len(words), 2):
|
||||
word = words[j]
|
||||
if not word:
|
||||
continue # Can happen when line ends in whitespace
|
||||
if len((partial + word).expandtabs()) > limit and \
|
||||
partial != indent1:
|
||||
new.append(partial.rstrip())
|
||||
partial = indent2
|
||||
partial = partial + word + " "
|
||||
if j+1 < len(words) and words[j+1] != " ":
|
||||
partial = partial + " "
|
||||
i = i+1
|
||||
new.append(partial.rstrip())
|
||||
# XXX Should reformat remaining paragraphs as well
|
||||
new.extend(lines[i:])
|
||||
return "\n".join(new)
|
||||
|
||||
def is_all_white(line):
|
||||
return re.match(r"^\s*$", line) is not None
|
||||
|
||||
def get_indent(line):
|
||||
return re.match(r"^(\s*)", line).group()
|
||||
|
||||
def get_comment_header(line):
|
||||
m = re.match(r"^(\s*#*)", line)
|
||||
if m is None: return ""
|
||||
return m.group(1)
|
89
venv/Lib/site-packages/pythonwin/pywin/idle/IdleHistory.py
Normal file
89
venv/Lib/site-packages/pythonwin/pywin/idle/IdleHistory.py
Normal file
|
@ -0,0 +1,89 @@
|
|||
import string
|
||||
|
||||
class History:
|
||||
|
||||
def __init__(self, text, output_sep = "\n"):
|
||||
self.text = text
|
||||
self.history = []
|
||||
self.history_prefix = None
|
||||
self.history_pointer = None
|
||||
self.output_sep = output_sep
|
||||
text.bind("<<history-previous>>", self.history_prev)
|
||||
text.bind("<<history-next>>", self.history_next)
|
||||
|
||||
def history_next(self, event):
|
||||
self.history_do(0)
|
||||
return "break"
|
||||
|
||||
def history_prev(self, event):
|
||||
self.history_do(1)
|
||||
return "break"
|
||||
|
||||
def _get_source(self, start, end):
|
||||
# Get source code from start index to end index. Lines in the
|
||||
# text control may be separated by sys.ps2 .
|
||||
lines = self.text.get(start, end).split(self.output_sep)
|
||||
return "\n".join(lines)
|
||||
|
||||
def _put_source(self, where, source):
|
||||
output = self.output_sep.join(source.split("\n"))
|
||||
self.text.insert(where, output)
|
||||
|
||||
def history_do(self, reverse):
|
||||
nhist = len(self.history)
|
||||
pointer = self.history_pointer
|
||||
prefix = self.history_prefix
|
||||
if pointer is not None and prefix is not None:
|
||||
if self.text.compare("insert", "!=", "end-1c") or \
|
||||
self._get_source("iomark", "end-1c") != self.history[pointer]:
|
||||
pointer = prefix = None
|
||||
if pointer is None or prefix is None:
|
||||
prefix = self._get_source("iomark", "end-1c")
|
||||
if reverse:
|
||||
pointer = nhist
|
||||
else:
|
||||
pointer = -1
|
||||
nprefix = len(prefix)
|
||||
while 1:
|
||||
if reverse:
|
||||
pointer = pointer - 1
|
||||
else:
|
||||
pointer = pointer + 1
|
||||
if pointer < 0 or pointer >= nhist:
|
||||
self.text.bell()
|
||||
if self._get_source("iomark", "end-1c") != prefix:
|
||||
self.text.delete("iomark", "end-1c")
|
||||
self._put_source("iomark", prefix)
|
||||
pointer = prefix = None
|
||||
break
|
||||
item = self.history[pointer]
|
||||
if item[:nprefix] == prefix and len(item) > nprefix:
|
||||
self.text.delete("iomark", "end-1c")
|
||||
self._put_source("iomark", item)
|
||||
break
|
||||
self.text.mark_set("insert", "end-1c")
|
||||
self.text.see("insert")
|
||||
self.text.tag_remove("sel", "1.0", "end")
|
||||
self.history_pointer = pointer
|
||||
self.history_prefix = prefix
|
||||
|
||||
def history_store(self, source):
|
||||
source = source.strip()
|
||||
if len(source) > 2:
|
||||
# avoid duplicates
|
||||
try:
|
||||
self.history.remove(source)
|
||||
except ValueError:
|
||||
pass
|
||||
self.history.append(source)
|
||||
self.history_pointer = None
|
||||
self.history_prefix = None
|
||||
|
||||
def recall(self, s):
|
||||
s = s.strip()
|
||||
self.text.tag_remove("sel", "1.0", "end")
|
||||
self.text.delete("iomark", "end-1c")
|
||||
self.text.mark_set("insert", "end-1c")
|
||||
self.text.insert("insert", s)
|
||||
self.text.see("insert")
|
||||
|
568
venv/Lib/site-packages/pythonwin/pywin/idle/PyParse.py
Normal file
568
venv/Lib/site-packages/pythonwin/pywin/idle/PyParse.py
Normal file
|
@ -0,0 +1,568 @@
|
|||
import string
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Reason last stmt is continued (or C_NONE if it's not).
|
||||
C_NONE, C_BACKSLASH, C_STRING, C_BRACKET = list(range(4))
|
||||
|
||||
if 0: # for throwaway debugging output
|
||||
def dump(*stuff):
|
||||
sys.__stdout__.write(" ".join(map(str, stuff)) + "\n")
|
||||
|
||||
# Find what looks like the start of a popular stmt.
|
||||
|
||||
_synchre = re.compile(r"""
|
||||
^
|
||||
[ \t]*
|
||||
(?: if
|
||||
| for
|
||||
| while
|
||||
| else
|
||||
| def
|
||||
| return
|
||||
| assert
|
||||
| break
|
||||
| class
|
||||
| continue
|
||||
| elif
|
||||
| try
|
||||
| except
|
||||
| raise
|
||||
| import
|
||||
)
|
||||
\b
|
||||
""", re.VERBOSE | re.MULTILINE).search
|
||||
|
||||
# Match blank line or non-indenting comment line.
|
||||
|
||||
_junkre = re.compile(r"""
|
||||
[ \t]*
|
||||
(?: \# \S .* )?
|
||||
\n
|
||||
""", re.VERBOSE).match
|
||||
|
||||
# Match any flavor of string; the terminating quote is optional
|
||||
# so that we're robust in the face of incomplete program text.
|
||||
|
||||
_match_stringre = re.compile(r"""
|
||||
\""" [^"\\]* (?:
|
||||
(?: \\. | "(?!"") )
|
||||
[^"\\]*
|
||||
)*
|
||||
(?: \""" )?
|
||||
|
||||
| " [^"\\\n]* (?: \\. [^"\\\n]* )* "?
|
||||
|
||||
| ''' [^'\\]* (?:
|
||||
(?: \\. | '(?!'') )
|
||||
[^'\\]*
|
||||
)*
|
||||
(?: ''' )?
|
||||
|
||||
| ' [^'\\\n]* (?: \\. [^'\\\n]* )* '?
|
||||
""", re.VERBOSE | re.DOTALL).match
|
||||
|
||||
# Match a line that starts with something interesting;
|
||||
# used to find the first item of a bracket structure.
|
||||
|
||||
_itemre = re.compile(r"""
|
||||
[ \t]*
|
||||
[^\s#\\] # if we match, m.end()-1 is the interesting char
|
||||
""", re.VERBOSE).match
|
||||
|
||||
# Match start of stmts that should be followed by a dedent.
|
||||
|
||||
_closere = re.compile(r"""
|
||||
\s*
|
||||
(?: return
|
||||
| break
|
||||
| continue
|
||||
| raise
|
||||
| pass
|
||||
)
|
||||
\b
|
||||
""", re.VERBOSE).match
|
||||
|
||||
# Chew up non-special chars as quickly as possible. If match is
|
||||
# successful, m.end() less 1 is the index of the last boring char
|
||||
# matched. If match is unsuccessful, the string starts with an
|
||||
# interesting char.
|
||||
|
||||
_chew_ordinaryre = re.compile(r"""
|
||||
[^[\](){}#'"\\]+
|
||||
""", re.VERBOSE).match
|
||||
|
||||
# Build translation table to map uninteresting chars to "x", open
|
||||
# brackets to "(", and close brackets to ")".
|
||||
|
||||
_tran = ['x'] * 256
|
||||
for ch in "({[":
|
||||
_tran[ord(ch)] = '('
|
||||
for ch in ")}]":
|
||||
_tran[ord(ch)] = ')'
|
||||
for ch in "\"'\\\n#":
|
||||
_tran[ord(ch)] = ch
|
||||
# We are called with unicode strings, and str.translate is one of the few
|
||||
# py2k functions which can't 'do the right thing' - so take care to ensure
|
||||
# _tran is full of unicode...
|
||||
_tran = ''.join(_tran)
|
||||
del ch
|
||||
|
||||
class Parser:
|
||||
|
||||
def __init__(self, indentwidth, tabwidth):
|
||||
self.indentwidth = indentwidth
|
||||
self.tabwidth = tabwidth
|
||||
|
||||
def set_str(self, str):
|
||||
assert len(str) == 0 or str[-1] == '\n', "Oops - have str %r" % (str,)
|
||||
self.str = str
|
||||
self.study_level = 0
|
||||
|
||||
# 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:
|
||||
# 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.
|
||||
#
|
||||
# Ack, hack: in the shell window this kills us, because there's
|
||||
# no way to tell the differences between output, >>> etc and
|
||||
# user input. Indeed, IDLE's first output line makes the rest
|
||||
# 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):
|
||||
str, pos = self.str, None
|
||||
if use_ps1:
|
||||
# shell window
|
||||
ps1 = '\n' + sys.ps1
|
||||
i = str.rfind(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:]
|
||||
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 = str.rfind(":\n", 0, limit)
|
||||
if i < 0:
|
||||
break
|
||||
i = str.rfind('\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
|
||||
# find_good_parse_start's result.
|
||||
|
||||
def set_lo(self, lo):
|
||||
assert lo == 0 or self.str[lo-1] == '\n'
|
||||
if lo > 0:
|
||||
self.str = self.str[lo:]
|
||||
|
||||
# As quickly as humanly possible <wink>, find the line numbers (0-
|
||||
# based) of the non-continuation lines.
|
||||
# Creates self.{goodlines, continuation}.
|
||||
|
||||
def _study1(self):
|
||||
if self.study_level >= 1:
|
||||
return
|
||||
self.study_level = 1
|
||||
|
||||
# Map all uninteresting characters to "x", all open brackets
|
||||
# to "(", all close brackets to ")", then collapse runs of
|
||||
# uninteresting characters. This can cut the number of chars
|
||||
# by a factor of 10-40, and so greatly speed the following loop.
|
||||
str = self.str
|
||||
str = str.translate(_tran)
|
||||
str = str.replace('xxxxxxxx', 'x')
|
||||
str = str.replace('xxxx', 'x')
|
||||
str = str.replace('xx', 'x')
|
||||
str = str.replace('xx', 'x')
|
||||
str = str.replace('\nx', '\n')
|
||||
# note that replacing x\n with \n would be incorrect, because
|
||||
# x may be preceded by a backslash
|
||||
|
||||
# March over the squashed version of the program, accumulating
|
||||
# the line numbers of non-continued stmts, and determining
|
||||
# whether & why the last stmt is a continuation.
|
||||
continuation = C_NONE
|
||||
level = lno = 0 # level is nesting level; lno is line number
|
||||
self.goodlines = goodlines = [0]
|
||||
push_good = goodlines.append
|
||||
i, n = 0, len(str)
|
||||
while i < n:
|
||||
ch = str[i]
|
||||
i = i+1
|
||||
|
||||
# cases are checked in decreasing order of frequency
|
||||
if ch == 'x':
|
||||
continue
|
||||
|
||||
if ch == '\n':
|
||||
lno = lno + 1
|
||||
if level == 0:
|
||||
push_good(lno)
|
||||
# else we're in an unclosed bracket structure
|
||||
continue
|
||||
|
||||
if ch == '(':
|
||||
level = level + 1
|
||||
continue
|
||||
|
||||
if ch == ')':
|
||||
if level:
|
||||
level = level - 1
|
||||
# else the program is invalid, but we can't complain
|
||||
continue
|
||||
|
||||
if ch == '"' or ch == "'":
|
||||
# consume the string
|
||||
quote = ch
|
||||
if str[i-1:i+2] == quote * 3:
|
||||
quote = quote * 3
|
||||
w = len(quote) - 1
|
||||
i = i+w
|
||||
while i < n:
|
||||
ch = str[i]
|
||||
i = i+1
|
||||
|
||||
if ch == 'x':
|
||||
continue
|
||||
|
||||
if str[i-1:i+w] == quote:
|
||||
i = i+w
|
||||
break
|
||||
|
||||
if ch == '\n':
|
||||
lno = lno + 1
|
||||
if w == 0:
|
||||
# unterminated single-quoted string
|
||||
if level == 0:
|
||||
push_good(lno)
|
||||
break
|
||||
continue
|
||||
|
||||
if ch == '\\':
|
||||
assert i < n
|
||||
if str[i] == '\n':
|
||||
lno = lno + 1
|
||||
i = i+1
|
||||
continue
|
||||
|
||||
# else comment char or paren inside string
|
||||
|
||||
else:
|
||||
# didn't break out of the loop, so we're still
|
||||
# inside a string
|
||||
continuation = C_STRING
|
||||
continue # with outer loop
|
||||
|
||||
if ch == '#':
|
||||
# consume the comment
|
||||
i = str.find('\n', i)
|
||||
assert i >= 0
|
||||
continue
|
||||
|
||||
assert ch == '\\'
|
||||
assert i < n
|
||||
if str[i] == '\n':
|
||||
lno = lno + 1
|
||||
if i+1 == n:
|
||||
continuation = C_BACKSLASH
|
||||
i = i+1
|
||||
|
||||
# The last stmt may be continued for all 3 reasons.
|
||||
# String continuation takes precedence over bracket
|
||||
# continuation, which beats backslash continuation.
|
||||
if continuation != C_STRING and level > 0:
|
||||
continuation = C_BRACKET
|
||||
self.continuation = continuation
|
||||
|
||||
# Push the final line number as a sentinel value, regardless of
|
||||
# whether it's continued.
|
||||
assert (continuation == C_NONE) == (goodlines[-1] == lno)
|
||||
if goodlines[-1] != lno:
|
||||
push_good(lno)
|
||||
|
||||
def get_continuation_type(self):
|
||||
self._study1()
|
||||
return self.continuation
|
||||
|
||||
# study1 was sufficient to determine the continuation status,
|
||||
# but doing more requires looking at every character. study2
|
||||
# does this for the last interesting statement in the block.
|
||||
# Creates:
|
||||
# self.stmt_start, stmt_end
|
||||
# slice indices of last interesting stmt
|
||||
# self.lastch
|
||||
# last non-whitespace character before optional trailing
|
||||
# comment
|
||||
# self.lastopenbracketpos
|
||||
# if continuation is C_BRACKET, index of last open bracket
|
||||
|
||||
def _study2(self):
|
||||
_ws=string.whitespace
|
||||
if self.study_level >= 2:
|
||||
return
|
||||
self._study1()
|
||||
self.study_level = 2
|
||||
|
||||
# Set p and q to slice indices of last interesting stmt.
|
||||
str, goodlines = self.str, self.goodlines
|
||||
i = len(goodlines) - 1
|
||||
p = len(str) # index of newest line
|
||||
while i:
|
||||
assert p
|
||||
# p is the index of the stmt at line number goodlines[i].
|
||||
# Move p back to the stmt at line number goodlines[i-1].
|
||||
q = p
|
||||
for nothing in range(goodlines[i-1], goodlines[i]):
|
||||
# tricky: sets p to 0 if no preceding newline
|
||||
p = str.rfind('\n', 0, p-1) + 1
|
||||
# The stmt str[p:q] isn't a continuation, but may be blank
|
||||
# or a non-indenting comment line.
|
||||
if _junkre(str, p):
|
||||
i = i-1
|
||||
else:
|
||||
break
|
||||
if i == 0:
|
||||
# nothing but junk!
|
||||
assert p == 0
|
||||
q = p
|
||||
self.stmt_start, self.stmt_end = p, q
|
||||
|
||||
# Analyze this stmt, to find the last open bracket (if any)
|
||||
# and last interesting character (if any).
|
||||
lastch = ""
|
||||
stack = [] # stack of open bracket indices
|
||||
push_stack = stack.append
|
||||
while p < q:
|
||||
# suck up all except ()[]{}'"#\\
|
||||
m = _chew_ordinaryre(str, p, q)
|
||||
if m:
|
||||
# we skipped at least one boring char
|
||||
newp = m.end()
|
||||
# back up over totally boring whitespace
|
||||
i = newp - 1 # index of last boring char
|
||||
while i >= p and str[i] in " \t\n":
|
||||
i = i-1
|
||||
if i >= p:
|
||||
lastch = str[i]
|
||||
p = newp
|
||||
if p >= q:
|
||||
break
|
||||
|
||||
ch = str[p]
|
||||
|
||||
if ch in "([{":
|
||||
push_stack(p)
|
||||
lastch = ch
|
||||
p = p+1
|
||||
continue
|
||||
|
||||
if ch in ")]}":
|
||||
if stack:
|
||||
del stack[-1]
|
||||
lastch = ch
|
||||
p = p+1
|
||||
continue
|
||||
|
||||
if ch == '"' or ch == "'":
|
||||
# consume string
|
||||
# Note that study1 did this with a Python loop, but
|
||||
# we use a regexp here; the reason is speed in both
|
||||
# cases; the string may be huge, but study1 pre-squashed
|
||||
# strings to a couple of characters per line. study1
|
||||
# also needed to keep track of newlines, and we don't
|
||||
# have to.
|
||||
lastch = ch
|
||||
p = _match_stringre(str, p, q).end()
|
||||
continue
|
||||
|
||||
if ch == '#':
|
||||
# consume comment and trailing newline
|
||||
p = str.find('\n', p, q) + 1
|
||||
assert p > 0
|
||||
continue
|
||||
|
||||
assert ch == '\\'
|
||||
p = p+1 # beyond backslash
|
||||
assert p < q
|
||||
if str[p] != '\n':
|
||||
# the program is invalid, but can't complain
|
||||
lastch = ch + str[p]
|
||||
p = p+1 # beyond escaped char
|
||||
|
||||
# end while p < q:
|
||||
|
||||
self.lastch = lastch
|
||||
if stack:
|
||||
self.lastopenbracketpos = stack[-1]
|
||||
|
||||
# Assuming continuation is C_BRACKET, return the number
|
||||
# of spaces the next line should be indented.
|
||||
|
||||
def compute_bracket_indent(self):
|
||||
self._study2()
|
||||
assert self.continuation == C_BRACKET
|
||||
j = self.lastopenbracketpos
|
||||
str = self.str
|
||||
n = len(str)
|
||||
origi = i = str.rfind('\n', 0, j) + 1
|
||||
j = j+1 # one beyond open bracket
|
||||
# find first list item; set i to start of its line
|
||||
while j < n:
|
||||
m = _itemre(str, j)
|
||||
if m:
|
||||
j = m.end() - 1 # index of first interesting char
|
||||
extra = 0
|
||||
break
|
||||
else:
|
||||
# this line is junk; advance to next line
|
||||
i = j = str.find('\n', j) + 1
|
||||
else:
|
||||
# nothing interesting follows the bracket;
|
||||
# reproduce the bracket line's indentation + a level
|
||||
j = i = origi
|
||||
while str[j] in " \t":
|
||||
j = j+1
|
||||
extra = self.indentwidth
|
||||
return len(str[i:j].expandtabs(self.tabwidth)) + extra
|
||||
|
||||
# Return number of physical lines in last stmt (whether or not
|
||||
# it's an interesting stmt! this is intended to be called when
|
||||
# continuation is C_BACKSLASH).
|
||||
|
||||
def get_num_lines_in_stmt(self):
|
||||
self._study1()
|
||||
goodlines = self.goodlines
|
||||
return goodlines[-1] - goodlines[-2]
|
||||
|
||||
# Assuming continuation is C_BACKSLASH, return the number of spaces
|
||||
# the next line should be indented. Also assuming the new line is
|
||||
# the first one following the initial line of the stmt.
|
||||
|
||||
def compute_backslash_indent(self):
|
||||
self._study2()
|
||||
assert self.continuation == C_BACKSLASH
|
||||
str = self.str
|
||||
i = self.stmt_start
|
||||
while str[i] in " \t":
|
||||
i = i+1
|
||||
startpos = i
|
||||
|
||||
# See whether the initial line starts an assignment stmt; i.e.,
|
||||
# look for an = operator
|
||||
endpos = str.find('\n', startpos) + 1
|
||||
found = level = 0
|
||||
while i < endpos:
|
||||
ch = str[i]
|
||||
if ch in "([{":
|
||||
level = level + 1
|
||||
i = i+1
|
||||
elif ch in ")]}":
|
||||
if level:
|
||||
level = level - 1
|
||||
i = i+1
|
||||
elif ch == '"' or ch == "'":
|
||||
i = _match_stringre(str, i, endpos).end()
|
||||
elif ch == '#':
|
||||
break
|
||||
elif level == 0 and ch == '=' and \
|
||||
(i == 0 or str[i-1] not in "=<>!") and \
|
||||
str[i+1] != '=':
|
||||
found = 1
|
||||
break
|
||||
else:
|
||||
i = i+1
|
||||
|
||||
if found:
|
||||
# found a legit =, but it may be the last interesting
|
||||
# thing on the line
|
||||
i = i+1 # move beyond the =
|
||||
found = re.match(r"\s*\\", str[i:endpos]) is None
|
||||
|
||||
if not found:
|
||||
# oh well ... settle for moving beyond the first chunk
|
||||
# of non-whitespace chars
|
||||
i = startpos
|
||||
while str[i] not in " \t\n":
|
||||
i = i+1
|
||||
|
||||
return len(str[self.stmt_start : i].expandtabs(self.tabwidth)) + 1
|
||||
|
||||
# Return the leading whitespace on the initial line of the last
|
||||
# interesting stmt.
|
||||
|
||||
def get_base_indent_string(self):
|
||||
self._study2()
|
||||
i, n = self.stmt_start, self.stmt_end
|
||||
j = i
|
||||
str = self.str
|
||||
while j < n and str[j] in " \t":
|
||||
j = j + 1
|
||||
return str[i:j]
|
||||
|
||||
# Did the last interesting stmt open a block?
|
||||
|
||||
def is_block_opener(self):
|
||||
self._study2()
|
||||
return self.lastch == ':'
|
||||
|
||||
# Did the last interesting stmt close a block?
|
||||
|
||||
def is_block_closer(self):
|
||||
self._study2()
|
||||
return _closere(self.str, self.stmt_start) is not None
|
||||
|
||||
# index of last open bracket ({[, or None if none
|
||||
lastopenbracketpos = None
|
||||
|
||||
def get_last_open_bracket_pos(self):
|
||||
self._study2()
|
||||
return self.lastopenbracketpos
|
1
venv/Lib/site-packages/pythonwin/pywin/idle/__init__.py
Normal file
1
venv/Lib/site-packages/pythonwin/pywin/idle/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# This file denotes the directory as a Python package.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue