Uploaded Test files

This commit is contained in:
Batuhan Berk Başoğlu 2020-11-12 11:05:57 -05:00
parent f584ad9d97
commit 2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions

View 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:]

View 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

View 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)

View 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)

View 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")

View 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

View file

@ -0,0 +1 @@
# This file denotes the directory as a Python package.