Skip to content

Commit

Permalink
bpo-5680: IDLE: Customize running a module (pythonGH-13763)
Browse files Browse the repository at this point in the history
The initialize options are 1) add command line options, which are appended to sys.argv as if passed on a real command line, and 2) skip the shell restart. The customization dialog is accessed by a new entry on the Run menu.
  • Loading branch information
csabella authored and terryjreedy committed Jun 18, 2019
1 parent 7fb3190 commit 201bc2d
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 51 deletions.
16 changes: 15 additions & 1 deletion Doc/library/idle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -207,25 +207,39 @@ Strip trailing whitespace
Run menu (Editor window only)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. _python-shell:

Python Shell
Open or wake up the Python Shell window.

.. _check-module:

Check Module
Check the syntax of the module currently open in the Editor window. If the
module has not been saved IDLE will either prompt the user to save or
autosave, as selected in the General tab of the Idle Settings dialog. If
there is a syntax error, the approximate location is indicated in the
Editor window.

.. _run-module:

Run Module
Do Check Module (above). If no error, restart the shell to clean the
Do :ref:`Check Module <check-module>`. If no error, restart the shell to clean the
environment, then execute the module. Output is displayed in the Shell
window. Note that output requires use of ``print`` or ``write``.
When execution is complete, the Shell retains focus and displays a prompt.
At this point, one may interactively explore the result of execution.
This is similar to executing a file with ``python -i file`` at a command
line.

.. _run-custom:

Run... Customized
Same as :ref:`Run Module <run-module>`, but run the module with customized
settings. *Command Line Arguments* extend :data:`sys.argv` as if passed
on a command line. The module can be run in the Shell without restarting.


Shell menu (Shell window only)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
5 changes: 5 additions & 0 deletions Lib/idlelib/config-keys.def
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ force-open-calltip= <Control-Key-backslash>
format-paragraph= <Alt-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
run-custom= <Shift-Key-F5>
check-module= <Alt-Key-x>
zoom-height= <Alt-Key-2>

Expand Down Expand Up @@ -122,6 +123,7 @@ force-open-calltip= <Control-Key-backslash>
format-paragraph= <Alt-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
run-custom= <Shift-Key-F5>
check-module= <Alt-Key-x>
zoom-height= <Alt-Key-2>

Expand Down Expand Up @@ -181,6 +183,7 @@ force-open-calltip= <Control-Key-backslash>
format-paragraph= <Alt-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
run-custom= <Shift-Key-F5>
check-module= <Alt-Key-x>
zoom-height= <Alt-Key-2>

Expand Down Expand Up @@ -240,6 +243,7 @@ force-open-calltip= <Control-Key-backslash>
format-paragraph= <Option-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
run-custom= <Shift-Key-F5>
check-module= <Option-Key-x>
zoom-height= <Option-Key-0>

Expand Down Expand Up @@ -300,5 +304,6 @@ force-open-calltip= <Control-Key-backslash>
format-paragraph= <Option-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
run-custom= <Shift-Key-F5>
check-module= <Option-Key-x>
zoom-height= <Option-Key-0>
5 changes: 4 additions & 1 deletion Lib/idlelib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,9 @@ def IsCoreBinding(self, virtualEvent):
former_extension_events = { # Those with user-configurable keys.
'<<force-open-completions>>', '<<expand-word>>',
'<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
'<<run-module>>', '<<check-module>>', '<<zoom-height>>'}
'<<run-module>>', '<<check-module>>', '<<zoom-height>>',
'<<run-custom>>',
}

