mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Tim Peters keeps revising this module (more to come):
Removed "New tabwidth" menu binding. Added "a tab means how many spaces?" dialog to block tabify and untabify. I think prompting for this is good now: they're usually at-most-once-per-file commands, and IDLE can't let them change tabwidth from the Tk default anymore, so IDLE can no longer presume to have any idea what a tab means. Irony: for the purpose of keeping comments aligned via tabs, Tk's non-default approach is much nicer than the Emacs/Notepad/Codewright/vi/etc approach.
This commit is contained in:
parent
198e7cac5a
commit
d93f739556
1 changed files with 190 additions and 85 deletions
|
@ -38,22 +38,6 @@ TK_TABWIDTH_DEFAULT = 8
|
||||||
###$ win <Alt-Key-6>
|
###$ win <Alt-Key-6>
|
||||||
###$ unix <Alt-Key-6>
|
###$ unix <Alt-Key-6>
|
||||||
|
|
||||||
import re
|
|
||||||
_is_block_closer = re.compile(r"""
|
|
||||||
\s*
|
|
||||||
( return
|
|
||||||
| break
|
|
||||||
| continue
|
|
||||||
| raise
|
|
||||||
| pass
|
|
||||||
)
|
|
||||||
\b
|
|
||||||
""", re.VERBOSE).match
|
|
||||||
|
|
||||||
# colon followed by optional comment
|
|
||||||
_looks_like_opener = re.compile(r":\s*(#.*)?$").search
|
|
||||||
del re
|
|
||||||
|
|
||||||
class AutoIndent:
|
class AutoIndent:
|
||||||
|
|
||||||
menudefs = [
|
menudefs = [
|
||||||
|
@ -66,7 +50,6 @@ class AutoIndent:
|
||||||
('Tabify region', '<<tabify-region>>'),
|
('Tabify region', '<<tabify-region>>'),
|
||||||
('Untabify region', '<<untabify-region>>'),
|
('Untabify region', '<<untabify-region>>'),
|
||||||
('Toggle tabs', '<<toggle-tabs>>'),
|
('Toggle tabs', '<<toggle-tabs>>'),
|
||||||
('New tab width', '<<change-tabwidth>>'),
|
|
||||||
('New indent width', '<<change-indentwidth>>'),
|
('New indent width', '<<change-indentwidth>>'),
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
|
@ -85,8 +68,7 @@ class AutoIndent:
|
||||||
'<<tabify-region>>': ['<Alt-Key-5>'],
|
'<<tabify-region>>': ['<Alt-Key-5>'],
|
||||||
'<<untabify-region>>': ['<Alt-Key-6>'],
|
'<<untabify-region>>': ['<Alt-Key-6>'],
|
||||||
'<<toggle-tabs>>': ['<Alt-Key-t>'],
|
'<<toggle-tabs>>': ['<Alt-Key-t>'],
|
||||||
'<<change-tabwidth>>': ['<Alt-Key-u>'],
|
'<<change-indentwidth>>': ['<Alt-Key-u>'],
|
||||||
'<<change-indentwidth>>': ['<Alt-Key-v>'],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unix_keydefs = {
|
unix_keydefs = {
|
||||||
|
@ -107,11 +89,15 @@ class AutoIndent:
|
||||||
# indentwidth is not a multiple of tabwidth
|
# indentwidth is not a multiple of tabwidth
|
||||||
# false -> tab characters are converted to spaces by indent
|
# false -> tab characters are converted to spaces by indent
|
||||||
# and dedent cmds, and ditto TAB keystrokes
|
# and dedent cmds, and ditto TAB keystrokes
|
||||||
# indentwidth is the number of characters per logical indent level
|
# indentwidth is the number of characters per logical indent level.
|
||||||
# tabwidth is the display width of a literal tab character
|
# 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 = 0
|
usetabs = 0
|
||||||
indentwidth = 4
|
indentwidth = 4
|
||||||
tabwidth = 8
|
tabwidth = TK_TABWIDTH_DEFAULT
|
||||||
|
|
||||||
def __init__(self, editwin):
|
def __init__(self, editwin):
|
||||||
self.text = editwin.text
|
self.text = editwin.text
|
||||||
|
@ -138,8 +124,6 @@ class AutoIndent:
|
||||||
|
|
||||||
if guess and ispythonsource:
|
if guess and ispythonsource:
|
||||||
i = self.guess_indent()
|
i = self.guess_indent()
|
||||||
##import sys
|
|
||||||
##sys.__stdout__.write("indent %d\n" % i)
|
|
||||||
if 2 <= i <= 8:
|
if 2 <= i <= 8:
|
||||||
self.indentwidth = i
|
self.indentwidth = i
|
||||||
if self.indentwidth != self.tabwidth:
|
if self.indentwidth != self.tabwidth:
|
||||||
|
@ -246,9 +230,31 @@ class AutoIndent:
|
||||||
# without regard for usetabs etc; could instead insert
|
# without regard for usetabs etc; could instead insert
|
||||||
# "\n" + self._make_blanks(classifyws(indent)[1]).
|
# "\n" + self._make_blanks(classifyws(indent)[1]).
|
||||||
text.insert("insert", "\n" + indent)
|
text.insert("insert", "\n" + indent)
|
||||||
if _is_block_opener(line):
|
|
||||||
|
# adjust indentation for continuations and block open/close
|
||||||
|
x = LineStudier(line)
|
||||||
|
if x.is_block_opener():
|
||||||
self.smart_indent_event(event)
|
self.smart_indent_event(event)
|
||||||
elif indent and _is_block_closer(line) and line[-1] != "\\":
|
elif x.is_bracket_continued():
|
||||||
|
# if there's something interesting after the last open
|
||||||
|
# bracket, line up with it; else just indent one level
|
||||||
|
i = x.last_open_bracket_index() + 1
|
||||||
|
while i < n and line[i] in " \t":
|
||||||
|
i = i + 1
|
||||||
|
if i < n and line[i] not in "#\n\\":
|
||||||
|
effective = len(string.expandtabs(line[:i],
|
||||||
|
self.tabwidth))
|
||||||
|
else:
|
||||||
|
raw, effective = classifyws(indent, self.tabwidth)
|
||||||
|
effective = effective + self.indentwidth
|
||||||
|
self.reindent_to(effective)
|
||||||
|
elif x.is_backslash_continued():
|
||||||
|
# local info isn't enough to do anything intelligent here;
|
||||||
|
# e.g., if it's the 2nd line a backslash block we want to
|
||||||
|
# indent extra, but if it's the 3rd we don't want to indent
|
||||||
|
# at all; rather than make endless mistakes, leave it alone
|
||||||
|
pass
|
||||||
|
elif indent and x.is_block_closer():
|
||||||
self.smart_backspace_event(event)
|
self.smart_backspace_event(event)
|
||||||
text.see("insert")
|
text.see("insert")
|
||||||
return "break"
|
return "break"
|
||||||
|
@ -302,44 +308,46 @@ class AutoIndent:
|
||||||
|
|
||||||
def tabify_region_event(self, event):
|
def tabify_region_event(self, event):
|
||||||
head, tail, chars, lines = self.get_region()
|
head, tail, chars, lines = self.get_region()
|
||||||
|
tabwidth = self._asktabwidth()
|
||||||
for pos in range(len(lines)):
|
for pos in range(len(lines)):
|
||||||
line = lines[pos]
|
line = lines[pos]
|
||||||
if line:
|
if line:
|
||||||
raw, effective = classifyws(line, self.tabwidth)
|
raw, effective = classifyws(line, tabwidth)
|
||||||
ntabs, nspaces = divmod(effective, self.tabwidth)
|
ntabs, nspaces = divmod(effective, tabwidth)
|
||||||
lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
|
lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
|
||||||
self.set_region(head, tail, chars, lines)
|
self.set_region(head, tail, chars, lines)
|
||||||
|
|
||||||
def untabify_region_event(self, event):
|
def untabify_region_event(self, event):
|
||||||
head, tail, chars, lines = self.get_region()
|
head, tail, chars, lines = self.get_region()
|
||||||
|
tabwidth = self._asktabwidth()
|
||||||
for pos in range(len(lines)):
|
for pos in range(len(lines)):
|
||||||
lines[pos] = string.expandtabs(lines[pos], self.tabwidth)
|
lines[pos] = string.expandtabs(lines[pos], tabwidth)
|
||||||
self.set_region(head, tail, chars, lines)
|
self.set_region(head, tail, chars, lines)
|
||||||
|
|
||||||
def toggle_tabs_event(self, event):
|
def toggle_tabs_event(self, event):
|
||||||
if tkMessageBox.askyesno("Toggle tabs",
|
if tkMessageBox.askyesno(
|
||||||
|
"Toggle tabs",
|
||||||
"Turn tabs " + ("on", "off")[self.usetabs] + "?",
|
"Turn tabs " + ("on", "off")[self.usetabs] + "?",
|
||||||
parent=self.text):
|
parent=self.text):
|
||||||
self.usetabs = not self.usetabs
|
self.usetabs = not self.usetabs
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
|
# XXX this isn't bound to anything -- see class tabwidth comments
|
||||||
def change_tabwidth_event(self, event):
|
def change_tabwidth_event(self, event):
|
||||||
new = tkSimpleDialog.askinteger("Tab width",
|
new = self._asktabwidth()
|
||||||
"New tab width (2-16)",
|
if new != self.tabwidth:
|
||||||
parent=self.text,
|
|
||||||
initialvalue=self.tabwidth,
|
|
||||||
minvalue=2, maxvalue=16)
|
|
||||||
if new and new != self.tabwidth:
|
|
||||||
self.tabwidth = new
|
self.tabwidth = new
|
||||||
self.set_indentation_params(0, guess=0)
|
self.set_indentation_params(0, guess=0)
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def change_indentwidth_event(self, event):
|
def change_indentwidth_event(self, event):
|
||||||
new = tkSimpleDialog.askinteger("Indent width",
|
new = tkSimpleDialog.askinteger(
|
||||||
"New indent width (1-16)",
|
"Indent width",
|
||||||
parent=self.text,
|
"New indent width (1-16)",
|
||||||
initialvalue=self.indentwidth,
|
parent=self.text,
|
||||||
minvalue=1, maxvalue=16)
|
initialvalue=self.indentwidth,
|
||||||
|
minvalue=1,
|
||||||
|
maxvalue=16)
|
||||||
if new and new != self.indentwidth:
|
if new and new != self.indentwidth:
|
||||||
self.indentwidth = new
|
self.indentwidth = new
|
||||||
return "break"
|
return "break"
|
||||||
|
@ -389,6 +397,15 @@ class AutoIndent:
|
||||||
text.insert("insert", self._make_blanks(column))
|
text.insert("insert", self._make_blanks(column))
|
||||||
text.undo_block_stop()
|
text.undo_block_stop()
|
||||||
|
|
||||||
|
def _asktabwidth(self):
|
||||||
|
return tkSimpleDialog.askinteger(
|
||||||
|
"Tab width",
|
||||||
|
"Spaces per tab?",
|
||||||
|
parent=self.text,
|
||||||
|
initialvalue=self.tabwidth,
|
||||||
|
minvalue=1,
|
||||||
|
maxvalue=16) or self.tabwidth
|
||||||
|
|
||||||
# Guess indentwidth from text content.
|
# Guess indentwidth from text content.
|
||||||
# Return guessed indentwidth. This should not be believed unless
|
# Return guessed indentwidth. This should not be believed unless
|
||||||
# it's in a reasonable range (e.g., it will be 0 if no indented
|
# it's in a reasonable range (e.g., it will be 0 if no indented
|
||||||
|
@ -425,53 +442,141 @@ def classifyws(s, tabwidth):
|
||||||
break
|
break
|
||||||
return raw, effective
|
return raw, effective
|
||||||
|
|
||||||
# Return true iff line probably opens a block. This is a limited
|
class LineStudier:
|
||||||
# analysis based on whether the line's last "interesting" character
|
|
||||||
# is a colon.
|
|
||||||
|
|
||||||
def _is_block_opener(line):
|
# set to false by self.study(); the other vars retain default values
|
||||||
if not _looks_like_opener(line):
|
# until then
|
||||||
return 0
|
needstudying = 1
|
||||||
# Looks like an opener, but possible we're in a comment
|
|
||||||
# x = 3 # and then:
|
# line ends with an unescaped backslash not in string or comment?
|
||||||
# or a string
|
backslash_continued = 0
|
||||||
# x = ":#"
|
|
||||||
# If no comment character, we're not in a comment <duh>, and the
|
# line ends with an unterminated string?
|
||||||
# colon is the last non-ws char on the line so it's not in a
|
string_continued = 0
|
||||||
# (single-line) string either.
|
|
||||||
if string.find(line, '#') < 0:
|
# the last "interesting" character on a line: the last non-ws char
|
||||||
return 1
|
# before an optional trailing comment; if backslash_continued, lastch
|
||||||
# Now it's hard: There's a colon and a comment char. Brute force
|
# precedes the final backslash; if string_continued, the required
|
||||||
# approximation.
|
# string-closer (", """, ', ''')
|
||||||
lastch, i, n = 0, 0, len(line)
|
lastch = ""
|
||||||
while i < n:
|
|
||||||
ch = line[i]
|
# index of rightmost unmatched ([{ not in a string or comment
|
||||||
if ch == '\\':
|
lastopenbrackpos = -1
|
||||||
lastch = ch
|
|
||||||
i = i+2
|
import re
|
||||||
elif ch in "\"'":
|
_is_block_closer_re = re.compile(r"""
|
||||||
# consume string
|
\s*
|
||||||
w = 1 # width of string quote
|
( return
|
||||||
if line[i:i+3] in ('"""', "'''"):
|
| break
|
||||||
w = 3
|
| continue
|
||||||
ch = ch * 3
|
| raise
|
||||||
i = i+w
|
| pass
|
||||||
while i < n:
|
)
|
||||||
if line[i] == '\\':
|
\b
|
||||||
i = i+2
|
""", re.VERBOSE).match
|
||||||
elif line[i:i+w] == ch:
|
|
||||||
i = i+w
|
# colon followed by optional comment
|
||||||
break
|
_looks_like_opener_re = re.compile(r":\s*(#.*)?$").search
|
||||||
|
del re
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, line):
|
||||||
|
if line[-1:] == '\n':
|
||||||
|
line = line[:-1]
|
||||||
|
self.line = line
|
||||||
|
self.stack = []
|
||||||
|
|
||||||
|
def is_continued(self):
|
||||||
|
return self.is_block_opener() or \
|
||||||
|
self.is_backslash_continued() or \
|
||||||
|
self.is_bracket_continued() or \
|
||||||
|
self.is_string_continued()
|
||||||
|
|
||||||
|
def is_block_opener(self):
|
||||||
|
if not self._looks_like_opener_re(self.line):
|
||||||
|
return 0
|
||||||
|
# Looks like an opener, but possible we're in a comment
|
||||||
|
# x = 3 # and then:
|
||||||
|
# or a string
|
||||||
|
# x = ":#"
|
||||||
|
# If no comment character, we're not in a comment <duh>, and the
|
||||||
|
# colon is the last non-ws char on the line so it's not in a
|
||||||
|
# (single-line) string either.
|
||||||
|
if string.find(self.line, '#') < 0:
|
||||||
|
return 1
|
||||||
|
self.study()
|
||||||
|
return self.lastch == ":"
|
||||||
|
|
||||||
|
def is_backslash_continued(self):
|
||||||
|
self.study()
|
||||||
|
return self.backslash_continued
|
||||||
|
|
||||||
|
def is_bracket_continued(self):
|
||||||
|
self.study()
|
||||||
|
return self.lastopenbrackpos >= 0
|
||||||
|
|
||||||
|
def is_string_continued(self):
|
||||||
|
self.study()
|
||||||
|
return self.string_continued
|
||||||
|
|
||||||
|
def is_block_closer(self):
|
||||||
|
return self._is_block_closer_re(self.line)
|
||||||
|
|
||||||
|
def last_open_bracket_index(self):
|
||||||
|
assert self.stack
|
||||||
|
return self.lastopenbrackpos
|
||||||
|
|
||||||
|
def study(self):
|
||||||
|
if not self.needstudying:
|
||||||
|
return
|
||||||
|
self.needstudying = 0
|
||||||
|
line = self.line
|
||||||
|
i, n = 0, len(line)
|
||||||
|
while i < n:
|
||||||
|
ch = line[i]
|
||||||
|
if ch == '\\':
|
||||||
|
i = i+1
|
||||||
|
if i == n:
|
||||||
|
self.backslash_continued = 1
|
||||||
else:
|
else:
|
||||||
|
self.lastch = ch + line[i]
|
||||||
i = i+1
|
i = i+1
|
||||||
lastch = ch
|
|
||||||
elif ch == '#':
|
elif ch in "\"'":
|
||||||
break
|
# consume string
|
||||||
else:
|
w = 1 # width of string quote
|
||||||
if ch not in string.whitespace:
|
if line[i:i+3] in ('"""', "'''"):
|
||||||
lastch = ch
|
w = 3
|
||||||
i = i+1
|
ch = ch * 3
|
||||||
return lastch == ':'
|
i = i+w
|
||||||
|
self.lastch = ch
|
||||||
|
while i < n:
|
||||||
|
if line[i] == '\\':
|
||||||
|
i = i+2
|
||||||
|
elif line[i:i+w] == ch:
|
||||||
|
i = i+w
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
i = i+1
|
||||||
|
else:
|
||||||
|
self.string_continued = 1
|
||||||
|
|
||||||
|
elif ch == '#':
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
if ch not in string.whitespace:
|
||||||
|
self.lastch = ch
|
||||||
|
if ch in "([(":
|
||||||
|
self.stack.append(i)
|
||||||
|
elif ch in ")]}" and self.stack:
|
||||||
|
if line[self.stack[-1]] + ch in ("()", "[]", "{}"):
|
||||||
|
del self.stack[-1]
|
||||||
|
i = i+1
|
||||||
|
# end while i < n:
|
||||||
|
|
||||||
|
if self.stack:
|
||||||
|
self.lastopenbrackpos = self.stack[-1]
|
||||||
|
|
||||||
import tokenize
|
import tokenize
|
||||||
_tokenize = tokenize
|
_tokenize = tokenize
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue