-
-
Notifications
You must be signed in to change notification settings - Fork 31.6k
bpo-36390: IDLE: Combine region formatting methods. #12481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
* Create module for indent, dedent, comment, uncomment, tabify, and untabify. * Add tests for new module.
Lib/idlelib/formatregion.py
Outdated
## is appended to the beginning of each line to comment it out. | ||
""" | ||
head, tail, chars, lines = self.get_region() | ||
for pos in range(len(lines) - 1): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the last line excluded?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the last line ends in a newline, then get_region()
will return an empty string as the last value. For example, the the text is a = 'hello'\n
, then lines
contains ['a = hello', '']
. Again, I didn't alter the existing code from editor.py
, so if this needs to be improved, I think it should be in a subsequent PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with leave as is now. By manual test, the code works (almost*) correctly. Note 1. 'a\n'.splitlines() == ['a'], 'a\n'.split('\n') == ['a', '']; get_region does the latter. *Perhaps' it should do the former. *If cursor is at the end of a file, (such as when opening a new file), a new newline may be added. Adding trailing blank lines is a bug.
Lib/idlelib/formatregion.py
Outdated
text.undo_block_stop() | ||
text.tag_add("sel", head, "insert") | ||
|
||
def indent_region_event(self, event=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and the other methods below have so much in common that they should probably be refactored.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, but I wanted to make this PR about removing them from editor.py, so I kept them as they were currently written. Once the code has been split from editor.py and the tests are in place, then I'll work on refactoring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have noticed duplication also, and I agree with leaving them as-is until a follow-up.
@csabella, excellent job adding the tests for all of this functionality! |
Hi @taleinat, thank you for the review. I had tried to make the minimal changes in this PR to just extract the code from |
|
||
# Remove selection. | ||
text.tag_remove('sel', '1.0', 'end') | ||
eq(get(), ('15.0', '16.0', '\n', ['', ''])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very surprising: Why is this returned? This should have a comment, and perhaps explicitly set the insertion index before calling get_region()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sample has 14 lines, all with newline. Apparently, tag_remove to 'end' puts the cursor after the last newline, which is 15,0. Yes, say so. To get the tail index, get_region adds 1c, resulting in 16,0, after the hidden newline tk maintains as the end of every file. It then split('\n')s on that hidden newline, giving the fake list. set_region may (wrongly) add a new newline, as I said above.
self.text.delete('1.0', 'end') | ||
|
||
def test_get_region(self): | ||
get = self.formatter.get_region |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find all of these assignments make the tests harder to read rather than easier. It increases the number of things I need to remember, and makes it necessary to read every test method from the beginning to understand any part of it.
This is a matter of style, but IMO when using many short test methods as done here (which I like!) these "shortcuts" do more harm than good.
To be clear, this is true for all of the test methods here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cheryl learned this from me ;-). I get bleary-eyed reading self.assertEqual too many times. Aside from that, the short func name, usually followed by 'eq' are local conventions that we understand immediately, and you don't -- yet. The 'text' abbreviation in not needed for 2 uses and an be deleted and expanded back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking more, all functions start with func =, text =, eq =, and I think uniformity is better than variety here.
del cls.root | ||
|
||
def setUp(self): | ||
self.text.insert('1.0', code_sample) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a single code sample makes the tests harder to read IMO. Worse, in the future, it will make maintenance difficult: Any change to the code sample would require updating many of the tests.
I suggest having each test define its own code sample(s), which will make them more self-contained.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tal, this and the next comment suggest that your eyes and brain and mine work a bit differently, such that I appear to be more bothered by redundancy than your are.
In this case, I agree that a) the sample is too far from from code that depends on its fine details, and b) 1 sample is likely too few, perhaps more complicated than needed for any of the subset of tests. Perhaps one for the 2 region functions, just before test_get_region, one for the 4 indent/comment function, just before the first of those, and one for the 2 tab functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sample moved in commit 'Move code sample.'. I want to mostly leave tests alone for now because we may refactor them along with the functions they test.
# Add selection. | ||
text.tag_add('sel', '7.0', '10.0') | ||
eq(get(), ('7.0', '10.0', | ||
'\n def compare(self):\n if a > b:\n', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be re-written to use a multi-line string? I think it would be clearer. Or better, avoid duplication entirely, e.g.:
expected_lines = [
'',
' def compare(self):',
' if a > b:',
'',
]
eq(get(), ('7.0', '10.0', '\n'.join(expected_lines), expected_lines))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I very much agree with removing this redundancy. Given no shortage of horizontal space, I would likely write it without the newlines after [ and before ].
expected_lines = ['',
' def compare(self):',
' if a > b:',
'',]
# Conflicts: # Lib/idlelib/editor.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My unposted todo for Format is "Consolidate into 1 file. Paragraph, Rstrip, stuff in editor." My first concern is that this issue and PR be at least a step towards doing that, as discussed on the issue.
My second goal was responding to Tal's comments.
Lib/idlelib/formatregion.py
Outdated
text.undo_block_stop() | ||
text.tag_add("sel", head, "insert") | ||
|
||
def indent_region_event(self, event=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have noticed duplication also, and I agree with leaving them as-is until a follow-up.
Lib/idlelib/formatregion.py
Outdated
## is appended to the beginning of each line to comment it out. | ||
""" | ||
head, tail, chars, lines = self.get_region() | ||
for pos in range(len(lines) - 1): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with leave as is now. By manual test, the code works (almost*) correctly. Note 1. 'a\n'.splitlines() == ['a'], 'a\n'.split('\n') == ['a', '']; get_region does the latter. *Perhaps' it should do the former. *If cursor is at the end of a file, (such as when opening a new file), a new newline may be added. Adding trailing blank lines is a bug.
del cls.root | ||
|
||
def setUp(self): | ||
self.text.insert('1.0', code_sample) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tal, this and the next comment suggest that your eyes and brain and mine work a bit differently, such that I appear to be more bothered by redundancy than your are.
In this case, I agree that a) the sample is too far from from code that depends on its fine details, and b) 1 sample is likely too few, perhaps more complicated than needed for any of the subset of tests. Perhaps one for the 2 region functions, just before test_get_region, one for the 4 indent/comment function, just before the first of those, and one for the 2 tab functions.
self.text.delete('1.0', 'end') | ||
|
||
def test_get_region(self): | ||
get = self.formatter.get_region |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cheryl learned this from me ;-). I get bleary-eyed reading self.assertEqual too many times. Aside from that, the short func name, usually followed by 'eq' are local conventions that we understand immediately, and you don't -- yet. The 'text' abbreviation in not needed for 2 uses and an be deleted and expanded back.
# Add selection. | ||
text.tag_add('sel', '7.0', '10.0') | ||
eq(get(), ('7.0', '10.0', | ||
'\n def compare(self):\n if a > b:\n', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I very much agree with removing this redundancy. Given no shortage of horizontal space, I would likely write it without the newlines after [ and before ].
expected_lines = ['',
' def compare(self):',
' if a > b:',
'',]
|
||
# Remove selection. | ||
text.tag_remove('sel', '1.0', 'end') | ||
eq(get(), ('15.0', '16.0', '\n', ['', ''])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sample has 14 lines, all with newline. Apparently, tag_remove to 'end' puts the cursor after the last newline, which is 15,0. Yes, say so. To get the tail index, get_region adds 1c, resulting in 16,0, after the hidden newline tk maintains as the end of every file. It then split('\n')s on that hidden newline, giving the fake list. set_region may (wrongly) add a new newline, as I said above.
When you're done making the requested changes, leave the comment: And if you don't make the requested changes, you will be poked with soft cushions! |
Cheryl, you said elsewhere that you cannot work on issues for a bit. I would, then, like to finish this myself so I can move on to followups. |
I am working on this now. |
Make other changes so manual testing passes.
Edit to make tests pass.
Terry, thank you for working on this. I had hoped to get some bandwidth after this past weekend, but it doesn't look like that's going to happen, so I appreciate you moving this forward. And Tal, thank you for the review and apologies for not getting to it. |
@csabella, I reviewed hoping to help this along, but please don't feel pressured by that! Take your time and do things whenever you can find the time for them. There's certainly no need to apologize! |
Thanks @csabella for the PR, and @terryjreedy for merging it 🌮🎉.. I'm working now to backport this PR to: 3.7, 3.8. |
Rename paragraph.py to format.py and add region formatting methods from editor.py. Add tests for the latter. (cherry picked from commit 82494aa) Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
GH-14811 is a backport of this pull request to the 3.8 branch. |
Rename paragraph.py to format.py and add region formatting methods from editor.py. Add tests for the latter. (cherry picked from commit 82494aa) Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
GH-14812 is a backport of this pull request to the 3.7 branch. |
Rename paragraph.py to format.py and add region formatting methods from editor.py. Add tests for the latter.
Rename paragraph.py to format.py and add region formatting methods from editor.py. Add tests for the latter.
Rename paragraph.py to format.py and add region formatting methods from editor.py. Add tests for the latter.
https://bugs.python.org/issue36390