Skip to content

Commit

Permalink
Add a 'y' command to copy focused query to clipboard
Browse files Browse the repository at this point in the history
  • Loading branch information
dlax committed May 28, 2024
1 parent e5dd8ed commit 5080f0d
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
* Add non-negative counterparts of many `--no-...` command-line option, thus
allowing to enable respective feature/behaviour even if disabled in the
configuration. (Requires Python 3.9 or higher.)
* Add a `y` command to copy focused query to the clipboard, using
[pyperclip](https://pypi.org/project/pyperclip/) (#311).

### Fixed

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ bytes). If your SQL query text look truncated, you should increase
| `c` | Sort by CPU%, descending |
| `m` | Sort by MEM%, descending |
| `t` | Sort by TIME+, descending |
| `y` | Copy focused query to clipboard |
| `T` | Change duration mode: query, transaction, backend |
| `Space` | Pause on/off |
| `v` | Change queries display mode: full, indented, truncated |
Expand Down
2 changes: 2 additions & 0 deletions docs/man/pg_activity.1
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,8 @@ See: https://www.postgresql.org/docs/current/libpq\-envars.html
.IX Item "m Sort by MEM%, descending."
.IP "\fBt\fR Sort by \s-1TIME+,\s0 descending." 2
.IX Item "t Sort by TIME+, descending."
.IP "\fBy\fR Copy focused query to clipboard." 2
.IX Item "y Copy focused query to clipboard."
.IP "\fBT\fR Change duration mode: query, transaction, backend." 2
.IX Item "T Change duration mode: query, transaction, backend."
.IP "\fBSpace\fR Pause on/off." 2
Expand Down
2 changes: 2 additions & 0 deletions docs/man/pg_activity.pod
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,8 @@ See: https://www.postgresql.org/docs/current/libpq-envars.html

=item B<t> Sort by TIME+, descending.

=item B<y> Copy focused query to clipboard.

=item B<T> Change duration mode: query, transaction, backend.

=item B<Space> Pause on/off.
Expand Down
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
files = pgactivity
show_error_codes = true
strict = true

[mypy-pyperclip]
ignore_missing_imports = true
1 change: 1 addition & 0 deletions pgactivity/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __eq__(self, other: Any) -> bool:
EXIT = "q"
HELP = "h"
SPACE = " "
COPY_TO_CLIPBOARD = "y"
PROCESS_CANCEL = "C"
PROCESS_KILL = "K"
PROCESS_FIRST = "KEY_HOME"
Expand Down
21 changes: 21 additions & 0 deletions pgactivity/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@

import enum
import functools
import logging
from datetime import timedelta
from ipaddress import IPv4Address, IPv6Address
from typing import Any, Tuple, TypeVar, Union, overload

import attr
import psutil
import pyperclip
from attr import validators

from . import colors, compat, pg, utils
from .compat import Callable, Iterable, Iterator, Mapping, MutableSet, Sequence
from .config import Configuration, Flag, HeaderSection, UISection

logger = logging.getLogger("pgactivity")


class Pct(float):
"""Used to distinguish percentage from float when displaying the header"""
Expand Down Expand Up @@ -1168,6 +1172,23 @@ def toggle_pin_focused(self) -> None:
except KeyError:
self.pinned.add(self.focused)

def copy_focused_query_to_clipboard(self) -> str:
assert self.focused is not None
for proc in self.items:
if proc.pid == self.focused:
break
else:
return "no focused process found"
if proc.query is None:
return "process has no query"
else:
try:
pyperclip.copy(proc.query)
except pyperclip.PyperclipException as exc:
logger.error(str(exc))
return "failed to copy to clipboard"
return f"query of process {proc.pid} copied to clipboard"


ActivityStats = Union[
Iterable[WaitingProcess],
Expand Down
3 changes: 3 additions & 0 deletions pgactivity/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ def main(
ui.start_interactive()
elif key == keys.SPACE:
pg_procs.toggle_pin_focused()
elif key == keys.COPY_TO_CLIPBOARD:
msg = pg_procs.copy_focused_query_to_clipboard()
msg_pile.send(msg)
elif key.name == keys.CANCEL_SELECTION:
pg_procs.reset()
ui.end_interactive()
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ dependencies = [
"humanize >= 0.5.1",
"importlib_metadata; python_version < '3.8'",
"psutil >= 2.0.0",
"pyperclip",
]

[project.optional-dependencies]
Expand Down
114 changes: 114 additions & 0 deletions tests/test_ui.txt
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,120 @@ Interactive mode:
(Note: we patch boxed() widget to disable border in order to make output
independent of the number of digits in PIDs.)

>>> keys = ["j", "j", "y", "q"]
>>> with patch.object(
... widgets, "boxed", new=functools.partial(widgets.boxed, border=False),
... ):
... run_ui(options, keys, render_footer=True, render_header=False, width=140) # doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE
RUNNING QUERIES
PID DATABASE state Query
... tests idle in trans SELECT 42
... tests idle in trans SELECT 43
... tests idle in trans UPDATE t SET s = 'blocking'
... tests active UPDATE t SET s = 'waiting'
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
F1/1 Running queries F2/2 Waiting queries F3/3 Blocking queries Space Pause/unpause q Quit h Help
------------------------------------------------------------- sending key 'j' --------------------------------------------------------------
RUNNING QUERIES
PID DATABASE state Query
... tests idle in trans SELECT 42
... tests idle in trans SELECT 43
... tests idle in trans UPDATE t SET s = 'blocking'
... tests active UPDATE t SET s = 'waiting'
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
C Cancel current query K Terminate underlying ses Space Tag/untag current qu Other Back to activities q Quit
------------------------------------------------------------- sending key 'j' --------------------------------------------------------------
RUNNING QUERIES
PID DATABASE state Query
... tests idle in trans SELECT 43
... tests idle in trans UPDATE t SET s = 'blocking'
... tests active UPDATE t SET s = 'waiting'
... tests idle in trans SELECT 42
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
C Cancel current query K Terminate underlying ses Space Tag/untag current qu Other Back to activities q Quit
------------------------------------------------------------- sending key 'y' --------------------------------------------------------------
RUNNING QUERIES
PID DATABASE state Query
... tests idle in trans SELECT 43
... tests idle in trans UPDATE t SET s = 'blocking'
... tests active UPDATE t SET s = 'waiting'
... tests idle in trans SELECT 42
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
query of process ... copied to clipboard
------------------------------------------------------------- sending key 'q' --------------------------------------------------------------

>>> import pyperclip
>>> pyperclip.paste()
'SELECT 43'

>>> key_down = {"ucs": "KEY_DOWN", "name": "KEY_DOWN"}
>>> keys = [key_down, 2, 3, 1, key_down, "C", "n", "K", "y",
... key_down, " ", "j", " ", "K", "y", "q"]
Expand Down

0 comments on commit 5080f0d

Please sign in to comment.