Skip to content

Commit

Permalink
feat: support clipboard copy action (#24)
Browse files Browse the repository at this point in the history
Now we can use key 'y' and 'Y' to copy selected item's name or value to
system clipboard.

This is based on external clipboard helper program in system:

- MacOS: `pbcopy`
- Linux X11: `xclip`
- Linux Wayland: `wl-copy`
- Windows: `clip`

The other system does not support clipboard currently.
  • Loading branch information
fioncat authored Jun 26, 2024
1 parent bbaf9d5 commit 715d1e6
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 21 deletions.
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ description = "A command line tool to view objects (json/yaml/toml) in TUI tree

[dependencies]
anyhow = "1.0.86"
clap = { version = "4.5.4", features = ["derive"] }
clap = { version = "4.5.7", features = ["derive"] }
crossterm = "0.27.0"
dirs = "5.0.1"
humansize = "2.1.3"
once_cell = "1.19.0"
paste = "1.0.15"
ratatui = "0.26.3"
regex = "1.10.4"
regex = "1.10.5"
serde = { version = "1.0.203", features = ["derive"] }
serde_json = { version = "1.0.117", features = ["preserve_order"] }
serde_json = { version = "1.0.118", features = ["preserve_order"] }
serde_yml = "0.0.10"
toml = { version = "0.8.13", features = ["preserve_order"] }
toml = { version = "0.8.14", features = ["preserve_order"] }
tui-tree-widget = { git = "https://github.com/EdJoPaTo/tui-rs-tree-widget.git", rev = "dceb9f87b0afc12695b633d17f7f86690a2ec682" }

[build-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ For how to configure TUI colors, please refer to: [Colors Document](docs/colors.
- [x] Action: Close all opened items (v0.1)
- [ ] Action: Filter items, highlight searching keywords
- [ ] Action: Popup to show help messages
- [ ] Action: Clipboard support, copy current item's content (might need to call external program like `wl-copy`, `pbcopy`)
- [x] Action: Clipboard support, copy current item's content (might need to call external program like `wl-copy`, `pbcopy`) (v0.2)
- [x] Syntax highlighting in data block (v0.2)
- [x] Allow user to customize TUI colors and key bindings (and other things you can imagine) (v0.1)
- [ ] **Filter items! (Like [jnv](https://github.com/ynqa/jnv))**
Expand Down
3 changes: 3 additions & 0 deletions config/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ change_layout = ["v"]
tree_scale_up = ["["]
tree_scale_down = ["]"]
switch = ["<tab>"]
edit = ["e"]
copy_key = ["y"]
copy_value = ["Y"]
quit = ["<ctrl-c>", "q"]

[colors]
Expand Down
2 changes: 2 additions & 0 deletions docs/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
| tree_scale_down | `]` | Scale down tree widget |
| switch | `<tab>` | Switch focus widget |
| edit | `e` | Open current item in editor<br />**(ReadOnly)** |
| copy_key | `y` | Copy current selected item's key name |
| copy_value | `Y` | Copy current selected item's value |
| quit | `<ctrl-c>`, `q` | Quit program |

All available keys:
Expand Down
57 changes: 57 additions & 0 deletions src/clipboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use std::env;
use std::io::{self, Write};
use std::process::{Command, Stdio};

use anyhow::{bail, Context, Result};

fn get_cmd() -> Result<Command> {
let cmd = match env::consts::OS {
"macos" => Command::new("pbcopy"),
"linux" => {
if env::var("WAYLAND_DISPLAY").is_ok() {
Command::new("wl-copy")
} else {
let mut cmd = Command::new("xclip");
cmd.args(["-selection", "clipboard"]);
cmd
}
}
"windows" => Command::new("clip"),
_ => bail!(
"os {} does not support clipboard, you can create issue if you have requirement",
env::consts::OS
),
};
Ok(cmd)
}

pub fn write_clipboard(text: &str) -> Result<()> {
let mut cmd = get_cmd()?;
cmd.stdin(Stdio::piped());

let mut child = match cmd.spawn() {
Ok(child) => child,
Err(err) if err.kind() == io::ErrorKind::NotFound => {
let program = cmd.get_program().to_string_lossy();
bail!("cannot find clipboard program '{program}' in your system, please install it first to support clipboard")
}
Err(err) => return Err(err).context("launch clipboard program failed"),
};

let stdin = child.stdin.as_mut().unwrap();
if let Err(err) = stdin.write_all(text.as_bytes()) {
return Err(err).context("write text to clipboard program");
}
drop(child.stdin.take());

let status = child.wait().context("wait clipboard program done")?;
if !status.success() {
let code = status
.code()
.map(|code| code.to_string())
.unwrap_or("<unknown>".to_string());
bail!("clipboard program exited with bad status {code}",);
}

Ok(())
}
10 changes: 10 additions & 0 deletions src/config/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ pub struct Keys {
#[serde(default = "Keys::default_edit")]
pub edit: Vec<String>,

#[serde(default = "Keys::default_copy_key")]
pub copy_key: Vec<String>,

#[serde(default = "Keys::default_copy_value")]
pub copy_value: Vec<String>,

#[serde(default = "Keys::default_quit")]
pub quit: Vec<String>,

Expand All @@ -269,6 +275,8 @@ generate_keys_default!(
tree_scale_down => ["]"],
switch => ["<tab>"],
edit => ["e"],
copy_key => ["y"],
copy_value => ["Y"],
quit => ["<ctrl-c>", "q"]
);

Expand All @@ -291,6 +299,8 @@ generate_actions!(
tree_scale_down => TreeScaleDown,
switch => Switch,
edit => Edit,
copy_key => CopyKey,
copy_value => CopyValue,
quit => Quit
);

Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod clipboard;
mod cmd;
mod config;
mod edit;
Expand Down
29 changes: 29 additions & 0 deletions src/ui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use ratatui::layout::{Constraint, Layout, Position, Rect};
use ratatui::{Frame, Terminal};
use serde_json::Value;

use crate::clipboard::write_clipboard;
use crate::config::keys::Action;
use crate::config::{Config, LayoutDirection};
use crate::edit::Edit;
Expand Down Expand Up @@ -323,6 +324,21 @@ impl<'a> App<'a> {
};
Refresh::Edit(edit)
}
Action::CopyKey | Action::CopyValue => {
let text = match self.get_copy_text(action) {
Some(text) => text,
None => return Refresh::Skip,
};

if let Err(err) = write_clipboard(&text) {
let message = format!("Failed to copy text to clipboard: {err:#}");
self.popup(message, PopupLevel::Error);
return Refresh::Update;
}

// TODO: When copy success, show a temporary message in footer
Refresh::Update
}
_ => {
// These actions are handled by the focused widget
if match self.focus {
Expand Down Expand Up @@ -444,4 +460,17 @@ impl<'a> App<'a> {
let extension = parser.extension();
Some(Edit::new(self.cfg, identify, data, extension))
}

fn get_copy_text(&self, action: Action) -> Option<String> {
let identify = self.tree_overview.get_selected()?;
let item = self.tree_overview.get_item(identify.as_str())?;

if matches!(action, Action::CopyKey) {
return Some(item.name.clone());
}

let parser = self.tree_overview.get_parser();
let data = parser.to_string(&item.value);
Some(data)
}
}

0 comments on commit 715d1e6

Please sign in to comment.