"""Need to do redo :mod:`IPython.lib._clipboard` because it doesn't work.
`%paste` or `%cpaste` doesn't work on Termux and there's no built-in
customizability.
Let's re-implement it as an `abstract factory
<https://en.wikipedia.org/wiki/Abstract_factory_pattern>`_.
"""
import platform
import shutil
import subprocess
from os import environ
from typing import TYPE_CHECKING, AnyStr
from IPython.core.magic import line_magic, Magics, magics_class
from IPython.core.error import TryNext
from IPython.core.getipython import get_ipython
from prompt_toolkit.clipboard.base import Clipboard, ClipboardData
from prompt_toolkit.clipboard.in_memory import InMemoryClipboard
try:
import pyperclip
except ImportError:
PyperclipClipboard = None
clipboard = None
else:
from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard
try:
import win32clipboard
except ImportError:
win32clipboard = None
[docs]class ClipboardEmpty(ValueError):
pass
[docs]class WindowsClipboard(Clipboard):
"""Creates a prompt_toolkit compatible implementation of a _clipboard.
Notes
------
Requires Mark Hammond's pywin32 extensions.
"""
[docs] def __init__(self, _clipboard=None, *args, **kwargs):
"""Open a _clipboard on windows with win32clipboard.OpenClipboard.
Raises
------
:exc:`TryNext`
If win32clipboard can't be imported.
"""
if win32clipboard is None:
print(
"Getting text from the _clipboard requires the pywin32 "
"extensions: http://sourceforge.net/projects/pywin32/"
)
win32clipboard.OpenClipboard()
self.clipboard = _clipboard
super(WindowsClipboard, self).__init__(*args, **kwargs)
[docs] def win_clip_pywin32(self):
"""Utilize pywin32 to get the user's text.
:return:
:rtype:
"""
try:
text = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)
except (TypeError, win32clipboard.error):
try:
text = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT)
except (TypeError, win32clipboard.error):
raise ClipboardEmpty
finally:
win32clipboard.CloseClipboard()
return text
[docs] def win32_clipboard_get(self):
"""Get the current _clipboard's text on Windows.
Runs :meth:`win_clip_pywin32` and if there's any exception
attempts to run :command:`win32yank` through a piped subprocess.
Returns
-------
Text as returned by win32clipboard.GetClipboardData or None.
"""
try:
return self.win_clip_pywin32()
except ClipboardEmpty:
return
except Exception: # noqa
return subprocess.run(
["win32yank", "-o", "lf"], stdout=subprocess.PIPE
).stdout
def __call__(self):
self.win32_clipboard_get()
# store the clipboarddata and put it on the stack
[docs] def rotate(self):
raise
[docs] def get_text(self):
return self.win32_clipboard_get()
[docs]def tkinter_clipboard_get():
"""Get the _clipboard's text using Tkinter.
This is the default on systems that are not Windows or OS X. It may
interfere with other UI toolkits and should be replaced with an
implementation that uses that toolkit.
Notes
--------
Requires :mod:`tkinter`.
Raises
------
:exc:`ClipboardEmpty`
"""
try:
from tkinter import Tk, TclError
except ImportError:
raise TryNext(
"Getting text from the _clipboard on this platform requires tkinter."
)
root = Tk()
root.withdraw()
try:
text = root.clipboard_get()
except TclError:
raise ClipboardEmpty
finally:
root.destroy()
return text
[docs]@magics_class
class ClipboardMagics(Magics):
"""Haven't seen it implemented in a different way than this."""
[docs] def __init__(self, shell=None, *args, **kwargs):
"""Bind the IPython instance and it's config and parent attributes."""
self.shell = shell or get_ipython()
if self.shell is not None:
if getattr(self.shell, "config", None):
self.config = self.shell.config
else:
self.config = None
if getattr(self.shell, "parent", None):
self.parent = self.shell.parent
else:
self.parent = None
super().__init__(*args, **kwargs)
def __repr__(self):
return "<{}>:".format(self.__class__.__name__)
[docs] def load_ipython_extension(self):
"""Sep 20, 2019: Works!"""
self.shell.set_hook("clipboard_get", self.termux_clipboard_get)
[docs] @line_magic
def termux_clipboard_get(self):
if not shutil.which("termux-_clipboard-get"):
return
p = subprocess.run(["termux-_clipboard-get"], stdout=subprocess.PIPE)
text = p.stdout
return text
[docs] @line_magic
def pyperclip_magic(self):
try:
# This is what you were looking for.
from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard
except ModuleNotFoundError:
# womp
print("pyperclip not imported.")
else:
self.shell.pt_app.clipboard = PyperclipClipboard()
[docs]class UsefulClipboard(Clipboard):
"""Clipboard class that can dynamically returns any Clipboard.
Uses more functionally applicable defaults and requires less boilerplate.
"""
[docs] def __init__(self, clipboard=None):
self._environ = environ.copy()
self.shell = get_ipython()
if clipboard is None:
try:
self._clipboard = self.pyperclip()
except ClipboardEmpty:
self._clipboard = self.load()
else:
self._clipboard = clipboard
[docs] def load(self):
"""TODO: This actually isn't gonna work.
We need to implement each individual function above
as a class that meets the required API for a Clipboard class aka
has methods set_data, set_text, rotate, and get_data.
In addition it must be callable. Jesus.
"""
# self.shell.set_hook("clipboard_get", termux_clipboard_get)
if platform.platform().startswith("Win"):
clipboard = WindowsClipboard()
elif platform.platform().startswith("Linux"):
if not self._environ.get("DISPLAY"):
clipboard = self.termux_clipboard_get()
else:
clipboard = tkinter_clipboard_get()
else:
clipboard = InMemoryClipboard()
return clipboard
[docs] def set_data(self, data):
self._clipboard().set_data(data)
[docs] def set_text(self, text):
self._clipboard().set_text(text)
[docs] def rotate(self):
self._clipboard().rotate()
[docs] def get_data(self) -> ClipboardData:
return self._clipboard().get_data()
[docs] def get_text(self) -> AnyStr:
"""Return the text on the _clipboard."""
return self.get_data().text
def __call__(self):
return self.get_data()
def __repr__(self):
return f"{self.__class__.__name__}"
def __len__(self):
"""Return the length of _clipboard data on the _clipboard."""
return len(self.get_text())
[docs] def termux_clipboard_get(self):
if not shutil.which("termux-_clipboard-get"):
return
p = subprocess.run(["termux-_clipboard-get"], stdout=subprocess.PIPE)
text = p.stdout
return text
[docs] def pyperclip(self):
try:
# This is what you were looking for.
from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard
except ModuleNotFoundError:
# womp
raise ClipboardEmpty
# else:
# self.shell.pt_app._clipboard = PyperclipClipboard()
[docs]def setup_clipboard():
"""
:rtype: object
"""
ipy = get_ipython()
if ipy is not None:
# Because this occasionally happens and I have no idea why
# qtconsole
if not hasattr(ipy, "pt_app"):
if isinstance(ipy, ZMQInteractiveShell):
return
# idk why this one happen tho
elif ipy.pt_app is None:
breakpoint()
# only commented because ipy.pt_app.app._clipboard exists too
# if PyperclipClipboard is not None:
# ipy.pt_app._clipboard = PyperclipClipboard()
# else:
# ipy.pt_app._clipboard = InMemoryClipboard()
if __name__ == "__main__":
setup_clipboard()