Skip to content
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

bpo-36390: IDLE: Combine region formatting methods. #12481

Merged
merged 9 commits into from Jul 17, 2019

Conversation

csabella
Copy link
Contributor

@csabella csabella commented Mar 21, 2019

  • Create module for indent, dedent, comment, uncomment, tabify, and untabify.
  • Add tests for new module.

https://bugs.python.org/issue36390

* Create module for indent, dedent, comment, uncomment,
  tabify, and untabify.
* Add tests for new module.
## 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):
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

text.undo_block_stop()
text.tag_add("sel", head, "insert")

def indent_region_event(self, event=None):
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

@taleinat
Copy link
Contributor

taleinat commented Jun 6, 2019

@csabella, excellent job adding the tests for all of this functionality!

@csabella
Copy link
Contributor Author

Hi @taleinat, thank you for the review. I had tried to make the minimal changes in this PR to just extract the code from editor.py. It is challenging to not make more changes at the same time, but I really wanted to try to keep it as simple as possible for review. Once the formatting functions are split, it will be easier to focus on improving just these methods and also adding new formatting methods to the menu.

Lib/idlelib/idle_test/test_formatregion.py Outdated Show resolved Hide resolved

# Remove selection.
text.tag_remove('sel', '1.0', 'end')
eq(get(), ('15.0', '16.0', '\n', ['', '']))
Copy link
Contributor

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().

Copy link
Member

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
Copy link
Contributor

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.

Copy link
Member

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.

Copy link
Member

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)
Copy link
Contributor

@taleinat taleinat Jun 30, 2019

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.

Copy link
Member

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.

Copy link
Member

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.

Lib/idlelib/idle_test/test_formatregion.py Outdated Show resolved Hide resolved
# 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',
Copy link
Contributor

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

Copy link
Member

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
Copy link
Member

@terryjreedy terryjreedy left a 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.

text.undo_block_stop()
text.tag_add("sel", head, "insert")

def indent_region_event(self, event=None):
Copy link
Member

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 Show resolved Hide resolved
## 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):
Copy link
Member

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)
Copy link
Member

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
Copy link
Member

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',
Copy link
Member

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', ['', '']))
Copy link
Member

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.

@bedevere-bot
Copy link

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

And if you don't make the requested changes, you will be poked with soft cushions!

@terryjreedy
Copy link
Member

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.

@terryjreedy
Copy link
Member

I am working on this now.

@terryjreedy terryjreedy changed the title bpo-36390: IDLE: Refactor formatting methods from editor.py. bpo-36390: IDLE: Combine region formatting methods. Jul 17, 2019
@terryjreedy terryjreedy added the tests Tests in the Lib/test dir label Jul 17, 2019
@csabella
Copy link
Contributor Author

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.

@taleinat
Copy link
Contributor

@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!

@terryjreedy terryjreedy merged commit 82494aa into python:master Jul 17, 2019
@miss-islington
Copy link
Contributor

Thanks @csabella for the PR, and @terryjreedy for merging it 🌮🎉.. I'm working now to backport this PR to: 3.7, 3.8.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jul 17, 2019
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>
@bedevere-bot bedevere-bot removed the needs backport to 3.8 only security fixes label Jul 17, 2019
@bedevere-bot
Copy link

GH-14811 is a backport of this pull request to the 3.8 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jul 17, 2019
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>
@bedevere-bot
Copy link

GH-14812 is a backport of this pull request to the 3.7 branch.

terryjreedy pushed a commit that referenced this pull request Jul 17, 2019
)

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>
terryjreedy pushed a commit that referenced this pull request Jul 17, 2019
)

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>
lisroach pushed a commit to lisroach/cpython that referenced this pull request Sep 10, 2019
Rename paragraph.py to format.py and add region formatting methods
from editor.py.  Add tests for the latter.
DinoV pushed a commit to DinoV/cpython that referenced this pull request Jan 14, 2020
Rename paragraph.py to format.py and add region formatting methods
from editor.py.  Add tests for the latter.
@csabella csabella deleted the region branch February 23, 2020 00:33
websurfer5 pushed a commit to websurfer5/cpython that referenced this pull request Jul 20, 2020
Rename paragraph.py to format.py and add region formatting methods
from editor.py.  Add tests for the latter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tests Tests in the Lib/test dir type-feature A feature request or enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants