mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	bpo-33628: IDLE: Minor code cleanup of codecontext.py and its tests (GH-7085)
This commit is contained in:
		
							parent
							
								
									8ebf5ceb0f
								
							
						
					
					
						commit
						8506016f90
					
				
					 4 changed files with 62 additions and 59 deletions
				
			
		|  | @ -3,6 +3,8 @@ Released on 2018-06-18? | |||
| ====================================== | ||||
| 
 | ||||
| 
 | ||||
| bpo-33628: Cleanup codecontext.py and its test. | ||||
| 
 | ||||
| bpo-32831: Add docstrings and tests for codecontext.py. | ||||
| Coverage is 100%.  Patch by Cheryl Sabella. | ||||
| 
 | ||||
|  |  | |||
|  | @ -23,9 +23,23 @@ | |||
| FONTUPDATEINTERVAL = 1000 # millisec | ||||
| 
 | ||||
| 
 | ||||
| def getspacesfirstword(s, c=re.compile(r"^(\s*)(\w*)")): | ||||
|     "Extract the beginning whitespace and first word from s." | ||||
|     return c.match(s).groups() | ||||
| def get_spaces_firstword(codeline, c=re.compile(r"^(\s*)(\w*)")): | ||||
|     "Extract the beginning whitespace and first word from codeline." | ||||
|     return c.match(codeline).groups() | ||||
| 
 | ||||
| 
 | ||||
| def get_line_info(codeline): | ||||
|     """Return tuple of (line indent value, codeline, block start keyword). | ||||
| 
 | ||||
|     The indentation of empty lines (or comment lines) is INFINITY. | ||||
|     If the line does not start a block, the keyword value is False. | ||||
|     """ | ||||
|     spaces, firstword = get_spaces_firstword(codeline) | ||||
|     indent = len(spaces) | ||||
|     if len(codeline) == indent or codeline[indent] == '#': | ||||
|         indent = INFINITY | ||||
|     opener = firstword in BLOCKOPENERS and firstword | ||||
|     return indent, codeline, opener | ||||
| 
 | ||||
| 
 | ||||
| class CodeContext: | ||||
|  | @ -42,12 +56,12 @@ def __init__(self, editwin): | |||
|         self.textfont is the editor window font. | ||||
| 
 | ||||
|         self.label displays the code context text above the editor text. | ||||
|           Initially None it is toggled via <<toggle-code-context>>. | ||||
|           Initially None, it is toggled via <<toggle-code-context>>. | ||||
|         self.topvisible is the number of the top text line displayed. | ||||
|         self.info is a list of (line number, indent level, line text, | ||||
|           block keyword) tuples for the block structure above topvisible. | ||||
|           s self.info[0] is initialized a 'dummy' line which | ||||
|         # starts the toplevel 'block' of the module. | ||||
|           self.info[0] is initialized with a 'dummy' line which | ||||
|           starts the toplevel 'block' of the module. | ||||
| 
 | ||||
|         self.t1 and self.t2 are two timer events on the editor text widget to | ||||
|           monitor for changes to the context text or editor font. | ||||
|  | @ -94,23 +108,21 @@ def toggle_code_context_event(self, event=None): | |||
|             # All values are passed through getint(), since some | ||||
|             # values may be pixel objects, which can't simply be added to ints. | ||||
|             widgets = self.editwin.text, self.editwin.text_frame | ||||
|             # Calculate the required vertical padding | ||||
|             # Calculate the required horizontal padding and border width. | ||||
|             padx = 0 | ||||
|             border = 0 | ||||
|             for widget in widgets: | ||||
|                 padx += widget.tk.getint(widget.pack_info()['padx']) | ||||
|                 padx += widget.tk.getint(widget.cget('padx')) | ||||
|             # Calculate the required border width | ||||
|             border = 0 | ||||
|             for widget in widgets: | ||||
|                 border += widget.tk.getint(widget.cget('border')) | ||||
|             self.label = tkinter.Label( | ||||
|                     self.editwin.top, text="\n" * (self.context_depth - 1), | ||||
|                     anchor=W, justify=LEFT, font=self.textfont, | ||||
|                     bg=self.bgcolor, fg=self.fgcolor, | ||||
|                     width=1, #don't request more than we get | ||||
|                     width=1,  # Don't request more than we get. | ||||
|                     padx=padx, border=border, relief=SUNKEN) | ||||
|             # Pack the label widget before and above the text_frame widget, | ||||
|             # thus ensuring that it will appear directly above text_frame | ||||
|             # thus ensuring that it will appear directly above text_frame. | ||||
|             self.label.pack(side=TOP, fill=X, expand=False, | ||||
|                             before=self.editwin.text_frame) | ||||
|         else: | ||||
|  | @ -118,21 +130,6 @@ def toggle_code_context_event(self, event=None): | |||
|             self.label = None | ||||
|         return "break" | ||||
| 
 | ||||
