MCP: document.inspect and document.open report the File Info title as fileInfoTitle

The title field of document.inspect and the document.open reply is the
document's tab name, which comes from the file name once the document
is saved to or opened from a file. A client had to call file.info or
document.info to read the File Info title, so reading a saved
document's title from these replies gave the file name instead.

Both replies now add fileInfoTitle, the title file.info reports, and
keep title as it was. docs/mcp.md's File Info section describes both.
This commit is contained in:
dotCooCoo
2026-10-24 17:29:33 +07:00
committed by bflatastic
parent e71bf71376
commit c5d010e7d7
4 changed files with 34 additions and 2 deletions
+6 -2
View File
@@ -463,8 +463,12 @@ fn open_loaded(s: &mut Session, loaded: Loaded, links: Resolved, path: Option<St
st.converted = converted;
st.imported_from = source_path;
st.import_losses = losses;
let title = s.documents()[index].title();
Ok(super::merge(json!({ "index": index, "title": title, "format": format.id, "warnings": warnings, "restored": restored }), links.to_json()))
let opened = &s.documents()[index];
let (title, file_info_title) = (opened.title(), opened.doc.title.clone());
Ok(super::merge(
json!({ "index": index, "title": title, "fileInfoTitle": file_info_title, "format": format.id, "warnings": warnings, "restored": restored }),
links.to_json(),
))
}
/// A file named by a command's params: `{path}` (read from disk) or `{name, dataBase64}`.
+2
View File
@@ -265,6 +265,8 @@ fn document_base(s: &Session) -> Value {
json!({
"uid": st.uid,
"title": st.title(),
// The File Info title (`file.info`), where `title` comes from the file name once the document has one.
"fileInfoTitle": d.title,
"path": st.path,
"dirty": st.is_dirty(),
"revision": st.revision,
+23
View File
@@ -112,6 +112,29 @@ fn saving_to_a_file_stamps_the_modified_date() {
assert!(s.execute("file.info", &json!({})).unwrap()["modified"].is_string());
}
#[test]
fn inspect_and_open_report_the_file_info_title_beside_the_file_name() {
let dir = std::env::temp_dir().join(format!("vc-fileinfotitle-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("poster.vectorcraft");
let mut s = session();
// Untitled, the two are the same.
let r = s.execute("document.inspect", &json!({"depth": 0})).unwrap();
assert_eq!((r["title"].as_str(), r["fileInfoTitle"].as_str()), (Some("Poster"), Some("Poster")));
s.execute("file.info", &json!({"title": "Poster: final"})).unwrap();
s.execute("document.save", &json!({"path": path.to_str().unwrap()})).unwrap();
let r = s.execute("document.inspect", &json!({"depth": 0})).unwrap();
assert_eq!((r["title"].as_str(), r["fileInfoTitle"].as_str()), (Some("poster.vectorcraft"), Some("Poster: final")));
// Opened from the file, in another session.
let mut s = Session::new();
let opened = s.execute("document.open", &json!({"path": path.to_str().unwrap()})).unwrap();
assert_eq!((opened["title"].as_str(), opened["fileInfoTitle"].as_str()), (Some("poster.vectorcraft"), Some("Poster: final")));
let r = s.execute("document.inspect", &json!({"depth": 0})).unwrap();
assert_eq!((r["title"].as_str(), r["fileInfoTitle"].as_str()), (Some("poster.vectorcraft"), Some("Poster: final")));
assert_eq!(r["fileInfoTitle"], s.execute("file.info", &json!({})).unwrap()["title"]);
let _ = std::fs::remove_dir_all(dir);
}
#[test]
fn pdf_info_carries_author_subject_and_keywords() {
let mut s = session();
+3
View File
@@ -3184,6 +3184,9 @@ packet (`dc:creator`, `dc:description`, `dc:subject` or `pdf:Keywords`, `xmpRigh
`Subject`, `Keywords`, `CreationDate`, `ModDate`) give, the XMP packet's value where both give one. A value that
`file.info` would refuse is left out, and the title is set as for any other open (the file name, or a new untitled
name for a template).
`document.inspect` (`inspect_document`) and the `document.open` (`open_file`) reply report the File Info title as
`fileInfoTitle`. Their `title` is the document's tab name, which comes from the file name once the document is saved
to or opened from a file (with ` [Converted]` or ` [Recovered]` where that applies).
`file.new {created}` and a save to a file (`document.save` or `file.saveAs` with
`{path, modified}`) use the given date (Unix seconds, or `null` for none) instead of now. Their journal entries always
record the date they used, so replaying the journal makes the same document whenever it runs (an action recorded in