bpo-37903: IDLE: add shell sidebar mouse interactions (GH-25708) · python/cpython@b43cc31
@@ -33,6 +33,7 @@
3333raise SystemExit(1)
34343535from code import InteractiveInterpreter
36+import itertools
3637import linecache
3738import os
3839import os.path
@@ -865,6 +866,13 @@ class PyShell(OutputWindow):
865866rmenu_specs = OutputWindow.rmenu_specs + [
866867 ("Squeeze", "<<squeeze-current-text>>"),
867868 ]
869+_idx = 1 + len(list(itertools.takewhile(
870+lambda rmenu_item: rmenu_item[0] != "Copy", rmenu_specs)
871+ ))
872+rmenu_specs.insert(_idx, ("Copy with prompts",
873+"<<copy-with-prompts>>",
874+"rmenu_check_copy"))
875+del _idx
868876869877allow_line_numbers = False
870878user_input_insert_tags = "stdin"
@@ -906,6 +914,7 @@ def __init__(self, flist=None):
906914text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
907915text.bind("<<toggle-debugger>>", self.toggle_debugger)
908916text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
917+text.bind("<<copy-with-prompts>>", self.copy_with_prompts_callback)
909918if use_subprocess:
910919text.bind("<<view-restart>>", self.view_restart_mark)
911920text.bind("<<restart-shell>>", self.restart_shell)
@@ -979,6 +988,42 @@ def replace_event(self, event):
979988def get_standard_extension_names(self):
980989return idleConf.GetExtensions(shell_only=True)
981990991+def copy_with_prompts_callback(self, event=None):
992+"""Copy selected lines to the clipboard, with prompts.
993+994+ This makes the copied text useful for doc-tests and interactive
995+ shell code examples.
996+997+ This always copies entire lines, even if only part of the first
998+ and/or last lines is selected.
999+ """
1000+text = self.text
1001+1002+selection_indexes = (
1003+self.text.index("sel.first linestart"),
1004+self.text.index("sel.last +1line linestart"),
1005+ )
1006+if selection_indexes[0] is None:
1007+# There is no selection, so do nothing.
1008+return
1009+1010+selected_text = self.text.get(*selection_indexes)
1011+selection_lineno_range = range(
1012+int(float(selection_indexes[0])),
1013+int(float(selection_indexes[1]))
1014+ )
1015+prompts = [
1016+self.shell_sidebar.line_prompts.get(lineno)
1017+for lineno in selection_lineno_range
1018+ ]
1019+selected_text_with_prompts = "\n".join(
1020+line if prompt is None else f"{prompt} {line}"
1021+for prompt, line in zip(prompts, selected_text.splitlines())
1022+ ) + "\n"
1023+1024+text.clipboard_clear()
1025+text.clipboard_append(selected_text_with_prompts)
1026+9821027reading = False
9831028executing = False
9841029canceled = False