-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
gh-75710: IDLE: Add tests to some editor.py functions #3669
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
base: main
Are you sure you want to change the base?
Changes from 2 commits
c21d679
d047c39
435ea82
ac8dae4
eb25140
8dfbd49
0308f80
c32379b
ba274a4
b5d80cb
3fbf6da
0aea634
cf8044e
fba2ef2
47ebc2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -414,6 +414,21 @@ def set_line_and_column(self, event=None): | |
|
||
|
||
def createmenubar(self): | ||
"""Populate the menu bar widget for the editor window. | ||
|
||
Each option on the menubar is itself a cascade-type Menu widget | ||
with the menubar as the parent. The names, labels, and menu | ||
shortcuts for the menubar items are stored in menu_specs. Each | ||
submenu is subsequently populated in fill_menus(), except for | ||
'Recent Files' which is added to the File menu here. | ||
|
||
Instance variables: | ||
menubar: Menu widget containing first level menu items. | ||
menudict: Dictionary of {menuname: Menu instance} items. The keys | ||
represent the valid menu items for this window and may be a | ||
subset of all the menudefs available. | ||
recent_files_menu: Menu widget contained within the 'file' menudict. | ||
""" | ||
mbar = self.menubar | ||
self.menudict = menudict = {} | ||
for name, label in self.menu_specs: | ||
|
@@ -760,7 +775,13 @@ def ResetFont(self): | |
self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow') | ||
|
||
def RemoveKeybindings(self): | ||
"Remove the keybindings before they are changed." | ||
"""Remove the virtual, configurable keybindings. | ||
|
||
This should be called before the keybindings are applied | ||
in ApplyKeyBindings() otherwise the old bindings will still exist. | ||
Note: this does not remove the Tk/Tcl keybindings attached to | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add another blank line before the "Note:". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or leave out 'Note:'. I am going to revise these added docstrings. |
||
Text widgets by default. | ||
""" | ||
# Called from configdialog.py | ||
self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet() | ||
for event, keylist in keydefs.items(): | ||
|
@@ -772,15 +793,21 @@ def RemoveKeybindings(self): | |
self.text.event_delete(event, *keylist) | ||
|
||
def ApplyKeybindings(self): | ||
"Update the keybindings after they are changed" | ||
"""Apply the virtual, configurable keybindings. | ||
|
||
The binding events are attached to self.text. Also, the | ||
menu accelerator keys are updated to match the current | ||
configuration. | ||
""" | ||
# Called from configdialog.py | ||
self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet() | ||
self.apply_bindings() | ||
for extensionName in self.get_standard_extension_names(): | ||
xkeydefs = idleConf.GetExtensionBindings(extensionName) | ||
if xkeydefs: | ||
self.apply_bindings(xkeydefs) | ||
#update menu accelerators | ||
# Update menu accelerators. | ||
# XXX - split into its own function and call it from here? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally, I don't like adding such comments to the code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Such comments should have initials and at least a year. But yes, they can hang around for decades. In this case I agree, as a separate method is needed to group together menu methods. Understanding this code requires knowing the menu term definitions. |
||
menuEventDict = {} | ||
for menu in self.mainmenu.menudefs: | ||
menuEventDict[menu[0]] = {} | ||
|
@@ -815,32 +842,43 @@ def set_notabs_indentwidth(self): | |
type='int') | ||
|
||
def reset_help_menu_entries(self): | ||
"Update the additional help entries on the Help menu" | ||
"""Update the additional help entries on the Help menu. | ||
|
||
First the existing additional help entries are removed from | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this doc-string, I find the added description of how the method works entirely unnecessary, because it is already similarly detailed in comments. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. |
||
the help menu, then the new help entries are added from idleConf. | ||
""" | ||
help_list = idleConf.GetAllExtraHelpSourcesList() | ||
helpmenu = self.menudict['help'] | ||
# first delete the extra help entries, if any | ||
# First delete the extra help entries, if any. | ||
helpmenu_length = helpmenu.index(END) | ||
if helpmenu_length > self.base_helpmenu_length: | ||
helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length) | ||
# then rebuild them | ||
# Then rebuild them. | ||
if help_list: | ||
helpmenu.add_separator() | ||
for entry in help_list: | ||
cmd = self.__extra_help_callback(entry[1]) | ||
helpmenu.add_command(label=entry[0], command=cmd) | ||
# and update the menu dictionary | ||
# And update the menu dictionary. | ||
self.menudict['help'] = helpmenu | ||
|
||
def __extra_help_callback(self, helpfile): | ||
"Create a callback with the helpfile value frozen at definition time" | ||
"""Create a callback with the helpfile value frozen at definition time. | ||
|
||
Args: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This style of describing args in doc-strings is non-standard. I think it would be shorter and simpler to just remove the "Args:" heading, and change the "Returns:" section into a single line of text. Likewise for other methods. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. Cheryl did this PR after we refactored configdialog. For that, we had some exceptionally wordy docstrings to keep things clear. This docstring should start with 'Return...' |
||
helpfile: Filename or website to open. | ||
|
||
Returns: | ||
Function to open the helpfile. | ||
""" | ||
def display_extra_help(helpfile=helpfile): | ||
if not helpfile.startswith(('www', 'http')): | ||
helpfile = os.path.normpath(helpfile) | ||
if sys.platform[:3] == 'win': | ||
try: | ||
os.startfile(helpfile) | ||
except OSError as why: | ||
tkMessageBox.showerror(title='Document Start Failure', | ||
self.showerror(title='Document Start Failure', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I checked this. self.showerror is set in init . |
||
message=str(why), parent=self.text) | ||
else: | ||
webbrowser.open(helpfile) | ||
|
@@ -999,6 +1037,8 @@ def _close(self): | |
if self.color: | ||
self.color.close(False) | ||
self.color = None | ||
# Allow code context to close its text.after calls. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does unbinding the event allow the code context to do anything? Also this is surprising to find given the title of this PR. If kept, it should be mentioned in the description and in the final commit message as an additional fix. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed that revising _close, which has been somewhat problematical, is a separate issue. |
||
self.text.unbind('<<toggle-code-context>>') | ||
self.text = None | ||
self.tkinter_vars = None | ||
self.per.close() | ||
|
@@ -1062,6 +1102,11 @@ def load_extension(self, name): | |
self.text.bind(vevent, getattr(ins, methodname)) | ||
|
||
def apply_bindings(self, keydefs=None): | ||
"""Add the event bindings in keydefs to self.text. | ||
|
||
Args: | ||
keydefs: Virtual events and keybinding definitions. | ||
""" | ||
if keydefs is None: | ||
keydefs = self.mainmenu.default_keydefs | ||
text = self.text | ||
|
@@ -1071,9 +1116,28 @@ def apply_bindings(self, keydefs=None): | |
text.event_add(event, *keylist) | ||
|
||
def fill_menus(self, menudefs=None, keydefs=None): | ||
"""Add appropriate entries to the menus and submenus | ||
|
||
Menus that are absent or None in self.menudict are ignored. | ||
"""Add appropriate entries to the menus and submenus. | ||
|
||
The default menudefs and keydefs are loaded from idlelib.mainmenu. | ||
Menus that are absent or None in self.menudict are ignored. The | ||
default menu type created for submenus from menudefs is `command`. | ||
A submenu item of None results in a `separator` menu type. | ||
A submenu name beginning with ! represents a `checkbutton` type. | ||
|
||
The menus are stored in self.menudict. | ||
|
||
Args: | ||
menudefs: Menu and submenu names, underlines (shortcuts), | ||
and events which is a list of tuples of the form: | ||
[(menu1, [(submenu1a, '<<virtual event>>'), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the menu and sub-menu names in the examples be stings? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be here. See previous comment about explaining menu terms once. |
||
(submenu1b, '<<virtual event>>'), ...]), | ||
(menu2, [(submenu2a, '<<virtual event>>'), | ||
(submenu2b, '<<virtual event>>'), ...]), | ||
] | ||
keydefs: Virtual events and keybinding definitions. Used for | ||
the 'accelerator' text on the menu. Stored as a | ||
dictionary of | ||
{'<<virtual event>>': ['<binding1>', '<binding2>'],} | ||
""" | ||
if menudefs is None: | ||
menudefs = self.mainmenu.menudefs | ||
|
@@ -1123,6 +1187,17 @@ def setvar(self, name, value, vartype=None): | |
raise NameError(name) | ||
|
||
def get_var_obj(self, name, vartype=None): | ||
"""Return a tkinter variable instance for the event. | ||
|
||
Cache vars in self.tkinter_vars as {name: Var instance}. | ||
|
||
Args: | ||
name: Event name. | ||
vartype: Tkinter Var type. | ||
|
||
Returns: | ||
Tkinter Var instance. | ||
""" | ||
var = self.tkinter_vars.get(name) | ||
if not var and vartype: | ||
# create a Tkinter variable object with self.text as master: | ||
|
@@ -1630,8 +1705,16 @@ def run(self): | |
### end autoindent code ### | ||
|
||
def prepstr(s): | ||
# Helper to extract the underscore from a string, e.g. | ||
# prepstr("Co_py") returns (2, "Copy"). | ||
"""Extract the underscore from a string. | ||
|
||
For example, prepstr("Co_py") returns (2, "Copy"). | ||
|
||
Args: | ||
s: String with underscore. | ||
|
||
Returns: | ||
Tuple of (position of underscore, string without underscore). | ||
""" | ||
i = s.find('_') | ||
if i >= 0: | ||
s = s[:i] + s[i+1:] | ||
|
@@ -1645,6 +1728,18 @@ def prepstr(s): | |
} | ||
|
||
def get_accelerator(keydefs, eventname): | ||
"""Return a formatted string for the keybinding of an event. | ||
|
||
Convert the first keybinding for a given event to a form that | ||
can be displayed as an accelerator on the menu. | ||
|
||
Args: | ||
keydefs: Dictionary of valid events to keybindings. | ||
eventname: Event to retrieve keybinding for. | ||
|
||
Returns: | ||
Formatted string of the keybinding. | ||
""" | ||
keylist = keydefs.get(eventname) | ||
# issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5 | ||
# if not keylist: | ||
|
@@ -1654,14 +1749,23 @@ def get_accelerator(keydefs, eventname): | |
"<<change-indentwidth>>"}): | ||
return "" | ||
s = keylist[0] | ||
# Convert strings of the form -singlelowercase to -singleuppercase. | ||
s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s) | ||
# Convert certain keynames to their symbol. | ||
s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s) | ||
# Remove Key- from string. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The first 2 added comments are helpful, the rest are obvious. |
||
s = re.sub("Key-", "", s) | ||
s = re.sub("Cancel","Ctrl-Break",s) # [email protected] | ||
# Convert Cancel to Ctrl-Break. | ||
s = re.sub("Cancel", "Ctrl-Break", s) # [email protected] | ||
# Convert Control to Ctrl-. | ||
s = re.sub("Control-", "Ctrl-", s) | ||
# Change - to +. | ||
s = re.sub("-", "+", s) | ||
# Change >< to space. | ||
s = re.sub("><", " ", s) | ||
# Remove <. | ||
s = re.sub("<", "", s) | ||
# Remove >. | ||
s = re.sub(">", "", s) | ||
return s | ||
|
||
|
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.
IMO in this case, the rest of the doc-string after the first line should be a comment instead, since it describes the method's code rather than it's purpose.
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 problem goes deeper. There should be separate window-with-menu, frame, and text classes, and possibly an IDLE-menu class with a docstring that explains the menu system. And it maybe should be in mainmenu.py.
At present, the menu functions are scattered around this file. Right now, they should be at least gathered together in one section with one class-level block comment. Or maybe the added comment should be in mainmenu.py. This should be a separate PR.