forked from 1b-agent/opencadstudio-upstream
Merge pull request #933 from schoeller/plugin_document_path
feat(plugins): expose document path to plugins via HostApi
This commit is contained in:
9 files changed
+62
-2
No files matched your search
@@ -49,7 +49,7 @@ The host and each plugin run in separate OS processes. The host re-executes itse
|
||||
- `OCS_PLUGIN_MAX_API_VERSION` can cap the accepted major at runtime (e.g. `4` to disable V5).
|
||||
- A plugin built against major `N` runs on a host whose major is `>= N` because new vtable entries and enum variants are appended at the end.
|
||||
- V4 introduces the **acadrust gate**: plugins targeting API v4 or later must resolve the same `acadrust` source as the host (see [`src/version_info.rs`](src/version_info.rs)).
|
||||
- V5 introduces `BuiltinPlugin::on_load`, invoked once after the runner connects.
|
||||
- V5 introduces `BuiltinPlugin::on_load` and tab-keyed document paths.
|
||||
|
||||
The runtime enforces three gates:
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ enum variants at the end, so older plugins continue to load on newer hosts.
|
||||
| **V2** | Out-of-process runner | Synchronous commands + interactive point collection (`HostApi::start_interactive`) | Baseline |
|
||||
| **V3** | Out-of-process runner | Local cached `document()`/`document_mut()` + shared-memory `DocumentReader`/`document_view` for large reads | New vtable entries appended; new `PluginRequest`/`PluginResponse` variants appended |
|
||||
| **V4** | Out-of-process runner over a multiplexed local socket | Full-duplex notifications + asynchronous REPL `ExecuteCode` | New `HostToPluginV4`/`PluginToHostV4` frame layer; `BuiltinPlugin::on_notification` and `start_execute_code` appended to trait |
|
||||
| **V5** | V4 multiplexed runner | One-shot `BuiltinPlugin::on_load` initialization | Lifecycle callback appended to `BuiltinPlugin` |
|
||||
| **V5** | V4 multiplexed runner | One-shot initialization + tab-keyed document paths | `BuiltinPlugin::on_load` and `HostApi::document_path` appended to the traits |
|
||||
|
||||
The host controls which generations are accepted at runtime through
|
||||
`OCS_PLUGIN_MAX_API_VERSION` (e.g. `2` for V2-only mode, `4` to disable V5). The
|
||||
@@ -28,6 +28,7 @@ back to `API_VERSION_MIN_SUPPORTED = 2`.
|
||||
V5 calls `BuiltinPlugin::on_load` once after the runner connects and before the
|
||||
first user command. Plugins can cache `HostApi::plugin_request_sender()` there
|
||||
for worker-thread access. A panic in the callback fails plugin loading.
|
||||
`HostApi::document_path(tab_id)` returns the saved path for a document tab.
|
||||
|
||||
### High-level layout
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
//! [`ensure_plugin_state`] helpers for the ergonomic typed access.
|
||||
|
||||
use std::any::Any;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
@@ -539,6 +540,14 @@ pub trait HostApi {
|
||||
fn add_entities(&mut self, entities: Vec<EntityType>) -> Vec<Handle> {
|
||||
entities.into_iter().map(|e| self.add_entity(e)).collect()
|
||||
}
|
||||
|
||||
/// Return the filesystem path of the document in tab `tab_id`, if any.
|
||||
/// Added in API v5. The default returns `None`; in-process hosts should
|
||||
/// override it to expose the real path.
|
||||
fn document_path(&self, tab_id: u64) -> Option<PathBuf> {
|
||||
let _ = tab_id;
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Simplified, read-only entity kind exposed by [`DocumentReader`].
|
||||
|
||||
@@ -401,6 +401,21 @@ impl HostApi for PluginHostApi {
|
||||
None => Box::new(EmptyDocumentReader),
|
||||
}
|
||||
}
|
||||
|
||||
fn document_path(&self, tab_id: u64) -> Option<std::path::PathBuf> {
|
||||
match self.client.request(PluginRequest::DocumentPath { tab_id }) {
|
||||
Ok(PluginResponse::DocumentPath(Some(path))) => Some(std::path::PathBuf::from(path)),
|
||||
Ok(PluginResponse::DocumentPath(None)) => None,
|
||||
Ok(other) => {
|
||||
eprintln!("[plugin] unexpected DocumentPath response: {other:?}");
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[plugin] DocumentPath request failed: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sentinel reader used when the shared-memory view could not be initialized.
|
||||
|
||||
@@ -136,6 +136,8 @@ pub enum PluginRequest {
|
||||
CloseDocumentViewV4 { tab_id: u64 },
|
||||
/// V4: ask the host for the stable tab identifier of the active tab.
|
||||
GetTabId,
|
||||
/// V5: ask the host for the filesystem path of the document in `tab_id`.
|
||||
DocumentPath { tab_id: u64 },
|
||||
}
|
||||
|
||||
/// Responses the host sends back for `PluginRequest`.
|
||||
@@ -160,6 +162,8 @@ pub enum PluginResponse {
|
||||
},
|
||||
/// V4: stable tab identifier of the active tab.
|
||||
TabId(u64),
|
||||
/// V5: filesystem path of the document in the requested tab, if any.
|
||||
DocumentPath(Option<std::ffi::OsString>),
|
||||
}
|
||||
|
||||
/// Messages sent from the host to the plugin runner.
|
||||
|
||||
@@ -74,5 +74,8 @@ pub fn handle_plugin_request(
|
||||
PluginResponse::Ok
|
||||
}
|
||||
GetTabId => PluginResponse::TabId(host.tab_id()),
|
||||
DocumentPath { tab_id } => PluginResponse::DocumentPath(
|
||||
host.document_path(tab_id).map(|path| path.into_os_string()),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -715,6 +715,21 @@ impl HostApi for V4PluginHostApi {
|
||||
view.take();
|
||||
}
|
||||
}
|
||||
|
||||
fn document_path(&self, tab_id: u64) -> Option<std::path::PathBuf> {
|
||||
match self.request(PluginRequest::DocumentPath { tab_id }) {
|
||||
Ok(PluginResponse::DocumentPath(Some(path))) => Some(std::path::PathBuf::from(path)),
|
||||
Ok(PluginResponse::DocumentPath(None)) => None,
|
||||
Ok(other) => {
|
||||
eprintln!("[plugin] unexpected DocumentPath response: {other:?}");
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[plugin] DocumentPath request failed: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -130,6 +130,7 @@ pub trait BuiltinPlugin: Send + Sync {
|
||||
fn manifest(&self) -> &'static PluginManifest;
|
||||
fn ribbon(&self) -> Box<dyn CadModule>; // the ribbon tab
|
||||
fn dispatch(&self, host: &mut dyn HostApi, cmd: &str) -> bool;
|
||||
fn on_load(&mut self, host: &mut dyn HostApi) {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -146,6 +147,7 @@ concrete types:
|
||||
| Command line | `push_info`, `push_output`, `push_error` |
|
||||
| Undo / dirty | `push_undo`, `set_dirty` |
|
||||
| Tab | `tab_index()` |
|
||||
| Document identity (V5) | `document_path(tab_id)` returns the saved path for a document tab. |
|
||||
| Notifications (V4) | `on_notification` receives `HostNotification::SelectionChangedV4 { tab_id, handles }` when the active tab's selection changes, plus `DocumentChangedV4` / `DocumentTabClosed`. |
|
||||
|
||||
### `export_plugin!` — the C-ABI export
|
||||
|
||||
@@ -35,6 +35,14 @@ impl<'a> HostSession<'a> {
|
||||
self.app.tabs[self.tab].id
|
||||
}
|
||||
|
||||
pub fn document_path(&self, tab_id: u64) -> Option<std::path::PathBuf> {
|
||||
self.app
|
||||
.tabs
|
||||
.iter()
|
||||
.find(|t| t.id == tab_id)
|
||||
.and_then(|t| t.current_path.clone())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn document_view_v4(&mut self, tab_id: u64) -> Option<ocs_plugin_api::shm::DocumentViewInfo> {
|
||||
if tab_id != self.tab_id() {
|
||||
@@ -341,6 +349,9 @@ impl HostApi for HostSession<'_> {
|
||||
fn tab_id(&self) -> u64 {
|
||||
self.tab_id()
|
||||
}
|
||||
fn document_path(&self, tab_id: u64) -> Option<std::path::PathBuf> {
|
||||
self.document_path(tab_id)
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn document_view_v4(&mut self, tab_id: u64) -> Option<ocs_plugin_api::shm::DocumentViewInfo> {
|
||||
self.document_view_v4(tab_id)
|
||||
|
||||
Reference in new issue
Block a user