Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 0 additions & 26 deletions Cargo.lock

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

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ image = { version = "0.25.10", default-features = false, features = [

[dev-dependencies]
rstest = "0.26.1"
serial_test = "4.0.1"

[lints.rust]
# Code quality
Expand Down
2 changes: 2 additions & 0 deletions assets/locales/en.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ filter_files = "Filter files"
filter_favorites = "Filter favorites"
search_case_sensitive = "Case-sensitive"
search_match_whole_word = "Whole word"
record_file_empty = "File"
record_file_count = "{count} files"
# Settings
settings_button = "Settings"
settings_title = "Settings"
Expand Down
2 changes: 2 additions & 0 deletions assets/locales/ja.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ filter_files = "ファイルをフィルター"
filter_favorites = "お気に入りをフィルター"
search_case_sensitive = "大文字と小文字を区別"
search_match_whole_word = "単語一致"
record_file_empty = "ファイル"
record_file_count = "{count} 個のファイル"
# Settings
settings_button = "設定"
settings_title = "設定"
Expand Down
2 changes: 2 additions & 0 deletions assets/locales/zh-CN.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ filter_files = "筛选文件"
filter_favorites = "筛选收藏"
search_case_sensitive = "区分大小写"
search_match_whole_word = "全词"
record_file_empty = "文件"
record_file_count = "{count} 个文件"
# 设置
settings_button = "设置"
settings_title = "设置"
Expand Down
3 changes: 2 additions & 1 deletion scripts/check/check_i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@ def collect_used_keys(src_root: Path) -> set[str]:

Recognises these call patterns:
* I18n::translate(cx, "key")
* I18n::translate_count(cx, "key", count)
* i18n.t("key")
* translations.get("key")
* any_field_key: "key" (struct field ending with ``_key``, used for
indirect i18n lookups like ``i18n.t(row.label_key)``)
"""
# Direct I18n::translate / .t() / .get() call patterns
direct_pattern = re.compile(r'I18n::translate\(\s*\w+\s*,\s*"([^"]+)"\s*\)|\.t\(\s*"([^"]+)"\s*\)|\.get\(\s*"([^"]+)"\s*\)')
direct_pattern = re.compile(r'I18n::translate(?:_count)?\(\s*\w+\s*,\s*"([^"]+)"|\.t\(\s*"([^"]+)"\s*\)|\.get\(\s*"([^"]+)"\s*\)')
# Struct field whose name ends with ``_key`` assigned a string literal,
# e.g. ``label_key: "help_search"``
field_key_pattern = re.compile(r'\b\w+_key\s*:\s*"([^"]+)"')
Expand Down
6 changes: 4 additions & 2 deletions scripts/generate_update_manifest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ append_asset() {

while IFS= read -r -d '' archive; do
append_asset "$archive"
if [[ -f "${archive}.sha256" ]]; then
append_asset "${archive}.sha256"
if [[ ! -f "${archive}.sha256" ]]; then
echo "Missing checksum for update archive: ${archive}.sha256" >&2
exit 1
fi
append_asset "${archive}.sha256"
done < <(
find "$artifact_dir" -maxdepth 1 -type f \
\( -name 'ropy-*.tar.xz' -o -name 'ropy-*.zip' \) \
Expand Down
1 change: 1 addition & 0 deletions scripts/precheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ fi
$CARGO_CMD check --all-targets --all-features
$CARGO_CMD clippy --all-targets --all-features
$CARGO_CMD test
RUSTDOCFLAGS="-D warnings" $CARGO_CMD doc --no-deps

# Check unused dependencies
if command -v cargo-machete &>/dev/null; then
Expand Down
7 changes: 2 additions & 5 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use crate::{
};

#[cfg(target_os = "linux")]
/// Shared X11 connection used for native window mapping and activation.
pub static X11_INSTANCE: OnceLock<X11> = OnceLock::new();

/// Capacity for the clipboard event channel between the OS clipboard listener
Expand Down Expand Up @@ -250,11 +251,7 @@ fn load_settings() -> Settings {
}
Err(e) => {
tracing::warn!(error = %e, "failed to load settings; using defaults");
let default_settings = Settings::default();
default_settings.save().unwrap_or_else(|err| {
tracing::error!(error = %err, "failed to save default settings");
});
default_settings
Settings::default()
}
}
}
Expand Down
59 changes: 43 additions & 16 deletions src/clipboard.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::sync::mpsc::Sender as CompletionSender;

use thiserror::Error;

/// Clipboard event monitoring and ingestion.
pub mod listener;
/// Clipboard asset persistence helpers.
Expand All @@ -26,24 +28,36 @@ pub(crate) enum ClipboardEvent {
},
}

pub(crate) type ClipboardWriteResult = Result<(), ClipboardWriteError>;

#[derive(Debug, Error)]
pub(crate) enum ClipboardWriteError {
#[error("clipboard operation failed: {0}")]
Clipboard(String),
#[error("failed to load clipboard image: {0}")]
Image(#[from] image::ImageError),
#[error("cannot copy an empty file list")]
EmptyFileList,
}

pub(crate) enum CopyRequest {
Text {
text: String,
completion: Option<CompletionSender<()>>,
completion: Option<CompletionSender<ClipboardWriteResult>>,
},
Image {
path: String,
completion: Option<CompletionSender<()>>,
completion: Option<CompletionSender<ClipboardWriteResult>>,
},
Files {
paths: Vec<String>,
completion: Option<CompletionSender<()>>,
completion: Option<CompletionSender<ClipboardWriteResult>>,
},
RichText {
plain_text: String,
html: Option<String>,
rtf: Option<String>,
completion: Option<CompletionSender<()>>,
completion: Option<CompletionSender<ClipboardWriteResult>>,
},
}

Expand All @@ -57,7 +71,7 @@ impl CopyRequest {

pub(crate) const fn text_with_completion(
text: String,
completion: CompletionSender<()>,
completion: CompletionSender<ClipboardWriteResult>,
) -> Self {
Self::Text {
text,
Expand All @@ -74,7 +88,7 @@ impl CopyRequest {

pub(crate) const fn image_with_completion(
path: String,
completion: CompletionSender<()>,
completion: CompletionSender<ClipboardWriteResult>,
) -> Self {
Self::Image {
path,
Expand All @@ -91,7 +105,7 @@ impl CopyRequest {

pub(crate) const fn files_with_completion(
paths: Vec<String>,
completion: CompletionSender<()>,
completion: CompletionSender<ClipboardWriteResult>,
) -> Self {
Self::Files {
paths,
Expand All @@ -116,7 +130,7 @@ impl CopyRequest {
plain_text: String,
html: Option<String>,
rtf: Option<String>,
completion: CompletionSender<()>,
completion: CompletionSender<ClipboardWriteResult>,
) -> Self {
Self::RichText {
plain_text,
Expand All @@ -129,6 +143,7 @@ impl CopyRequest {

pub(crate) enum LastCopyState {
Text(String),
RichText(u64),
Image(u64),
Files(u64),
}
Expand Down Expand Up @@ -165,7 +180,7 @@ mod tests {
match request {
CopyRequest::Text { text, completion } => {
assert_eq!(text, "hello");
completion.unwrap().send(()).unwrap();
completion.unwrap().send(Ok(())).unwrap();
}
CopyRequest::Image { .. }
| CopyRequest::Files { .. }
Expand All @@ -174,7 +189,10 @@ mod tests {
}
}

assert_eq!(completion_rx.recv_timeout(Duration::from_secs(1)), Ok(()));
assert!(matches!(
completion_rx.recv_timeout(Duration::from_secs(1)),
Ok(Ok(()))
));
}

#[test]
Expand Down Expand Up @@ -202,14 +220,17 @@ mod tests {
match request {
CopyRequest::Image { path, completion } => {
assert_eq!(path, "/tmp/example.png");
completion.unwrap().send(()).unwrap();
completion.unwrap().send(Ok(())).unwrap();
}
CopyRequest::Text { .. } | CopyRequest::Files { .. } | CopyRequest::RichText { .. } => {
panic!("expected image copy request")
}
}

assert_eq!(completion_rx.recv_timeout(Duration::from_secs(1)), Ok(()));
assert!(matches!(
completion_rx.recv_timeout(Duration::from_secs(1)),
Ok(Ok(()))
));
}

#[test]
Expand Down Expand Up @@ -237,14 +258,17 @@ mod tests {
match request {
CopyRequest::Files { paths, completion } => {
assert_eq!(paths, vec!["/tmp/example.txt"]);
completion.unwrap().send(()).unwrap();
completion.unwrap().send(Ok(())).unwrap();
}
CopyRequest::Text { .. } | CopyRequest::Image { .. } | CopyRequest::RichText { .. } => {
panic!("expected files copy request")
}
}

assert_eq!(completion_rx.recv_timeout(Duration::from_secs(1)), Ok(()));
assert!(matches!(
completion_rx.recv_timeout(Duration::from_secs(1)),
Ok(Ok(()))
));
}

#[test]
Expand Down Expand Up @@ -294,13 +318,16 @@ mod tests {
assert_eq!(plain_text, "hello");
assert_eq!(html.as_deref(), Some("<p>hello</p>"));
assert_eq!(rtf.as_deref(), Some("{\\rtf1 hello}"));
completion.unwrap().send(()).unwrap();
completion.unwrap().send(Ok(())).unwrap();
}
CopyRequest::Text { .. } | CopyRequest::Image { .. } | CopyRequest::Files { .. } => {
panic!("expected rich text copy request")
}
}

assert_eq!(completion_rx.recv_timeout(Duration::from_secs(1)), Ok(()));
assert!(matches!(
completion_rx.recv_timeout(Duration::from_secs(1)),
Ok(Ok(()))
));
}
}
Loading
Loading