aboutsummaryrefslogtreecommitdiff
path: root/pw_console/py/pw_console/console_app.py
diff options
context:
space:
mode:
Diffstat (limited to 'pw_console/py/pw_console/console_app.py')
-rw-r--r--pw_console/py/pw_console/console_app.py120
1 files changed, 97 insertions, 23 deletions
diff --git a/pw_console/py/pw_console/console_app.py b/pw_console/py/pw_console/console_app.py
index 38f0bfa38..d27ea7308 100644
--- a/pw_console/py/pw_console/console_app.py
+++ b/pw_console/py/pw_console/console_app.py
@@ -14,6 +14,7 @@
"""ConsoleApp control class."""
import asyncio
+import base64
import builtins
import functools
import socketserver
@@ -21,13 +22,16 @@ import importlib.resources
import logging
import os
from pathlib import Path
+import subprocess
import sys
+import tempfile
import time
from threading import Thread
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
from jinja2 import Environment, DictLoader, make_logging_undefined
from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard
+from prompt_toolkit.clipboard import ClipboardData
from prompt_toolkit.layout.menus import CompletionsMenu
from prompt_toolkit.output import ColorDepth
from prompt_toolkit.application import Application
@@ -57,8 +61,9 @@ from ptpython.key_bindings import ( # type: ignore
load_python_bindings,
load_sidebar_bindings,
)
+from pyperclip import PyperclipException # type: ignore
-from pw_console.command_runner import CommandRunner
+from pw_console.command_runner import CommandRunner, CommandRunnerItem
from pw_console.console_log_server import (
ConsoleLogHTTPRequestHandler,
pw_console_http_server,
@@ -504,6 +509,58 @@ class ConsoleApp:
)
return call_function
+ def set_system_clipboard_data(self, data: ClipboardData) -> str:
+ return self.set_system_clipboard(data.text)
+
+ def set_system_clipboard(self, text: str) -> str:
+ """Set the host system clipboard.
+
+ The following methods are attempted in order:
+
+ - The pyperclip package which uses various cross platform methods.
+ - Teminal OSC 52 escape sequence which works on some terminal emulators
+ such as: iTerm2 (MacOS), Alacritty, xterm.
+ - Tmux paste buffer via the load-buffer command. This only happens if
+ pw-console is running inside tmux. You can paste in tmux by pressing:
+ ctrl-b =
+ """
+ copied = False
+ copy_methods = []
+ try:
+ self.application.clipboard.set_text(text)
+
+ copied = True
+ copy_methods.append('system clipboard')
+ except PyperclipException:
+ pass
+
+ # Set the clipboard via terminal escape sequence.
+ b64_data = base64.b64encode(text.encode('utf-8'))
+ sys.stdout.write(f"\x1B]52;c;{b64_data.decode('utf-8')}\x07")
+ _LOG.debug('Clipboard set via teminal escape sequence')
+ copy_methods.append('teminal')
+ copied = True
+
+ if os.environ.get('TMUX'):
+ with tempfile.NamedTemporaryFile(
+ prefix='pw_console_clipboard_',
+ delete=True,
+ ) as clipboard_file:
+ clipboard_file.write(text.encode('utf-8'))
+ clipboard_file.flush()
+ subprocess.run(
+ ['tmux', 'load-buffer', '-w', clipboard_file.name]
+ )
+ _LOG.debug('Clipboard set via tmux load-buffer')
+ copy_methods.append('tmux')
+ copied = True
+
+ message = ''
+ if copied:
+ message = 'Copied to: '
+ message += ', '.join(copy_methods)
+ return message
+
def update_menu_items(self):
self.menu_items = self._create_menu_items()
self.root_container.menu_items = self.menu_items
@@ -521,11 +578,11 @@ class ConsoleApp:
if not self.command_runner_is_open():
self.command_runner.open_dialog()
- def _create_logger_completions(self) -> List[Tuple[str, Callable]]:
- completions: List[Tuple[str, Callable]] = [
- (
- 'root',
- functools.partial(
+ def _create_logger_completions(self) -> List[CommandRunnerItem]:
+ completions: List[CommandRunnerItem] = [
+ CommandRunnerItem(
+ title='root',
+ handler=functools.partial(
self.open_new_log_pane_for_logger, '', window_title='root'
),
),
@@ -535,9 +592,9 @@ class ConsoleApp:
for logger_name in all_logger_names:
completions.append(
- (
- logger_name,
- functools.partial(
+ CommandRunnerItem(
+ title=logger_name,
+ handler=functools.partial(
self.open_new_log_pane_for_logger, logger_name
),
)
@@ -552,15 +609,15 @@ class ConsoleApp:
if not self.command_runner_is_open():
self.command_runner.open_dialog()
- def _create_history_completions(self) -> List[Tuple[str, Callable]]:
+ def _create_history_completions(self) -> List[CommandRunnerItem]:
return [
- (
- description,
- functools.partial(
+ CommandRunnerItem(
+ title=title,
+ handler=functools.partial(
self.repl_pane.insert_text_into_input_buffer, text
),
)
- for description, text in self.repl_pane.history_completions()
+ for title, text in self.repl_pane.history_completions()
]
def open_command_runner_snippets(self) -> None:
@@ -593,16 +650,24 @@ class ConsoleApp:
)
server_thread.start()
- def _create_snippet_completions(self) -> List[Tuple[str, Callable]]:
- completions: List[Tuple[str, Callable]] = [
- (
- description,
- functools.partial(
- self.repl_pane.insert_text_into_input_buffer, text
- ),
+ def _create_snippet_completions(self) -> List[CommandRunnerItem]:
+ completions: List[CommandRunnerItem] = []
+
+ for snippet in self.prefs.snippet_completions():
+ fenced_code = f'```python\n{snippet.code.strip()}\n```'
+ description = '\n' + fenced_code + '\n'
+ if snippet.description:
+ description += '\n' + snippet.description.strip() + '\n'
+ completions.append(
+ CommandRunnerItem(
+ title=snippet.title,
+ handler=functools.partial(
+ self.repl_pane.insert_text_into_input_buffer,
+ snippet.code,
+ ),
+ description=description,
+ )
)
- for description, text in self.prefs.snippet_completions()
- ]
return completions
@@ -1007,6 +1072,15 @@ class ConsoleApp:
self.update_menu_items()
self._update_help_window()
+ def all_log_stores(self) -> List[LogStore]:
+ log_stores: List[LogStore] = []
+ for pane in self.window_manager.active_panes():
+ if not isinstance(pane, LogPane):
+ continue
+ if pane.log_view.log_store not in log_stores:
+ log_stores.append(pane.log_view.log_store)
+ return log_stores
+
def add_log_handler(
self,
window_title: str,