From 0de84eaf47420634f414a6680f1fa1b3ae2c0898 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Mon, 19 Jan 2026 22:06:04 +0100 Subject: [PATCH] Shortcut to copy currently selected query to clipboard Fixes: https://github.com/azat/chdig/issues/209 Co-Authored-By: Claude --- Cargo.lock | 143 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + Documentation/Actions.md | 1 + src/view/queries_view.rs | 34 ++++++++++ 4 files changed, 179 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index cc726f8..ef07e50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,23 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + [[package]] name = "arraydeque" version = "0.4.5" @@ -212,6 +229,7 @@ name = "chdig" version = "26.1.1" dependencies = [ "anyhow", + "arboard", "backtrace", "chrono", "chrono-tz", @@ -369,6 +387,15 @@ dependencies = [ "cc", ] +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -651,6 +678,16 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -725,6 +762,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "fastrand" version = "2.3.0" @@ -897,6 +940,16 @@ dependencies = [ "thread_local", ] +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.3", + "windows-link", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -1385,6 +1438,79 @@ dependencies = [ "libc", ] +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-graphics", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + [[package]] name = "object" version = "0.37.3" @@ -2700,6 +2826,23 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "gethostname", + "rustix 1.1.3", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + [[package]] name = "xi-unicode" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index e4148a7..dcb2caf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ cursive-syntect = { version = "*", default-features = true } unicode-width = "0.1" cursive-flexi-logger-view = { git = "https://github.com/azat-rust/cursive-flexi-logger-view", branch = "next", default-features = false } syntect = { version = "*", default-features = false, features = ["default-syntaxes", "default-themes"] } +arboard = { version = "*", default-features = false } clickhouse-rs = { git = "https://github.com/azat-rust/clickhouse-rs", branch = "next", default-features = false, features = ["tokio_io"] } tokio = { version = "*", default-features = false, features = ["macros"] } # Flamegraphs diff --git a/Documentation/Actions.md b/Documentation/Actions.md index 33116b9..a7fa42c 100644 --- a/Documentation/Actions.md +++ b/Documentation/Actions.md @@ -52,6 +52,7 @@ Here is a list of available shortcuts | | | Show live flamegraph in speedscope | | | **Alt+E** | Edit query and execute | | | **S** | Show query | +| | **y** | Copy query to clipboard | | | **s** | `EXPLAIN SYNTAX` | | | **e** | `EXPLAIN PLAN` | | | **E** | `EXPLAIN PIPELINE` | diff --git a/src/view/queries_view.rs b/src/view/queries_view.rs index dcd468c..d76c4ba 100644 --- a/src/view/queries_view.rs +++ b/src/view/queries_view.rs @@ -193,6 +193,8 @@ pub struct QueriesView { filter: Arc>, // Number of queries to render limit: Arc>, + // Keep clipboard alive so X11 clipboard manager can persist the data + clipboard: Option, #[allow(unused)] bg_runner: BackgroundRunner, @@ -555,6 +557,36 @@ impl QueriesView { Ok(Some(EventResult::consumed())) } + fn action_copy_query(&mut self) -> Result> { + let selected_query = self.get_selected_query()?; + let query = selected_query.original_query.clone(); + + match arboard::Clipboard::new() { + Ok(mut clipboard) => { + if let Err(e) = clipboard.set_text(query) { + return Ok(Some(EventResult::Consumed(Some(Callback::from_fn_once( + move |siv| { + siv.add_layer(Dialog::info(format!( + "Failed to copy to clipboard: {}", + e + ))); + }, + ))))); + } + self.clipboard = Some(clipboard); + } + Err(e) => { + return Ok(Some(EventResult::Consumed(Some(Callback::from_fn_once( + move |siv| { + siv.add_layer(Dialog::info(format!("Failed to access clipboard: {}", e))); + }, + ))))); + } + } + + Ok(Some(EventResult::consumed())) + } + fn action_explain_syntax(&mut self) -> Result> { let selected_query = self.get_selected_query()?; let query = selected_query.original_query.clone(); @@ -981,6 +1013,7 @@ impl QueriesView { is_system_processes, filter, limit, + clipboard: None, bg_runner, }; @@ -1024,6 +1057,7 @@ impl QueriesView { add_action!(context, &mut event_view, "Query events flamegraph", action_show_flamegraph(true, Some(TraceType::ProfileEvents))); add_action!(context, &mut event_view, "Edit query and execute", Event::AltChar('E'), action_edit_query_and_execute); add_action!(context, &mut event_view, "Show query", 'S', action_show_query); + add_action!(context, &mut event_view, "Copy query to clipboard", 'y', action_copy_query); add_action!(context, &mut event_view, "EXPLAIN SYNTAX", 's', action_explain_syntax); add_action!(context, &mut event_view, "EXPLAIN PLAN", 'e', action_explain_plan); add_action!(context, &mut event_view, "EXPLAIN PIPELINE", 'E', action_explain_pipeline);