|     def get_line_info(self, linenum): | ||||
|         """Return tuple of (line indent value, text, and block start keyword). | ||||
| 
 | ||||
|         If the line does not start a block, the keyword value is False. | ||||
|         The indentation of empty lines (or comment lines) is INFINITY. | ||||
|         """ | ||||
|         text = self.text.get("%d.0" % linenum, "%d.end" % linenum) | ||||
|         spaces, firstword = getspacesfirstword(text) | ||||
|         opener = firstword in BLOCKOPENERS and firstword | ||||
|         if len(text) == len(spaces) or text[len(spaces)] == '#': | ||||
|             indent = INFINITY | ||||
|         else: | ||||
|             indent = len(spaces) | ||||
|         return indent, text, opener | ||||
| 
 | ||||
|     def get_context(self, new_topvisible, stopline=1, stopindent=0): | ||||
|         """Return a list of block line tuples and the 'last' indent. | ||||
| 
 | ||||
|  | @ -144,16 +141,17 @@ def get_context(self, new_topvisible, stopline=1, stopindent=0): | |||
|         """ | ||||
|         assert stopline > 0 | ||||
|         lines = [] | ||||
|         # The indentation level we are currently in: | ||||
|         # The indentation level we are currently in. | ||||
|         lastindent = INFINITY | ||||
|         # For a line to be interesting, it must begin with a block opening | ||||
|         # keyword, and have less indentation than lastindent. | ||||
|         for linenum in range(new_topvisible, stopline-1, -1): | ||||
|             indent, text, opener = self.get_line_info(linenum) | ||||
|             codeline = self.text.get(f'{linenum}.0', f'{linenum}.end') | ||||
|             indent, text, opener = get_line_info(codeline) | ||||
|             if indent < lastindent: | ||||
|                 lastindent = indent | ||||
|                 if opener in ("else", "elif"): | ||||
|                     # We also show the if statement | ||||
|                     # Also show the if statement. | ||||
|                     lastindent += 1 | ||||
|                 if opener and linenum < new_topvisible and indent >= stopindent: | ||||
|                     lines.append((linenum, indent, text, opener)) | ||||
|  | @ -172,19 +170,19 @@ def update_code_context(self): | |||
|         the context label. | ||||
|         """ | ||||
|         new_topvisible = int(self.text.index("@0,0").split('.')[0]) | ||||
|         if self.topvisible == new_topvisible:      # haven't scrolled | ||||
|         if self.topvisible == new_topvisible:      # Haven't scrolled. | ||||
|             return | ||||
|         if self.topvisible < new_topvisible:       # scroll down | ||||
|         if self.topvisible < new_topvisible:       # Scroll down. | ||||
|             lines, lastindent = self.get_context(new_topvisible, | ||||
|                                                  self.topvisible) | ||||
|             # retain only context info applicable to the region | ||||
|             # between topvisible and new_topvisible: | ||||
|             # Retain only context info applicable to the region | ||||
|             # between topvisible and new_topvisible. | ||||
|             while self.info[-1][1] >= lastindent: | ||||
|                 del self.info[-1] | ||||
|         else:  # self.topvisible > new_topvisible:     # scroll up | ||||
|         else:  # self.topvisible > new_topvisible: # Scroll up. | ||||
|             stopindent = self.info[-1][1] + 1 | ||||
|             # retain only context info associated | ||||
|             # with lines above new_topvisible: | ||||
|             # Retain only context info associated | ||||
|             # with lines above new_topvisible. | ||||
|             while self.info[-1][0] >= new_topvisible: | ||||
|                 stopindent = self.info[-1][1] | ||||
|                 del self.info[-1] | ||||
|  | @ -193,9 +191,9 @@ def update_code_context(self): | |||
|                                                  stopindent) | ||||
|         self.info.extend(lines) | ||||
|         self.topvisible = new_topvisible | ||||
|         # empty lines in context pane: | ||||
|         # Empty lines in context pane. | ||||
|         context_strings = [""] * max(0, self.context_depth - len(self.info)) | ||||
|         # followed by the context hint lines: | ||||
|         # Followed by the context hint lines. | ||||
|         context_strings += [x[2] for x in self.info[-self.context_depth:]] | ||||
|         self.label["text"] = '\n'.join(context_strings) | ||||
| 
 | ||||
|  |  | |||
|  | @ -96,8 +96,6 @@ def test_init(self): | |||
|         eq(self.root.tk.call('after', 'info', self.cc.t2)[1], 'timer') | ||||
| 
 | ||||
|     def test_del(self): | ||||
|         self.root.tk.call('after', 'info', self.cc.t1) | ||||
|         self.root.tk.call('after', 'info', self.cc.t2) | ||||
|         self.cc.__del__() | ||||
|         with self.assertRaises(TclError) as msg: | ||||
|             self.root.tk.call('after', 'info', self.cc.t1) | ||||
|  | @ -135,21 +133,6 @@ def test_toggle_code_context_event(self): | |||
|         eq(toggle(), 'break') | ||||
|         self.assertIsNone(cc.label) | ||||
| 
 | ||||
|     def test_get_line_info(self): | ||||
|         eq = self.assertEqual | ||||
|         gli = self.cc.get_line_info | ||||
| 
 | ||||
|         # Line 1 is not a BLOCKOPENER. | ||||
|         eq(gli(1), (codecontext.INFINITY, '', False)) | ||||
|         # Line 2 is a BLOCKOPENER without an indent. | ||||
|         eq(gli(2), (0, 'class C1():', 'class')) | ||||
|         # Line 3 is not a BLOCKOPENER and does not return the indent level. | ||||
|         eq(gli(3), (codecontext.INFINITY, '    # Class comment.', False)) | ||||
|         # Line 4 is a BLOCKOPENER and is indented. | ||||
|         eq(gli(4), (4, '    def __init__(self, a, b):', 'def')) | ||||
|         # Line 8 is a different BLOCKOPENER and is indented. | ||||
|         eq(gli(8), (8, '        if a > b:', 'if')) | ||||
| 
 | ||||
|     def test_get_context(self): | ||||
|         eq = self.assertEqual | ||||
|         gc = self.cc.get_context | ||||
|  | @ -323,8 +306,8 @@ def test_font_timer_event(self): | |||
| 
 | ||||
| class HelperFunctionText(unittest.TestCase): | ||||
| 
 | ||||
|     def test_getspacesfirstword(self): | ||||
|         get = codecontext.getspacesfirstword | ||||
|     def test_get_spaces_firstword(self): | ||||
|         get = codecontext.get_spaces_firstword | ||||
|         test_lines = ( | ||||
|             ('    first word', ('    ', 'first')), | ||||
|             ('\tfirst word', ('\t', 'first')), | ||||
|  | @ -342,6 +325,24 @@ def test_getspacesfirstword(self): | |||
|                              c=re.compile(r'^(\s*)([^\s]*)')), | ||||
|                          ('    ', '(continuation)')) | ||||
| 
 | ||||
|     def test_get_line_info(self): | ||||
|         eq = self.assertEqual | ||||
|         gli = codecontext.get_line_info | ||||
|         lines = code_sample.splitlines() | ||||
| 
 | ||||
|         # Line 1 is not a BLOCKOPENER. | ||||
|         eq(gli(lines[0]), (codecontext.INFINITY, '', False)) | ||||
|         # Line 2 is a BLOCKOPENER without an indent. | ||||
|         eq(gli(lines[1]), (0, 'class C1():', 'class')) | ||||
|         # Line 3 is not a BLOCKOPENER and does not return the indent level. | ||||
|         eq(gli(lines[2]), (codecontext.INFINITY, '    # Class comment.', False)) | ||||
|         # Line 4 is a BLOCKOPENER and is indented. | ||||
|         eq(gli(lines[3]), (4, '    def __init__(self, a, b):', 'def')) | ||||
|         # Line 8 is a different BLOCKOPENER and is indented. | ||||
|         eq(gli(lines[7]), (8, '        if a > b:', 'if')) | ||||
|         # Test tab. | ||||
|         eq(gli('\tif a == b:'), (1, '\tif a == b:', 'if')) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     unittest.main(verbosity=2) | ||||
|  |  | |||
|  | @ -0,0 +1,2 @@ | |||
| IDLE: Cleanup codecontext.py and its test. | ||||
| 
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Cheryl Sabella
						Cheryl Sabella