Save As: the suggested extension typed in another case gets the picked type's

On Linux and the BSDs, where the save dialog reports the file type
picked in it, a name that still ends in the suggested name's extension
gets the picked type's extension. The comparison was case-sensitive,
so Untitled-1.VECTORCRAFT with "PDF-compatible .ai" picked was saved
as a native file named Untitled-1.VECTORCRAFT instead of Untitled-1.ai.

with_picked_type now compares the lower-case extensions that
fileio::extension gives. A name typed with the picked type's own
extension in another case (a.AI with .ai picked) stays as typed.

This follows up #3372.

Docs: docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-10-22 05:09:55 +07:00
committed by bflatastic
parent b15230bfb5
commit bd9a508d49
3 changed files with 25 additions and 5 deletions
+5 -4
View File
@@ -410,16 +410,17 @@ fn with_save_extension(path: &str, f: &Format, mode: SaveMode) -> String {
/// [`with_save_extension`] for a save panel that reports the file type picked in it (`picked`,
/// that type's extension; the XDG portal's panel on Linux and the BSDs), which then takes `f`'s
/// place. A path that still ends in the extension of the name the panel suggested (`suggested`)
/// gets the picked type's extension instead. Without a picked type, or with one the panel did not
/// place. A path that still ends in the extension of the name the panel suggested (`suggested`),
/// in any letter case, gets the picked type's extension instead, unless that extension is one of
/// the picked type's; such a path stays as typed. Without a picked type, or with one the panel did not
/// offer, [`with_save_extension`] with `f`.
fn with_picked_type(path: &str, suggested: &str, picked: Option<&str>, f: &Format, mode: SaveMode) -> String {
let formats = if mode == SaveMode::Template { TEMPLATE_FORMATS } else { SAVE_FORMATS };
let Some(g) = picked.and_then(fileio::format).filter(|g| formats.contains(&g.id)) else {
return with_save_extension(path, f, mode);
};
let has = std::path::Path::new(path).extension();
if has.is_some() && has == std::path::Path::new(suggested).extension() {
let has = fileio::extension(path);
if !has.is_empty() && has == fileio::extension(suggested) && !g.extensions.contains(&has.as_str()) {
return std::path::Path::new(path).with_extension(g.extensions[0]).to_string_lossy().into_owned();
}
with_save_extension(path, g, mode)
+19
View File
@@ -161,6 +161,25 @@ fn the_file_type_picked_in_the_save_dialog_decides_the_format() {
}
}
/// The suggested name's extension typed in another case still gets the picked type's
/// (`Untitled-1.VECTORCRAFT` with .ai picked is saved as `Untitled-1.ai`), and the picked type's
/// own extension typed in another case stays as typed (`a.AI` with .ai picked).
#[test]
fn the_suggested_names_extension_in_another_case_gets_the_picked_types() {
for (cmd, answer, expected) in [
("file.save", &["/art/Untitled-1.VECTORCRAFT", "ai"][..], "/art/Untitled-1.ai"),
("file.save", &["/art/Untitled-1.VectorCraft", "svg"], "/art/Untitled-1.svg"),
("file.saveAsTemplate", &["/art/Untitled-1.VCTemplate", "ait"], "/art/Untitled-1.ait"),
] {
let (mut app, shown, written) = portal_app();
assert_eq!(saved_from(&mut app, &shown, &written, cmd, answer), expected, "{cmd} {answer:?}");
}
// A document saved as .ai suggests `a.ai`.
let (mut app, shown, written) = portal_app();
app.run("file.saveAs", json!({"path": "/art/a.ai", "options": {}})).unwrap();
assert_eq!(saved_from(&mut app, &shown, &written, "file.saveAs", &["/art/a.AI", "ai"]), "/art/a.AI");
}
/// A name typed with the extension of a format the save does not write has it replaced, with or
/// without a picked type: `card.ait` and `card.png` are saved as `card.vectorcraft`, not
/// `card.ait.vectorcraft`. An extension that names no format stays part of the name.
+1 -1
View File
@@ -783,7 +783,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
A name typed in the desktop save panel without the file type's extension gets it, and when a file of that name exists, which the panel did not ask about, Save, Save As, Save a Copy, Save as Template, the exports and every other write through a save panel ask before replacing it (dialog `replaceFile`; Cancel writes nothing).
A name returned by the desktop save panel with the extension of a format the save does not write has it replaced with the saved format's: Save As of a new document saves `card.ait` and `card.png` as `card.vectorcraft`, and Save as Template saves `card.pdf` as `card.vctemplate`. An extension that names no format stays part of the name (`notes.v2` is saved as `notes.v2.vectorcraft`).
- macOS: the save sheet of Save As, Save a Copy, Save as Template and the first Save of a new document has a Format menu that lists each format the save can write, with the suggested file's format selected. Choosing a format changes the extension in the name field, and a name typed without an extension gets the chosen format's. A name typed with another listed extension is saved in that format. The menu is set up before the sheet opens; a save panel with one file type is shown by rfd as before (#866, `apps/vectorcraft/src/mac_save_format.rs`).
- Linux and the BSDs: the save dialog of Save As, Save a Copy, Save as Template and the first Save of a new document is asked of the desktop portal, starting on the suggested file's type, and the file type picked in it decides the format. A name typed without an extension, or with the suggested file's, gets the picked type's extension; a name typed with another save format's extension (for Save as Template, a template format's) is saved in that format, another format's extension is replaced with the picked type's, and any other extension gets the picked type's added. rfd shows the dialog when the portal can't (#2679, `apps/vectorcraft/src/portal_save.rs`).
- Linux and the BSDs: the save dialog of Save As, Save a Copy, Save as Template and the first Save of a new document is asked of the desktop portal, starting on the suggested file's type, and the file type picked in it decides the format. A name typed without an extension, or with the suggested file's in any letter case, gets the picked type's extension; a name typed with another save format's extension (for Save as Template, a template format's) is saved in that format, another format's extension is replaced with the picked type's, and any other extension gets the picked type's added. rfd shows the dialog when the portal can't (#2679, `apps/vectorcraft/src/portal_save.rs`).
- Save as Template writes a PDF-compatible `.ait` as well as a `.vctemplate`, which stays the default: the save panel and the web build's Save as Template dialog list both, and New from Template opens the `.ait` as an untitled document with its layers and art (`file.saveAsTemplate {format: "ait"}`, or a path ending in `.ait`).
Save as Template always writes a file named with its template type's extension: `file.saveAsTemplate` saves a path ending in another format's extension (`card.png`) as `card.vctemplate` (or `card.ait`), the desktop save panel does the same with a Save As format's extension typed in it (`card.pdf` is saved as `card.vctemplate`), and `file.saveAs` and `document.save` refuse `ait` with an error that names `file.saveAsTemplate`. A `.ait` is PDF-compatible only: `aiVersion: "7"` is an error for it, and the web build's dialog shows no Version row for it.
Exports refuse a `.ait` with an error that names `file.saveAsTemplate`, and `vectorcraft-cli convert in.pdf out.ait` and `run --export out.ait` write the template as `file.saveAsTemplate` does.