Drafter.Clipboard (drafter v0.3.2)

Copy Markdown View Source

Read and write the user's clipboard.

Copying

copy/2 writes an OSC 52 sequence to the terminal the session is attached to, and additionally to the local machine's clipboard via pbcopy, clip, wl-copy, xclip or xsel when one of those is on PATH. For a session served over ssh or telnet the sequence reaches the connected client's terminal, so the text lands on the client's clipboard.

Terminals supporting OSC 52 include iTerm2, kitty, WezTerm, foot, Alacritty and xterm; some ship with it disabled. tmux passes the sequence through only with set -g set-clipboard on. The terminal sends no reply, so copy/2 returns :ok once the sequence and any local write have been issued and never reports whether the terminal accepted it.

Pasting

Text the user pastes with their terminal's paste key arrives as a bracketed paste, delivered as a {:bracketed_paste, text} event. A widget receives it by declaring handles: [:paste] and implementing handle_paste/2 — see Drafter.Widget. This is the only path that carries text from a remote client.

paste/0 reads the clipboard of the machine the app process runs on, which for a remote session is the server rather than the connected user. It shells out to a local clipboard tool and never issues an OSC 52 read.

Configuration

config :drafter, clipboard: false

or clipboard: false passed to Drafter.run/2 makes copy/2 and paste/0 no-ops returning {:error, :disabled}, and drops bracketed pastes before any widget sees them. A keyword list enables the two directions separately: clipboard: [copy: true, paste: false]. Key bindings are configured with :clipboard_keys — see key/1.

Summary

Functions

Put text on the clipboard.

The bindings used when :clipboard_keys says nothing.

Whether this run may write to the user's clipboard.

The key bound to action, as {key, modifiers}, or nil if unbound.

Whether key and mods are the binding for action.

The OSC 52 sequence that puts text on target.

The clipboard contents of the machine this app process is running on.

Whether pasted text is delivered to this run at all.

Strip control characters from a pasted string.

Types

target()

@type target() :: :clipboard | :primary

Functions

copy(text, opts \\ [])

@spec copy(
  String.t(),
  keyword()
) :: :ok | {:error, :disabled | :too_large}

Put text on the clipboard.

Options:

  • :target:clipboard (default) or :primary, the X11 primary selection.

Returns :ok once the sequence has been issued, {:error, :disabled} when copy is switched off, and {:error, :too_large} when text exceeds 74994 bytes, the most an OSC 52 sequence carries.

default_keys()

@spec default_keys() :: keyword()

The bindings used when :clipboard_keys says nothing.

enabled?()

@spec enabled?() :: boolean()

Whether this run may write to the user's clipboard.

key(action)

@spec key(atom()) :: {term(), [atom()]} | nil

The key bound to action, as {key, modifiers}, or nil if unbound.

Actions are :copy, :cut, :paste and :select_all, bound by default to ctrl+c, ctrl+x, ctrl+v and ctrl+a. Override them per run with config :drafter, clipboard_keys: [copy: {:y, [:ctrl]}], or unbind an action by giving it false.

These bindings are not installed framework-wide: a widget must call this function (or key?/3) to act on them, so a widget that wants ctrl+c for something else simply does not ask.

key?(action, key, mods)

@spec key?(atom(), term(), [atom()]) :: boolean()

Whether key and mods are the binding for action.

osc52(text, target \\ :clipboard)

@spec osc52(String.t(), target()) :: binary()

The OSC 52 sequence that puts text on target.

Exposed for callers that manage their own terminal output.

paste()

@spec paste() :: {:ok, String.t()} | {:error, :disabled | :unavailable}

The clipboard contents of the machine this app process is running on.

For a remote session that is the server, not the connected user; the user's own clipboard arrives as a bracketed paste instead.

Returns {:ok, text}, {:error, :unavailable} when no clipboard tool is on PATH or the tool fails, and {:error, :disabled} when enabled?/0 is false.

paste_enabled?()

@spec paste_enabled?() :: boolean()

Whether pasted text is delivered to this run at all.

When false, a bracketed paste is dropped before any widget or app sees it. Set independently of enabled?/0 via config :drafter, clipboard: [paste: false].

sanitize(text)

@spec sanitize(String.t()) :: String.t()

Strip control characters from a pasted string.

Carriage returns and CRLF pairs become newlines. Newlines and tabs survive; every other codepoint below 0x20, and DEL (0x7F), is removed. Call this on any text taken from a paste before acting on it, so that escape sequences the text carries cannot be interpreted as keystrokes.