M8: MCP and control calls name the document they act on (#998) (#1533)

Tools that act on a document, and the control methods engine.execute,
document.inspect, node.get and ui.render, take an optional `document`
(an open file's id from doc.list / document.inspect, or its path). The
call acts on that file and the tab in front stays in front, so an
attached agent keeps editing its own file when the user switches tabs.
This commit is contained in:
BFlat
2026-10-29 19:47:41 +07:00
committed by GitHub
parent b2421885d0
commit 714bbfcdaa
16 changed files with 289 additions and 18 deletions
+39
View File
@@ -1,6 +1,45 @@
use super::{DocState, Document, EngineError, NodeId, Result, Session, Value, json};
/// The file [`Session::bring_forward`] put in front for one request, and the tab to bring back.
#[must_use = "pass it to `Session::put_back`"]
pub struct Forward {
target: u64,
shown: Option<u64>,
}
impl Session {
/// The uid of open file `target`: its `id` (from `doc.list` or `document.inspect`) or its path.
pub fn document_uid(&self, target: &Value) -> Result<u64> {
let found = match target {
Value::Number(n) => n.as_u64().and_then(|id| self.docs.iter().find(|d| d.uid == id)),
Value::String(p) => self.docs.iter().find(|d| d.path.as_deref() == Some(p.as_str())),
_ => None,
};
found
.map(|d| d.uid)
.ok_or_else(|| EngineError::Other(format!("no open document {target} (`document` takes an `id` from doc.list, or an open file's path)")))
}
/// Put open file `target` in front for one request (a control call naming its `document`),
/// without the user's tab changing on screen: run the request, then [`Session::put_back`].
pub fn bring_forward(&mut self, target: &Value) -> Result<Forward> {
let uid = self.document_uid(target)?;
let shown = self.ds().ok().map(|d| d.uid);
if let Some(i) = self.docs.iter().position(|d| d.uid == uid) {
self.active = i;
}
Ok(Forward { target: uid, shown })
}
/// Bring back the tab that was in front before [`Session::bring_forward`], unless the request
/// itself moved the front to another open file (`file.open`, `doc.switch`…), as it would have.
pub fn put_back(&mut self, f: Forward) {
let moved = self.docs.iter().any(|d| d.uid == f.target) && self.ds().ok().map(|d| d.uid) != Some(f.target);
if let Some(i) = f.shown.and_then(|u| self.docs.iter().position(|d| d.uid == u)).filter(|_| !moved) {
self.active = i;
}
}
pub fn ds(&self) -> Result<&DocState> {
self.docs.get(self.active).ok_or(EngineError::NoDocument)
}