mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
LR-EXP-PSD: settings path fields use TextField; Windows default editor via Explorer; PackBits encoder without indexing
- Settings ▸ General ▸ External Editing: the PhotoCraft location and Other editor fields move from egui::TextEdit to TextField (edit menu, Esc restores). - Edit In with the system default on Windows starts explorer.exe with the file as one argument instead of cmd /C start, which read & ^ % | in file names as shell syntax. - psd::packbits walks the row with first()/get()/split_at_checked instead of direct indexing and slicing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BKWB9TCnvzJHV6xMLAj2J4
This commit is contained in:
@@ -36,11 +36,7 @@ pub fn open_with(path: &str, app: &str) -> Result<(), String> {
|
||||
c.arg(path);
|
||||
c
|
||||
}
|
||||
Launcher::Default if cfg!(target_os = "windows") => {
|
||||
let mut c = Command::new("cmd");
|
||||
c.args(["/C", "start", "", path]);
|
||||
c
|
||||
}
|
||||
Launcher::Default if cfg!(target_os = "windows") => windows_default_command(path),
|
||||
Launcher::Default => {
|
||||
let mut c = Command::new("xdg-open");
|
||||
c.arg(path);
|
||||
@@ -65,6 +61,15 @@ pub fn open_with(path: &str, app: &str) -> Result<(), String> {
|
||||
c.spawn().map(|_| ()).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Windows: open `path` in its default application through Explorer, given the path as its one
|
||||
/// argument. Not `cmd /C start`: cmd.exe would read `&`, `^`, `%` or `|` in a file name as its own
|
||||
/// syntax (running what follows an `&` as a command).
|
||||
fn windows_default_command(path: &str) -> Command {
|
||||
let mut c = Command::new("explorer.exe");
|
||||
c.arg(path.replace('/', "\\"));
|
||||
c
|
||||
}
|
||||
|
||||
fn launcher(app: &str) -> Result<Launcher, String> {
|
||||
let app = app.trim();
|
||||
if app.is_empty() {
|
||||
@@ -175,6 +180,17 @@ fn desktop_exec(entry: &str) -> Option<PathBuf> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A file name with cmd.exe syntax in it goes to Explorer as one plain argument, not
|
||||
/// through a shell.
|
||||
#[test]
|
||||
fn windows_default_open_goes_through_explorer_not_cmd() {
|
||||
let path = r"C:/Edits/a & b ^c %PATH% | d.psd";
|
||||
let c = windows_default_command(path);
|
||||
assert_eq!(c.get_program(), "explorer.exe");
|
||||
let args: Vec<_> = c.get_args().map(|a| a.to_string_lossy().into_owned()).collect();
|
||||
assert_eq!(args, [r"C:\Edits\a & b ^c %PATH% | d.psd"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_lookup_prefers_the_recorded_install_folder() {
|
||||
let env = |k: &str| match k {
|
||||
|
||||
+14
-16
@@ -316,26 +316,24 @@ fn resource_section(meta: &EncodeMeta) -> Vec<u8> {
|
||||
/// PackBits: append one encoded row of `src` to `out` (runs of 2+ equal bytes as repeats, the
|
||||
/// rest as literals of at most 128 bytes).
|
||||
fn packbits(src: &[u8], out: &mut Vec<u8>) {
|
||||
let mut i = 0;
|
||||
while i < src.len() {
|
||||
let b = src[i];
|
||||
let mut run = 1;
|
||||
while run < 128 && i + run < src.len() && src[i + run] == b {
|
||||
run += 1;
|
||||
}
|
||||
let mut rest = src;
|
||||
while let Some(&b) = rest.first() {
|
||||
// a run of 2 to 128 equal bytes
|
||||
let run = rest.iter().take(128).take_while(|&&x| x == b).count();
|
||||
if run >= 2 {
|
||||
out.push((1i16 - run as i16) as i8 as u8);
|
||||
// 1 - run in 2..=128: -1..=-127, as a signed byte
|
||||
out.push((1 - run as i16) as i8 as u8);
|
||||
out.push(b);
|
||||
i += run;
|
||||
rest = rest.get(run..).unwrap_or_default();
|
||||
continue;
|
||||
}
|
||||
// a literal: up to the next pair of equal bytes
|
||||
let start = i;
|
||||
while i < src.len() && i - start < 128 && !(i + 1 < src.len() && src[i] == src[i + 1]) {
|
||||
i += 1;
|
||||
}
|
||||
out.push((i - start - 1) as u8);
|
||||
out.extend_from_slice(&src[start..i]);
|
||||
// a literal of 1 to 128 bytes: up to the next pair of equal bytes, or the end of the row
|
||||
let unpaired = rest.windows(2).take(128).take_while(|w| w.first() != w.get(1)).count();
|
||||
let len = if unpaired.saturating_add(1) == rest.len() { rest.len() } else { unpaired };
|
||||
let (lit, tail) = rest.split_at_checked(len.clamp(1, 128)).unwrap_or((rest, &[][..]));
|
||||
out.push(lit.len().saturating_sub(1) as u8);
|
||||
out.extend_from_slice(lit);
|
||||
rest = tail;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,10 +145,10 @@ fn general_tab(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
check(ui, "settings.confirmDelete", &mut app.ui.settings.confirm_delete, "Confirm before moving photos to Recently Deleted");
|
||||
heading(ui, t, crate::i18n::tr("External Editing"));
|
||||
row(ui, t, crate::i18n::tr("PhotoCraft"), |ui| {
|
||||
let r = ui.add(
|
||||
egui::TextEdit::singleline(&mut app.ui.settings.photocraft_path).hint_text(crate::i18n::tr("Found automatically")).desired_width(220.0),
|
||||
);
|
||||
register(ui.ctx(), "field:photocraftPath", r.rect);
|
||||
crate::text_field::TextField::singleline("field:photocraftPath", &mut app.ui.settings.photocraft_path)
|
||||
.hint(crate::i18n::tr("Found automatically"))
|
||||
.width(220.0)
|
||||
.show(ui);
|
||||
});
|
||||
edit_file_rows(ui, t, "photocraftFile", &mut app.ui.settings.photocraft_file);
|
||||
hint(
|
||||
@@ -159,9 +159,10 @@ fn general_tab(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
),
|
||||
);
|
||||
row(ui, t, crate::i18n::tr("Other editor"), |ui| {
|
||||
let r = ui
|
||||
.add(egui::TextEdit::singleline(&mut app.ui.settings.external_editor).hint_text(crate::i18n::tr("System default")).desired_width(220.0));
|
||||
register(ui.ctx(), "field:externalEditor", r.rect);
|
||||
crate::text_field::TextField::singleline("field:externalEditor", &mut app.ui.settings.external_editor)
|
||||
.hint(crate::i18n::tr("System default"))
|
||||
.width(220.0)
|
||||
.show(ui);
|
||||
});
|
||||
edit_file_rows(ui, t, "externalFile", &mut app.ui.settings.external_file);
|
||||
hint(ui, t, crate::i18n::tr("Edit in Other Editor (⌥⌘E) opens the copy in this application (an app name on macOS, a program path elsewhere)."));
|
||||
|
||||
Reference in New Issue
Block a user