mirror of
https://github.com/zellij-org/zellij.git
synced 2024-12-24 17:53:36 +03:00
feat(copy): allow osc52 copy destination configuration (#1022)
add copy_cliboard option to allow configuring copy destination to primary selection instead of default clipboard
This commit is contained in:
parent
2799eb9160
commit
18709acde9
@ -24,7 +24,7 @@ struct State {
|
||||
tabs: Vec<TabInfo>,
|
||||
tip_name: String,
|
||||
mode_info: ModeInfo,
|
||||
diplay_text_copied_hint: bool,
|
||||
text_copy_destination: Option<CopyDestination>,
|
||||
display_system_clipboard_failure: bool,
|
||||
}
|
||||
|
||||
@ -156,14 +156,14 @@ impl ZellijPlugin for State {
|
||||
Event::TabUpdate(tabs) => {
|
||||
self.tabs = tabs;
|
||||
}
|
||||
Event::CopyToClipboard => {
|
||||
self.diplay_text_copied_hint = true;
|
||||
Event::CopyToClipboard(copy_destination) => {
|
||||
self.text_copy_destination = Some(copy_destination);
|
||||
}
|
||||
Event::SystemClipboardFailure => {
|
||||
self.display_system_clipboard_failure = true;
|
||||
}
|
||||
Event::InputReceived => {
|
||||
self.diplay_text_copied_hint = false;
|
||||
self.text_copy_destination = None;
|
||||
self.display_system_clipboard_failure = false;
|
||||
}
|
||||
_ => {}
|
||||
@ -186,64 +186,7 @@ impl ZellijPlugin for State {
|
||||
);
|
||||
|
||||
let first_line = format!("{}{}", superkey, ctrl_keys);
|
||||
|
||||
let mut second_line = LinePart::default();
|
||||
for t in &mut self.tabs {
|
||||
if t.active {
|
||||
match self.mode_info.mode {
|
||||
InputMode::Normal => {
|
||||
if t.is_fullscreen_active {
|
||||
second_line = if self.diplay_text_copied_hint {
|
||||
text_copied_hint(&self.mode_info.palette)
|
||||
} else if self.display_system_clipboard_failure {
|
||||
system_clipboard_error(&self.mode_info.palette)
|
||||
} else {
|
||||
fullscreen_panes_to_hide(&self.mode_info.palette, t.panes_to_hide)
|
||||
}
|
||||
} else {
|
||||
second_line = if self.diplay_text_copied_hint {
|
||||
text_copied_hint(&self.mode_info.palette)
|
||||
} else if self.display_system_clipboard_failure {
|
||||
system_clipboard_error(&self.mode_info.palette)
|
||||
} else {
|
||||
keybinds(&self.mode_info, &self.tip_name, cols)
|
||||
}
|
||||
}
|
||||
}
|
||||
InputMode::Locked => {
|
||||
if t.is_fullscreen_active {
|
||||
second_line = if self.diplay_text_copied_hint {
|
||||
text_copied_hint(&self.mode_info.palette)
|
||||
} else if self.display_system_clipboard_failure {
|
||||
system_clipboard_error(&self.mode_info.palette)
|
||||
} else {
|
||||
locked_fullscreen_panes_to_hide(
|
||||
&self.mode_info.palette,
|
||||
t.panes_to_hide,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
second_line = if self.diplay_text_copied_hint {
|
||||
text_copied_hint(&self.mode_info.palette)
|
||||
} else if self.display_system_clipboard_failure {
|
||||
system_clipboard_error(&self.mode_info.palette)
|
||||
} else {
|
||||
keybinds(&self.mode_info, &self.tip_name, cols)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
second_line = if self.diplay_text_copied_hint {
|
||||
text_copied_hint(&self.mode_info.palette)
|
||||
} else if self.display_system_clipboard_failure {
|
||||
system_clipboard_error(&self.mode_info.palette)
|
||||
} else {
|
||||
keybinds(&self.mode_info, &self.tip_name, cols)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let second_line = self.second_line(cols);
|
||||
|
||||
// [48;5;238m is gray background, [0K is so that it fills the rest of the line
|
||||
// [m is background reset, [0K is so that it clears the rest of the line
|
||||
@ -258,3 +201,32 @@ impl ZellijPlugin for State {
|
||||
println!("\u{1b}[m{}\u{1b}[0K", second_line);
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn second_line(&self, cols: usize) -> LinePart {
|
||||
let active_tab = self.tabs.iter().find(|t| t.active);
|
||||
|
||||
if let Some(copy_destination) = self.text_copy_destination {
|
||||
text_copied_hint(&self.mode_info.palette, copy_destination)
|
||||
} else if self.display_system_clipboard_failure {
|
||||
system_clipboard_error(&self.mode_info.palette)
|
||||
} else if let Some(active_tab) = active_tab {
|
||||
if active_tab.is_fullscreen_active {
|
||||
match self.mode_info.mode {
|
||||
InputMode::Normal => {
|
||||
fullscreen_panes_to_hide(&self.mode_info.palette, active_tab.panes_to_hide)
|
||||
}
|
||||
InputMode::Locked => locked_fullscreen_panes_to_hide(
|
||||
&self.mode_info.palette,
|
||||
active_tab.panes_to_hide,
|
||||
),
|
||||
_ => keybinds(&self.mode_info, &self.tip_name, cols),
|
||||
}
|
||||
} else {
|
||||
keybinds(&self.mode_info, &self.tip_name, cols)
|
||||
}
|
||||
} else {
|
||||
LinePart::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -229,12 +229,19 @@ pub fn keybinds(help: &ModeInfo, tip_name: &str, max_width: usize) -> LinePart {
|
||||
best_effort_shortcut_list(help, tip_body.short, max_width)
|
||||
}
|
||||
|
||||
pub fn text_copied_hint(palette: &Palette) -> LinePart {
|
||||
let hint = " Text copied to clipboard";
|
||||
pub fn text_copied_hint(palette: &Palette, copy_destination: CopyDestination) -> LinePart {
|
||||
let green_color = match palette.green {
|
||||
PaletteColor::Rgb((r, g, b)) => RGB(r, g, b),
|
||||
PaletteColor::EightBit(color) => Fixed(color),
|
||||
};
|
||||
let hint = match copy_destination {
|
||||
CopyDestination::Command => "Text piped to external command",
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
CopyDestination::Primary => "Text copied to primary selection",
|
||||
#[cfg(target_os = "macos")] // primary selection does not exist on macos
|
||||
CopyDestination::Primary => "Text copied to clipboard",
|
||||
CopyDestination::System => "Text copied to clipboard",
|
||||
};
|
||||
LinePart {
|
||||
part: Style::new().fg(green_color).bold().paint(hint).to_string(),
|
||||
len: hint.len(),
|
||||
|
@ -6,6 +6,7 @@ use std::os::unix::io::RawFd;
|
||||
use std::rc::Rc;
|
||||
use std::str;
|
||||
|
||||
use zellij_utils::input::options::Clipboard;
|
||||
use zellij_utils::pane_size::Size;
|
||||
use zellij_utils::{input::layout::Layout, position::Position, zellij_tile};
|
||||
|
||||
@ -194,10 +195,12 @@ pub(crate) struct Screen {
|
||||
draw_pane_frames: bool,
|
||||
session_is_mirrored: bool,
|
||||
copy_command: Option<String>,
|
||||
copy_clipboard: Clipboard,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
/// Creates and returns a new [`Screen`].
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
bus: Bus<ScreenInstruction>,
|
||||
client_attributes: &ClientAttributes,
|
||||
@ -206,6 +209,7 @@ impl Screen {
|
||||
draw_pane_frames: bool,
|
||||
session_is_mirrored: bool,
|
||||
copy_command: Option<String>,
|
||||
copy_clipboard: Clipboard,
|
||||
) -> Self {
|
||||
Screen {
|
||||
bus,
|
||||
@ -222,6 +226,7 @@ impl Screen {
|
||||
draw_pane_frames,
|
||||
session_is_mirrored,
|
||||
copy_command,
|
||||
copy_clipboard,
|
||||
}
|
||||
}
|
||||
|
||||
@ -495,6 +500,7 @@ impl Screen {
|
||||
self.session_is_mirrored,
|
||||
client_id,
|
||||
self.copy_command.clone(),
|
||||
self.copy_clipboard.clone(),
|
||||
);
|
||||
tab.apply_layout(layout, new_pids, tab_index, client_id);
|
||||
if self.session_is_mirrored {
|
||||
@ -700,6 +706,7 @@ pub(crate) fn screen_thread_main(
|
||||
draw_pane_frames,
|
||||
session_is_mirrored,
|
||||
config_options.copy_command,
|
||||
config_options.copy_clipboard.unwrap_or_default(),
|
||||
);
|
||||
loop {
|
||||
let (event, mut err_ctx) = screen
|
||||
|
50
zellij-server/src/tab/clipboard.rs
Normal file
50
zellij-server/src/tab/clipboard.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use zellij_tile::prelude::CopyDestination;
|
||||
use zellij_utils::{anyhow::Result, input::options::Clipboard};
|
||||
|
||||
use crate::ClientId;
|
||||
|
||||
use super::{copy_command::CopyCommand, Output};
|
||||
|
||||
pub(crate) enum ClipboardProvider {
|
||||
Command(CopyCommand),
|
||||
Osc52(Clipboard),
|
||||
}
|
||||
|
||||
impl ClipboardProvider {
|
||||
pub(crate) fn set_content(
|
||||
&self,
|
||||
content: &str,
|
||||
output: &mut Output,
|
||||
client_ids: impl Iterator<Item = ClientId>,
|
||||
) -> Result<()> {
|
||||
match &self {
|
||||
ClipboardProvider::Command(command) => {
|
||||
command.set(content.to_string())?;
|
||||
}
|
||||
ClipboardProvider::Osc52(clipboard) => {
|
||||
let dest = match clipboard {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
Clipboard::Primary => 'p',
|
||||
#[cfg(target_os = "macos")] // primary selection does not exist on macos
|
||||
Clipboard::Primary => 'c',
|
||||
Clipboard::System => 'c',
|
||||
};
|
||||
output.push_str_to_multiple_clients(
|
||||
&format!("\u{1b}]52;{};{}\u{1b}\\", dest, base64::encode(content)),
|
||||
client_ids,
|
||||
);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn as_copy_destination(&self) -> CopyDestination {
|
||||
match self {
|
||||
ClipboardProvider::Command(_) => CopyDestination::Command,
|
||||
ClipboardProvider::Osc52(clipboard) => match clipboard {
|
||||
Clipboard::Primary => CopyDestination::Primary,
|
||||
Clipboard::System => CopyDestination::System,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
use std::io::prelude::*;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use zellij_utils::anyhow::{Context, Result};
|
||||
|
||||
pub struct CopyCommand {
|
||||
command: String,
|
||||
args: Vec<String>,
|
||||
@ -15,25 +17,18 @@ impl CopyCommand {
|
||||
args: command_with_args.collect(),
|
||||
}
|
||||
}
|
||||
pub fn set(&self, value: String) -> bool {
|
||||
let process = match Command::new(self.command.clone())
|
||||
pub fn set(&self, value: String) -> Result<()> {
|
||||
let process = Command::new(self.command.clone())
|
||||
.args(self.args.clone())
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
Err(why) => {
|
||||
eprintln!("couldn't spawn {}: {}", self.command, why);
|
||||
return false;
|
||||
}
|
||||
Ok(process) => process,
|
||||
};
|
||||
.with_context(|| format!("couldn't spawn {}", self.command))?;
|
||||
process
|
||||
.stdin
|
||||
.context("could not get stdin")?
|
||||
.write_all(value.as_bytes())
|
||||
.with_context(|| format!("couldn't write to {} stdin", self.command))?;
|
||||
|
||||
match process.stdin.unwrap().write_all(value.as_bytes()) {
|
||||
Err(why) => {
|
||||
eprintln!("couldn't write to {} stdin: {}", self.command, why);
|
||||
false
|
||||
}
|
||||
Ok(_) => true,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size,
|
||||
//! as well as how they should be resized
|
||||
|
||||
mod clipboard;
|
||||
mod copy_command;
|
||||
mod pane_grid;
|
||||
mod pane_resizer;
|
||||
|
||||
use copy_command::CopyCommand;
|
||||
use zellij_utils::input::options::Clipboard;
|
||||
use zellij_utils::position::{Column, Line};
|
||||
use zellij_utils::{position::Position, serde, zellij_tile};
|
||||
|
||||
@ -41,6 +43,8 @@ use zellij_utils::{
|
||||
pane_size::{Offset, PaneGeom, Size, Viewport},
|
||||
};
|
||||
|
||||
use self::clipboard::ClipboardProvider;
|
||||
|
||||
// FIXME: This should be replaced by `RESIZE_PERCENT` at some point
|
||||
const MIN_TERMINAL_HEIGHT: usize = 5;
|
||||
const MIN_TERMINAL_WIDTH: usize = 5;
|
||||
@ -121,7 +125,7 @@ pub(crate) struct Tab {
|
||||
session_is_mirrored: bool,
|
||||
pending_vte_events: HashMap<RawFd, Vec<VteBytes>>,
|
||||
selecting_with_mouse: bool,
|
||||
copy_command: Option<String>,
|
||||
clipboard_provider: ClipboardProvider,
|
||||
// TODO: used only to focus the pane when the layout is loaded
|
||||
// it seems that optimization is possible using `active_panes`
|
||||
focus_pane_id: Option<PaneId>,
|
||||
@ -306,6 +310,7 @@ impl Tab {
|
||||
session_is_mirrored: bool,
|
||||
client_id: ClientId,
|
||||
copy_command: Option<String>,
|
||||
copy_clipboard: Clipboard,
|
||||
) -> Self {
|
||||
let panes = BTreeMap::new();
|
||||
|
||||
@ -318,6 +323,11 @@ impl Tab {
|
||||
let mut connected_clients = HashSet::new();
|
||||
connected_clients.insert(client_id);
|
||||
|
||||
let clipboard_provider = match copy_command {
|
||||
Some(command) => ClipboardProvider::Command(CopyCommand::new(command)),
|
||||
None => ClipboardProvider::Osc52(copy_clipboard),
|
||||
};
|
||||
|
||||
Tab {
|
||||
index,
|
||||
position,
|
||||
@ -342,7 +352,7 @@ impl Tab {
|
||||
connected_clients_in_app,
|
||||
connected_clients,
|
||||
selecting_with_mouse: false,
|
||||
copy_command,
|
||||
clipboard_provider,
|
||||
focus_pane_id: None,
|
||||
}
|
||||
}
|
||||
@ -1936,7 +1946,7 @@ impl Tab {
|
||||
.send_to_plugin(PluginInstruction::Update(
|
||||
None,
|
||||
None,
|
||||
Event::CopyToClipboard,
|
||||
Event::CopyToClipboard(self.clipboard_provider.as_copy_destination()),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
@ -1944,35 +1954,27 @@ impl Tab {
|
||||
|
||||
fn write_selection_to_clipboard(&self, selection: &str) {
|
||||
let mut output = Output::default();
|
||||
let mut system_clipboard_failure = false;
|
||||
output.add_clients(&self.connected_clients);
|
||||
match self.copy_command.clone() {
|
||||
Some(copy_command) => {
|
||||
let system_clipboard = CopyCommand::new(copy_command);
|
||||
system_clipboard_failure = !system_clipboard.set(selection.to_owned());
|
||||
}
|
||||
None => {
|
||||
output.push_str_to_multiple_clients(
|
||||
&format!("\u{1b}]52;c;{}\u{1b}\\", base64::encode(selection)),
|
||||
self.connected_clients.iter().copied(),
|
||||
);
|
||||
}
|
||||
}
|
||||
let client_ids = self.connected_clients.iter().copied();
|
||||
|
||||
// TODO: ideally we should be sending the Render instruction from the screen
|
||||
self.senders
|
||||
.send_to_server(ServerInstruction::Render(Some(output)))
|
||||
.unwrap();
|
||||
self.senders
|
||||
.send_to_plugin(PluginInstruction::Update(
|
||||
None,
|
||||
None,
|
||||
if system_clipboard_failure {
|
||||
let clipboard_event =
|
||||
match self
|
||||
.clipboard_provider
|
||||
.set_content(selection, &mut output, client_ids)
|
||||
{
|
||||
Ok(_) => {
|
||||
self.senders
|
||||
.send_to_server(ServerInstruction::Render(Some(output)))
|
||||
.unwrap();
|
||||
Event::CopyToClipboard(self.clipboard_provider.as_copy_destination())
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("could not write selection to clipboard: {}", err);
|
||||
Event::SystemClipboardFailure
|
||||
} else {
|
||||
Event::CopyToClipboard
|
||||
},
|
||||
))
|
||||
}
|
||||
};
|
||||
self.senders
|
||||
.send_to_plugin(PluginInstruction::Update(None, None, clipboard_event))
|
||||
.unwrap();
|
||||
}
|
||||
fn is_inside_viewport(&self, pane_id: &PaneId) -> bool {
|
||||
|
@ -9,6 +9,7 @@ use crate::{
|
||||
use std::convert::TryInto;
|
||||
use std::path::PathBuf;
|
||||
use zellij_utils::input::layout::LayoutTemplate;
|
||||
use zellij_utils::input::options::Clipboard;
|
||||
use zellij_utils::ipc::IpcReceiverWithContext;
|
||||
use zellij_utils::pane_size::Size;
|
||||
|
||||
@ -97,6 +98,7 @@ fn create_new_tab(size: Size) -> Tab {
|
||||
connected_clients.insert(client_id);
|
||||
let connected_clients = Rc::new(RefCell::new(connected_clients));
|
||||
let copy_command = None;
|
||||
let copy_clipboard = Clipboard::default();
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
@ -112,6 +114,7 @@ fn create_new_tab(size: Size) -> Tab {
|
||||
session_is_mirrored,
|
||||
client_id,
|
||||
copy_command,
|
||||
copy_clipboard,
|
||||
);
|
||||
tab.apply_layout(
|
||||
LayoutTemplate::default().try_into().unwrap(),
|
||||
|
@ -10,6 +10,7 @@ use std::convert::TryInto;
|
||||
use std::path::PathBuf;
|
||||
use zellij_utils::input::command::TerminalAction;
|
||||
use zellij_utils::input::layout::LayoutTemplate;
|
||||
use zellij_utils::input::options::Clipboard;
|
||||
use zellij_utils::ipc::IpcReceiverWithContext;
|
||||
use zellij_utils::pane_size::Size;
|
||||
|
||||
@ -92,6 +93,7 @@ fn create_new_screen(size: Size) -> Screen {
|
||||
let draw_pane_frames = false;
|
||||
let session_is_mirrored = true;
|
||||
let copy_command = None;
|
||||
let copy_clipboard = Clipboard::default();
|
||||
Screen::new(
|
||||
bus,
|
||||
&client_attributes,
|
||||
@ -100,6 +102,7 @@ fn create_new_screen(size: Size) -> Screen {
|
||||
draw_pane_frames,
|
||||
session_is_mirrored,
|
||||
copy_command,
|
||||
copy_clipboard,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ pub enum Event {
|
||||
Key(Key),
|
||||
Mouse(Mouse),
|
||||
Timer(f64),
|
||||
CopyToClipboard,
|
||||
CopyToClipboard(CopyDestination),
|
||||
SystemClipboardFailure,
|
||||
InputReceived,
|
||||
Visible(bool),
|
||||
@ -267,3 +267,10 @@ impl Default for PluginCapabilities {
|
||||
PluginCapabilities { arrow_fonts: true }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum CopyDestination {
|
||||
Command,
|
||||
Primary,
|
||||
System,
|
||||
}
|
||||
|
@ -477,3 +477,11 @@ plugins:
|
||||
#copy_command: "xclip -selection clipboard" # x11
|
||||
#copy_command: "wl-copy" # wayland
|
||||
#copy_command: "pbcopy" # osx
|
||||
|
||||
# Choose the destination for copied text
|
||||
# Allows using the primary selection buffer (on x11/wayland) instead of the system clipboard.
|
||||
# Does not apply when using copy_command.
|
||||
# Options:
|
||||
# - system (default)
|
||||
# - primary
|
||||
# copy_clipboard: primary
|
||||
|
@ -79,6 +79,25 @@ pub struct Options {
|
||||
#[clap(long)]
|
||||
#[serde(default)]
|
||||
pub copy_command: Option<String>,
|
||||
|
||||
/// OSC52 destination clipboard
|
||||
#[clap(long, arg_enum, ignore_case = true, conflicts_with = "copy-command")]
|
||||
#[serde(default)]
|
||||
pub copy_clipboard: Option<Clipboard>,
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, PartialEq)]
|
||||
pub enum Clipboard {
|
||||
#[serde(alias = "system")]
|
||||
System,
|
||||
#[serde(alias = "primary")]
|
||||
Primary,
|
||||
}
|
||||
|
||||
impl Default for Clipboard {
|
||||
fn default() -> Self {
|
||||
Self::System
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
@ -105,6 +124,7 @@ impl Options {
|
||||
let on_force_close = other.on_force_close.or(self.on_force_close);
|
||||
let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size);
|
||||
let copy_command = other.copy_command.or_else(|| self.copy_command.clone());
|
||||
let copy_clipboard = other.copy_clipboard.or_else(|| self.copy_clipboard.clone());
|
||||
|
||||
Options {
|
||||
simplified_ui,
|
||||
@ -118,6 +138,7 @@ impl Options {
|
||||
on_force_close,
|
||||
scroll_buffer_size,
|
||||
copy_command,
|
||||
copy_clipboard,
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,6 +169,7 @@ impl Options {
|
||||
let on_force_close = other.on_force_close.or(self.on_force_close);
|
||||
let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size);
|
||||
let copy_command = other.copy_command.or_else(|| self.copy_command.clone());
|
||||
let copy_clipboard = other.copy_clipboard.or_else(|| self.copy_clipboard.clone());
|
||||
|
||||
Options {
|
||||
simplified_ui,
|
||||
@ -161,6 +183,7 @@ impl Options {
|
||||
on_force_close,
|
||||
scroll_buffer_size,
|
||||
copy_command,
|
||||
copy_clipboard,
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,6 +233,7 @@ impl From<CliOptions> for Options {
|
||||
on_force_close: opts.on_force_close,
|
||||
scroll_buffer_size: opts.scroll_buffer_size,
|
||||
copy_command: opts.copy_command,
|
||||
copy_clipboard: opts.copy_clipboard,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ pub mod position;
|
||||
pub mod setup;
|
||||
pub mod shared;
|
||||
|
||||
pub use anyhow;
|
||||
pub use async_std;
|
||||
pub use clap;
|
||||
pub use interprocess;
|
||||
|
Loading…
Reference in New Issue
Block a user