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 Jun 10, 2024
1 parent 5e3cfc3 commit ef31d96
Show file tree
Hide file tree
Showing 9 changed files with 151 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 system clipboard, using
OSC 52 escape sequence (#311).

### Fixed

Expand Down
9 changes: 9 additions & 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 Expand Up @@ -302,6 +303,14 @@ Information can also be given via PostgreSQL's environment variables
The password file is preferred since it's more secure (security is deferred to
the OS). Please avoid password in connection strings at all cost.

**How to copy/paste the query of focused process?**

The `y` shortcut will copy the query of focused process to system clipboard
using OSC 52 escape sequence. This requires the terminal emulator to support
this escape sequence and set the clipboard accordingly. If so, the copy even
works across remote connections (SSH). In general, terminal emulators supporting
this would use `CTRL+SHIFT+V` to paste from this clipboard.

# Hacking

In order to work on pg\_activity source code, in particular to run the tests
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
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
14 changes: 14 additions & 0 deletions pgactivity/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,20 @@ def toggle_pin_focused(self) -> None:
except KeyError:
self.pinned.add(self.focused)

def copy_focused_query_to_clipboard(self) -> str:
"""Copy focused query to system clipboard using ANSI OSC 52 escape sequence."""
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"

utils.osc52_copy(proc.query)
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
8 changes: 8 additions & 0 deletions pgactivity/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations

import base64
import functools
import re
import sys
from datetime import datetime, timedelta, timezone
from typing import IO, Any, Iterable, Mapping

Expand Down Expand Up @@ -202,6 +204,12 @@ def short_state(state: str) -> str:
}.get(state, state)


def osc52_copy(text: str) -> None:
buffer = sys.__stderr__.buffer
buffer.write(b";".join([b"\033]52", b"c", base64.b64encode(text.encode())]) + b"\a")
buffer.flush()


def csv_write(
fobj: IO[str],
procs: Iterable[Mapping[str, Any]],
Expand Down
110 changes: 110 additions & 0 deletions tests/test_ui.txt
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,116 @@ 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' --------------------------------------------------------------

>>> 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 ef31d96

Please sign in to comment.