Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add "Copy with prompts" and "Copy only code" to shell's context menu too
  • Loading branch information
taleinat committed May 1, 2021
commit 7d1c5f539ef033ccb16a8061bb5803d21ca1ac47
68 changes: 68 additions & 0 deletions Lib/idlelib/pyshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
raise SystemExit(1)

from code import InteractiveInterpreter
import itertools
import linecache
import os
import os.path
Expand Down Expand Up @@ -865,6 +866,16 @@ class PyShell(OutputWindow):
rmenu_specs = OutputWindow.rmenu_specs + [
("Squeeze", "<<squeeze-current-text>>"),
]
_idx = 1 + len(list(itertools.takewhile(
lambda rmenu_item: rmenu_item[0] != "Copy", rmenu_specs)
))
rmenu_specs.insert(_idx, ("Copy with prompts",
"<<copy-with-prompts>>",
"rmenu_check_copy"))
rmenu_specs.insert(_idx + 1, ("Copy only code",
"<<copy-only-code>>",
"rmenu_check_copy"))
del _idx

allow_line_numbers = False
user_input_insert_tags = "stdin"
Expand Down Expand Up @@ -906,6 +917,8 @@ def __init__(self, flist=None):
text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
text.bind("<<toggle-debugger>>", self.toggle_debugger)
text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
text.bind("<<copy-with-prompts>>", self.copy_with_prompts_callback)
text.bind("<<copy-only-code>>", self.copy_only_code_callback)
if use_subprocess:
text.bind("<<view-restart>>", self.view_restart_mark)
text.bind("<<restart-shell>>", self.restart_shell)
Expand Down Expand Up @@ -979,6 +992,61 @@ def replace_event(self, event):
def get_standard_extension_names(self):
return idleConf.GetExtensions(shell_only=True)

def copy_with_prompts_callback(self, event=None):
"""Copy selected lines to the clipboard, with prompts.

This makes the copied text useful for doc-tests and interactive
shell code examples.

This always copies entire lines, even if only part of the first
and/or last lines is selected.
"""
text = self.text

selection_indexes = (
self.text.index("sel.first linestart"),
self.text.index("sel.last +1line linestart"),
)
if selection_indexes[0] is None:
# There is no selection, so do nothing.
return

selected_text = self.text.get(*selection_indexes)
selection_lineno_range = range(
int(float(selection_indexes[0])),
int(float(selection_indexes[1]))
)
prompts = [
self.shell_sidebar.line_prompts.get(lineno)
for lineno in selection_lineno_range
]
selected_text_with_prompts = "\n".join(
line if prompt is None else f"{prompt} {line}"
for prompt, line in zip(prompts, selected_text.splitlines())
) + "\n"

text.clipboard_clear()
text.clipboard_append(selected_text_with_prompts)

def copy_only_code_callback(self, event=None):
"""Copy only the code from the selected text to the clipboard."""
text = self.text

if not text.tag_ranges("sel"):
# There is no selection, so do nothing.
return None

# Find all text with the "stdin" tag in the selected range
code_pieces = []
index = text.index("sel.first")
while tag_range := text.tag_nextrange("stdin", index, "sel.last"):
index = tag_range[1]
code_pieces.append(text.get(*tag_range))

text.clipboard_clear()
text.clipboard_append("\n".join(code_pieces))


reading = False
executing = False
canceled = False
Expand Down
57 changes: 6 additions & 51 deletions Lib/idlelib/sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,65 +442,20 @@ def bind_events(self):
def context_menu_event(self, event):
rmenu = tk.Menu(self.main_widget, tearoff=0)
has_selection = bool(self.text.tag_nextrange('sel', '1.0'))
rmenu.add_command(label='Copy', command=self.rmenu_copy_handler,
def mkcmd(eventname):
return lambda: self.text.event_generate(eventname)
rmenu.add_command(label='Copy',
command=mkcmd('<<copy>>'),
state='normal' if has_selection else 'disabled')
rmenu.add_command(label='Copy with prompts',
command=self.rmenu_copy_with_prompts_handler,
command=mkcmd('<<copy-with-prompts>>'),
state='normal' if has_selection else 'disabled')
rmenu.add_command(label='Copy only code',
command=self.rmenu_copy_only_code_handler,
command=mkcmd('<<copy-only-code>>'),
state='normal' if has_selection else 'disabled')
rmenu.tk_popup(event.x_root, event.y_root)
return "break"

def rmenu_copy_handler(self):
"""Copy selected text to the clipboard."""
selected_text = self.text.get('sel.first', 'sel.last')

self.main_widget.clipboard_clear()
self.main_widget.clipboard_append(selected_text)

def rmenu_copy_with_prompts_handler(self):
"""Copy selected lines to the clipboard, with prompts.

This makes the copied text useful for doc-tests and interactive
shell code examples.

This always copies entire lines, even if only part of the first
and/or last lines is selected.
"""
selected_text = self.text.get('sel.first linestart',
'sel.last +1line linestart')
lineno_range = range(
get_lineno(self.text, 'sel.first linestart'),
get_lineno(self.text, 'sel.last +1line linestart'),
)
prompts = [
self.line_prompts.get(lineno)
for lineno in lineno_range
]
selected_text_with_prompts = '\n'.join(
line if prompt is None else f'{prompt} {line}'
for prompt, line in zip(prompts, selected_text.splitlines())
) + '\n'

self.main_widget.clipboard_clear()
self.main_widget.clipboard_append(selected_text_with_prompts)

def rmenu_copy_only_code_handler(self):
"""Copy only the code from the selected text to the clipboard."""
text = self.text

# Find all text with the 'stdin' tag in the selected range
code_pieces = []
index = text.index('sel.first')
while tag_range := text.tag_nextrange('stdin', index, 'sel.last'):
index = tag_range[1]
code_pieces.append(text.get(*tag_range))

self.main_widget.clipboard_clear()
self.main_widget.clipboard_append('\n'.join(code_pieces))

def grid(self):
self.canvas.grid(row=1, column=0, sticky=tk.NSEW, padx=2, pady=0)

Expand Down