def GetCoreKeys(self, keySetName=None):
"""Return dict of core virtual-key keybindings for keySetName.
Expand Down Expand Up @@ -658,6 +660,7 @@ def GetCoreKeys(self, keySetName=None):
'<<flash-paren>>': ['<Control-Key-0>'],
'<<format-paragraph>>': ['<Alt-Key-q>'],
'<<run-module>>': ['<Key-F5>'],
'<<run-custom>>': ['<Shift-Key-F5>'],
'<<check-module>>': ['<Alt-Key-x>'],
'<<zoom-height>>': ['<Alt-Key-2>'],
}
Expand Down
1 change: 1 addition & 0 deletions Lib/idlelib/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ def __init__(self, flist=None, filename=None, key=None, root=None):
scriptbinding = ScriptBinding(self)
text.bind("<<check-module>>", scriptbinding.check_module_event)
text.bind("<<run-module>>", scriptbinding.run_module_event)
text.bind("<<run-custom>>", scriptbinding.run_custom_event)
text.bind("<<do-rstrip>>", self.Rstrip(self).do_rstrip)
ctip = self.Calltip(self)
text.bind("<<try-open-calltip>>", ctip.try_open_calltip_event)
Expand Down
14 changes: 12 additions & 2 deletions Lib/idlelib/help.html
Original file line number Diff line number Diff line change
Expand Up @@ -248,24 +248,34 @@ <h3>Edit menu (Shell and Editor)<a class="headerlink" href="#edit-menu-shell-and
</div>
<div class="section" id="run-menu-editor-window-only">
<span id="index-2"></span><h3>Run menu (Editor window only)<a class="headerlink" href="#run-menu-editor-window-only" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dl class="docutils" id="python-shell">
<dt>Python Shell</dt>
<dd>Open or wake up the Python Shell window.</dd>
</dl>
<dl class="docutils" id="check-module">
<dt>Check Module</dt>
<dd>Check the syntax of the module currently open in the Editor window. If the
module has not been saved IDLE will either prompt the user to save or
autosave, as selected in the General tab of the Idle Settings dialog. If
there is a syntax error, the approximate location is indicated in the
Editor window.</dd>
</dl>
<dl class="docutils" id="run-module">
<dt>Run Module</dt>
<dd>Do Check Module (above). If no error, restart the shell to clean the
<dd>Do <a class="reference internal" href="#check-module"><span class="std std-ref">Check Module</span></a>. If no error, restart the shell to clean the
environment, then execute the module. Output is displayed in the Shell
window. Note that output requires use of <code class="docutils literal notranslate"><span class="pre">print</span></code> or <code class="docutils literal notranslate"><span class="pre">write</span></code>.
When execution is complete, the Shell retains focus and displays a prompt.
At this point, one may interactively explore the result of execution.
This is similar to executing a file with <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">-i</span> <span class="pre">file</span></code> at a command
line.</dd>
</dl>
<dl class="docutils" id="run-custom">
<dt>Run… Customized</dt>
<dd>Same as <a class="reference internal" href="#run-module"><span class="std std-ref">Run Module</span></a>, but run the module with customized
settings. <em>Command Line Arguments</em> extend <a class="reference internal" href="sys.html#sys.argv" title="sys.argv"><code class="xref py py-data docutils literal notranslate"><span class="pre">sys.argv</span></code></a> as if passed
on a command line. The module can be run in the Shell without restarting.</dd>
</dl>
</div>
<div class="section" id="shell-menu-shell-window-only">
<h3>Shell menu (Shell window only)<a class="headerlink" href="#shell-menu-shell-window-only" title="Permalink to this headline"></a></h3>
Expand Down
9 changes: 9 additions & 0 deletions Lib/idlelib/idle_test/htest.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ def _wrapper(parent): # htest #
"The default color scheme is in idlelib/config-highlight.def"
}

CustomRun_spec = {
'file': 'query',
'kwds': {'title': 'Custom Run Args',
'_htest': True},
'msg': "Enter with <Return> or [Ok]. Print valid entry to Shell\n"
"Arguments are parsed into a list\n"
"Close dialog with valid entry, <Escape>, [Cancel], [X]"
}

ConfigDialog_spec = {
'file': 'configdialog',
'kwds': {'title': 'ConfigDialogTest',
Expand Down
105 changes: 78 additions & 27 deletions Lib/idlelib/idle_test/test_query.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Test query, coverage 91%).
"""Test query, coverage 93%).
Non-gui tests for Query, SectionName, ModuleName, and HelpSource use
dummy versions that extract the non-gui methods and add other needed
Expand Down Expand Up @@ -30,11 +30,9 @@ class Dummy_Query:
ok = query.Query.ok
cancel = query.Query.cancel
# Add attributes and initialization needed for tests.
entry = Var()
entry_error = {}
def __init__(self, dummy_entry):
self.entry.set(dummy_entry)
self.entry_error['text'] = ''
self.entry = Var(value=dummy_entry)
self.entry_error = {'text': ''}
self.result = None
self.destroyed = False
def showerror(self, message):
Expand Down Expand Up @@ -80,11 +78,9 @@ class SectionNameTest(unittest.TestCase):
class Dummy_SectionName:
entry_ok = query.SectionName.entry_ok # Function being tested.
used_names = ['used']
entry = Var()
entry_error = {}
def __init__(self, dummy_entry):
self.entry.set(dummy_entry)
self.entry_error['text'] = ''
self.entry = Var(value=dummy_entry)
self.entry_error = {'text': ''}
def showerror(self, message):
self.entry_error['text'] = message

Expand Down Expand Up @@ -115,11 +111,9 @@ class ModuleNameTest(unittest.TestCase):
class Dummy_ModuleName:
entry_ok = query.ModuleName.entry_ok # Function being tested.
text0 = ''
entry = Var()
entry_error = {}
def __init__(self, dummy_entry):
self.entry.set(dummy_entry)
self.entry_error['text'] = ''
self.entry = Var(value=dummy_entry)
self.entry_error = {'text': ''}
def showerror(self, message):
self.entry_error['text'] = message

