X Tutup
Skip to content

Commit 5bf22e4

Browse files
committed
Implement copy to clipboard (fixes bpython#234)
Based on a patch by Keith Dart Signed-off-by: Sebastian Ramacher <sebastian+dev@ramacher.at>
1 parent 83b80ae commit 5bf22e4

File tree

6 files changed

+93
-0
lines changed

6 files changed

+93
-0
lines changed

bpython/cli.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,10 @@ def p_key(self, key):
962962
self.pastebin()
963963
return ''
964964

965+
elif key in key_dispatch[config.copy_clipboard_key]:
966+
self.copy2clipboard()
967+
return ''
968+
965969
elif key in key_dispatch[config.last_output_key]:
966970
page(self.stdout_hist[self.prev_block_finished:-4])
967971
return ''

bpython/clipboard.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# The MIT License
2+
#
3+
# Copyright (c) 2015 the bpython authors.
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
23+
import subprocess
24+
import os
25+
import platform
26+
from locale import getpreferredencoding
27+
28+
class CopyFailed(Exception):
29+
pass
30+
31+
class XClipboard(object):
32+
"""Manage clipboard with xclip."""
33+
34+
def copy(self, content):
35+
process = subprocess.Popen(['xclip', '-i', '-selection', 'clipboard'],
36+
stdin=subprocess.PIPE)
37+
process.communicate(content.encode(getpreferredencoding()))
38+
if process.returncode != 0:
39+
raise CopyFailed()
40+
41+
class OSXClipboard(object):
42+
"""Manage clipboard with pbcopy."""
43+
44+
def copy(self, content):
45+
process = subprocess.Popen(['pbcopy', 'w'], stdin=subprocess.PIPE)
46+
process.communicate(content.encode(getpreferredencoding()))
47+
if process.returncode != 0:
48+
raise CopyFailed()
49+
50+
def command_exists(command):
51+
process = subprocess.Popen(['which', command], stderr=subprocess.STDOUT,
52+
stdout=subprocess.PIPE)
53+
process.communicate()
54+
55+
return process.returncode == 0
56+
57+
def get_clipboard():
58+
"""Get best clipboard handling implemention for current system."""
59+
60+
if platform.system() == 'Darwin':
61+
if command_exists('pbcopy'):
62+
return OSXClipboard()
63+
if platform.system() in ('Linux', 'FreeBSD', 'OpenBSD') and os.getenv('DISPLAY') is not None:
64+
if command_exists('xclip'):
65+
return XClipboard()
66+
67+
return None

bpython/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def loadini(struct, configfile):
8282
'edit_current_block': 'C-x',
8383
'help': 'F1',
8484
'last_output': 'F9',
85+
'copy_clipboard': 'F10',
8586
'pastebin': 'F8',
8687
'save': 'C-s',
8788
'show_source': 'F2',
@@ -139,6 +140,7 @@ def get_key_no_doublebind(attr, already_used={}):
139140
struct.flush_output = config.getboolean('general', 'flush_output')
140141

141142
struct.pastebin_key = get_key_no_doublebind('pastebin')
143+
struct.copy_clipboard_key = get_key_no_doublebind('copy_clipboard')
142144
struct.save_key = get_key_no_doublebind('save')
143145
struct.search_key = get_key_no_doublebind('search')
144146
struct.show_source_key = get_key_no_doublebind('show_source')

bpython/curtsiesfrontend/interaction.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def confirm(self, q):
143143
self.prompt = q
144144
self.in_confirm = True
145145
return self.main_greenlet.switch(q)
146+
146147
def file_prompt(self, s):
147148
"""Expected to return a file name, given """
148149
self.request_greenlet = greenlet.getcurrent()

bpython/curtsiesfrontend/repl.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,8 @@ def process_key_event(self, e):
547547
greenlet.greenlet(self.write2file).switch()
548548
elif e in key_dispatch[self.config.pastebin_key]: # F8 for pastebin
549549
greenlet.greenlet(self.pastebin).switch()
550+
elif e in key_dispatch[self.config.copy_clipboard_key]:
551+
greenlet.greenlet(self.copy2clipboard).switch()
550552
elif e in key_dispatch[self.config.external_editor_key]:
551553
self.send_session_to_external_editor()
552554
elif e in key_dispatch[self.config.edit_config_key]:

bpython/repl.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
from bpython._py3compat import PythonLexer, py3
4949
from bpython.formatter import Parenthesis
5050
from bpython.translations import _
51+
from bpython.clipboard import get_clipboard, CopyFailed
5152
import bpython.autocomplete as autocomplete
5253

5354

@@ -446,6 +447,7 @@ def __init__(self, interp, config):
446447
# Necessary to fix mercurial.ui.ui expecting sys.stderr to have this
447448
# attribute
448449
self.closed = False
450+
self.clipboard = get_clipboard()
449451

450452
pythonhist = os.path.expanduser(self.config.hist_file)
451453
if os.path.exists(pythonhist):
@@ -768,6 +770,21 @@ def write2file(self):
768770
else:
769771
self.interact.notify('Saved to %s.' % (fn, ))
770772

773+
def copy2clipboard(self):
774+
"""Copy current content to clipboard."""
775+
776+
if self.clipboard is None:
777+
self.interact.notify(_('No clipboard available.'))
778+
return
779+
780+
content = self.formatforfile(self.getstdout())
781+
try:
782+
self.clipboard.copy(content)
783+
except CopyFailed:
784+
self.interact.notify(_('Could not copy to clipboard.'))
785+
else:
786+
self.interact.notify(_('Copied content to clipboard.'))
787+
771788
def pastebin(self, s=None):
772789
"""Upload to a pastebin and display the URL in the status bar."""
773790

0 commit comments

Comments
 (0)
X Tutup