Skip to content

Commit 9b5ce62

Browse files
authored
bpo-36390: simplify classifyws(), rename it and add unit tests (GH-14500)
1 parent 79042ac commit 9b5ce62

File tree

2 files changed

+77
-23
lines changed

2 files changed

+77
-23
lines changed

Lib/idlelib/editor.py

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,7 @@ def smart_indent_event(self, event):
12811281
text.delete(first, last)
12821282
text.mark_set("insert", first)
12831283
prefix = text.get("insert linestart", "insert")
1284-
raw, effective = classifyws(prefix, self.tabwidth)
1284+
raw, effective = get_line_indent(prefix, self.tabwidth)
12851285
if raw == len(prefix):
12861286
# only whitespace to the left
12871287
self.reindent_to(effective + self.indentwidth)
@@ -1415,7 +1415,7 @@ def indent_region_event(self, event):
14151415
for pos in range(len(lines)):
14161416
line = lines[pos]
14171417
if line:
1418-
raw, effective = classifyws(line, self.tabwidth)
1418+
raw, effective = get_line_indent(line, self.tabwidth)
14191419
effective = effective + self.indentwidth
14201420
lines[pos] = self._make_blanks(effective) + line[raw:]
14211421
self.set_region(head, tail, chars, lines)
@@ -1426,7 +1426,7 @@ def dedent_region_event(self, event):
14261426
for pos in range(len(lines)):
14271427
line = lines[pos]
14281428
if line:
1429-
raw, effective = classifyws(line, self.tabwidth)
1429+
raw, effective = get_line_indent(line, self.tabwidth)
14301430
effective = max(effective - self.indentwidth, 0)
14311431
lines[pos] = self._make_blanks(effective) + line[raw:]
14321432
self.set_region(head, tail, chars, lines)
@@ -1461,7 +1461,7 @@ def tabify_region_event(self, event):
14611461
for pos in range(len(lines)):
14621462
line = lines[pos]
14631463
if line:
1464-
raw, effective = classifyws(line, tabwidth)
1464+
raw, effective = get_line_indent(line, tabwidth)
14651465
ntabs, nspaces = divmod(effective, tabwidth)
14661466
lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
14671467
self.set_region(head, tail, chars, lines)
@@ -1575,8 +1575,8 @@ def _asktabwidth(self):
15751575
def guess_indent(self):
15761576
opener, indented = IndentSearcher(self.text, self.tabwidth).run()
15771577
if opener and indented:
1578-
raw, indentsmall = classifyws(opener, self.tabwidth)
1579-
raw, indentlarge = classifyws(indented, self.tabwidth)
1578+
raw, indentsmall = get_line_indent(opener, self.tabwidth)
1579+
raw, indentlarge = get_line_indent(indented, self.tabwidth)
15801580
else:
15811581
indentsmall = indentlarge = 0
15821582
return indentlarge - indentsmall
@@ -1585,23 +1585,16 @@ def guess_indent(self):
15851585
def index2line(index):
15861586
return int(float(index))
15871587

1588-
# Look at the leading whitespace in s.
1589-
# Return pair (# of leading ws characters,
1590-
# effective # of leading blanks after expanding
1591-
# tabs to width tabwidth)
1592-
1593-
def classifyws(s, tabwidth):
1594-
raw = effective = 0
1595-
for ch in s:
1596-
if ch == ' ':
1597-
raw = raw + 1
1598-
effective = effective + 1
1599-
elif ch == '\t':
1600-
raw = raw + 1
1601-
effective = (effective // tabwidth + 1) * tabwidth
1602-
else:
1603-
break
1604-
return raw, effective
1588+
1589+
_line_indent_re = re.compile(r'[ \t]*')
1590+
def get_line_indent(line, tabwidth):
1591+
"""Return a line's indentation as (# chars, effective # of spaces).
1592+
1593+
The effective # of spaces is the length after properly "expanding"
1594+
the tabs into spaces, as done by str.expandtabs(tabwidth).
1595+
"""
1596+
m = _line_indent_re.match(line)
1597+
return m.end(), len(m.group().expandtabs(tabwidth))
16051598

16061599

16071600
class IndentSearcher(object):

Lib/idlelib/idle_test/test_editor.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,66 @@ class dummy():
4242
self.assertEqual(func(dummy, inp), out)
4343

4444

45+
class TestGetLineIndent(unittest.TestCase):
46+
def test_empty_lines(self):
47+
for tabwidth in [1, 2, 4, 6, 8]:
48+
for line in ['', '\n']:
49+
with self.subTest(line=line, tabwidth=tabwidth):
50+
self.assertEqual(
51+
editor.get_line_indent(line, tabwidth=tabwidth),
52+
(0, 0),
53+
)
54+
55+
def test_tabwidth_4(self):
56+
# (line, (raw, effective))
57+
tests = (('no spaces', (0, 0)),
58+
# Internal space isn't counted.
59+
(' space test', (4, 4)),
60+
('\ttab test', (1, 4)),
61+
('\t\tdouble tabs test', (2, 8)),
62+
# Different results when mixing tabs and spaces.
63+
(' \tmixed test', (5, 8)),
64+
(' \t mixed test', (5, 6)),
65+
('\t mixed test', (5, 8)),
66+
# Spaces not divisible by tabwidth.
67+
(' \tmixed test', (3, 4)),
68+
(' \t mixed test', (3, 5)),
69+
('\t mixed test', (3, 6)),
70+
# Only checks spaces and tabs.
71+
('\nnewline test', (0, 0)))
72+
73+
for line, expected in tests:
74+
with self.subTest(line=line):
75+
self.assertEqual(
76+
editor.get_line_indent(line, tabwidth=4),
77+
expected,
78+
)
79+
80+
def test_tabwidth_8(self):
81+
# (line, (raw, effective))
82+
tests = (('no spaces', (0, 0)),
83+
# Internal space isn't counted.
84+
(' space test', (8, 8)),
85+
('\ttab test', (1, 8)),
86+
('\t\tdouble tabs test', (2, 16)),
87+
# Different results when mixing tabs and spaces.
88+
(' \tmixed test', (9, 16)),
89+
(' \t mixed test', (9, 10)),
90+
('\t mixed test', (9, 16)),
91+
# Spaces not divisible by tabwidth.
92+
(' \tmixed test', (3, 8)),
93+
(' \t mixed test', (3, 9)),
94+
('\t mixed test', (3, 10)),
95+
# Only checks spaces and tabs.
96+
('\nnewline test', (0, 0)))
97+
98+
for line, expected in tests:
99+
with self.subTest(line=line):
100+
self.assertEqual(
101+
editor.get_line_indent(line, tabwidth=8),
102+
expected,
103+
)
104+
105+
45106
if __name__ == '__main__':
46107
unittest.main(verbosity=2)

0 commit comments

Comments
 (0)