Expand All @@ -144,9 +138,7 @@ def test_good_module_name(self):
self.assertEqual(dialog.entry_error['text'], '')


# 3 HelpSource test classes each test one function.

orig_platform = query.platform
# 3 HelpSource test classes each test one method.

class HelpsourceBrowsefileTest(unittest.TestCase):
"Test browse_file method of ModuleName subclass of Query."
Expand Down Expand Up @@ -178,17 +170,16 @@ class HelpsourcePathokTest(unittest.TestCase):

class Dummy_HelpSource:
path_ok = query.HelpSource.path_ok
path = Var()
path_error = {}
def __init__(self, dummy_path):
self.path.set(dummy_path)
self.path_error['text'] = ''
self.path = Var(value=dummy_path)
self.path_error = {'text': ''}
def showerror(self, message, widget=None):
self.path_error['text'] = message

orig_platform = query.platform # Set in test_path_ok_file.
@classmethod
def tearDownClass(cls):
query.platform = orig_platform
query.platform = cls.orig_platform

def test_path_ok_blank(self):
dialog = self.Dummy_HelpSource(' ')
Expand Down Expand Up @@ -242,6 +233,56 @@ def test_entry_ok_helpsource(self):
self.assertEqual(dialog.entry_ok(), result)


# 2 CustomRun test classes each test one method.

class CustomRunCLIargsokTest(unittest.TestCase):
"Test cli_ok method of the CustomRun subclass of Query."

class Dummy_CustomRun:
cli_args_ok = query.CustomRun.cli_args_ok
def __init__(self, dummy_entry):
self.entry = Var(value=dummy_entry)
self.entry_error = {'text': ''}
def showerror(self, message):
self.entry_error['text'] = message

def test_blank_args(self):
dialog = self.Dummy_CustomRun(' ')
self.assertEqual(dialog.cli_args_ok(), [])

def test_invalid_args(self):
dialog = self.Dummy_CustomRun("'no-closing-quote")
self.assertEqual(dialog.cli_args_ok(), None)
self.assertIn('No closing', dialog.entry_error['text'])

def test_good_args(self):
args = ['-n', '10', '--verbose', '-p', '/path', '--name']
dialog = self.Dummy_CustomRun(' '.join(args) + ' "my name"')
self.assertEqual(dialog.cli_args_ok(), args + ["my name"])
self.assertEqual(dialog.entry_error['text'], '')


class CustomRunEntryokTest(unittest.TestCase):
"Test entry_ok method of the CustomRun subclass of Query."

class Dummy_CustomRun:
entry_ok = query.CustomRun.entry_ok
entry_error = {}
restartvar = Var()
def cli_args_ok(self):
return self.cli_args

def test_entry_ok_customrun(self):
dialog = self.Dummy_CustomRun()
for restart in {True, False}:
dialog.restartvar.set(restart)
for cli_args, result in ((None, None),
(['my arg'], (['my arg'], restart))):
with self.subTest(restart=restart, cli_args=cli_args):
dialog.cli_args = cli_args
self.assertEqual(dialog.entry_ok(), result)


# GUI TESTS

class QueryGuiTest(unittest.TestCase):
Expand Down Expand Up @@ -302,9 +343,7 @@ def test_click_section_name(self):
dialog.entry.insert(0, 'okay')
dialog.button_ok.invoke()
self.assertEqual(dialog.result, 'okay')
del dialog
root.destroy()
del root


class ModulenameGuiTest(unittest.TestCase):
Expand All @@ -321,9 +360,7 @@ def test_click_module_name(self):
self.assertEqual(dialog.entry.get(), 'idlelib')
dialog.button_ok.invoke()
self.assertTrue(dialog.result.endswith('__init__.py'))
del dialog
root.destroy()
del root


class HelpsourceGuiTest(unittest.TestCase):
Expand All @@ -343,9 +380,23 @@ def test_click_help_source(self):
dialog.button_ok.invoke()
prefix = "file://" if sys.platform == 'darwin' else ''
Equal(dialog.result, ('__test__', prefix + __file__))
del dialog
root.destroy()
del root


class CustomRunGuiTest(unittest.TestCase):

@classmethod
def setUpClass(cls):
requires('gui')

def test_click_args(self):
root = Tk()
root.withdraw()
dialog = query.CustomRun(root, 'Title', _utest=True)
dialog.entry.insert(0, 'okay')
dialog.button_ok.invoke()
self.assertEqual(dialog.result, (['okay'], True))
root.destroy()


if __name__ == '__main__':
Expand Down
1 change: 1 addition & 0 deletions Lib/idlelib/mainmenu.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
('Python Shell', '<<open-python-shell>>'),
('C_heck Module', '<<check-module>>'),
('R_un Module', '<<run-module>>'),
('Run... _Customized', '<<run-custom>>'),
]),

('shell', [
Expand Down
Loading

0 comments on commit 201bc2d

Please sign in to comment.