дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
Plugins can now ship a web page as the UI of a view or custom editor (`web = "web/index.html"`), so editors built on web libraries are plugins rather than part of the core. - Web views: every file under the page's directory is part of the package (checked, copied on install, covered by the digest and signature) and served through the `openidea-plugin` URL scheme, under a per-revision path with a CSP of its own, into an `<iframe sandbox="allow-scripts">`. An update or reinstall reloads open pages. - Frame bridge (`window.openidea`), shared by html bodies and web pages: theme, messages to the plugin's code, `document.get/edit/replace/onChange` (versioned edits that join the text editor's undo history, Save and dirty state), `saveFile`/`openFile` through the IDE's dialogs, and IDE shortcuts the page leaves alone. - Custom editor layouts: tab, split (Editor and X) or preview over the text editor, switchable from the tab's actions. - New points `fileIcons` and `fileTemplates`; new optional capability `clipboard` for web views. - Excalidraw official plugin: `.excalidraw` / `.excalidraw.json` open as an interactive canvas kept in step with the file's text, with self-hosted fonts, PNG/SVG export, images, file icons, a New Drawing template and JSON highlighting. Closes #732. - Fixes: a lock-order deadlock between listing plugins and refreshing project pins; the dev server ignored edits under `src/features/plugins`. - `plugins/build.sh` builds and checks web pages; CI typechecks and tests them. Co-authored-by: Deng Liu <[email protected]>
72 рядки
3.7 KiB
Rust
72 рядки
3.7 KiB
Rust
//! First-party extension points registered by the host, plus
|
|
//! [`register_builtin_points`], the one place that lists them. Feature crates
|
|
//! own their points (`ide_language::plugin_point`); adding a contribution type
|
|
//! means adding a point here or in its crate, and a `defineUiPoint` projection
|
|
//! on the client for `Ui` points — nothing else.
|
|
|
|
use ide_extension::{ExtensionRegistry, Package, UiDecl, UiPoint, UiStore, decode, validate_item_id, validate_when};
|
|
use ide_language::plugin_point::{LanguagesPoint, PluginLanguages};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::sync::Arc;
|
|
|
|
pub const COMMANDS: &str = "commands";
|
|
const MAX_TITLE: usize = 256;
|
|
|
|
/// `[[contributes.commands]]`: an action `plugin.<plugin-id>.<id>` in Search
|
|
/// Everywhere and the keymap editor; shown before the plugin's code runs.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, specta::Type)]
|
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
pub struct CommandDecl {
|
|
pub id: String,
|
|
pub title: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub category: Option<String>,
|
|
/// Enablement, evaluated by the client against context keys (`editorLang == rust`).
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub when: Option<String>,
|
|
}
|
|
|
|
fn command(_pkg: &Package, decl: toml::Value) -> Result<UiDecl, String> {
|
|
let c: CommandDecl = decode(COMMANDS, decl)?;
|
|
validate_item_id(&c.id, "command id")?;
|
|
if c.title.trim().is_empty() {
|
|
return Err(format!("command {:?} has an empty title", c.id));
|
|
}
|
|
if c.title.len() > MAX_TITLE || c.category.as_ref().is_some_and(|x| x.len() > MAX_TITLE) {
|
|
return Err(format!("command {:?}: title or category longer than {MAX_TITLE} bytes", c.id));
|
|
}
|
|
if let Some(when) = &c.when {
|
|
validate_when(when).map_err(|e| format!("command {:?}: {e}", c.id))?;
|
|
}
|
|
let c = CommandDecl { category: c.category.filter(|x| !x.trim().is_empty()), ..c };
|
|
let json = serde_json::to_value(&c).map_err(|e| e.to_string())?;
|
|
Ok(UiDecl::new(c.id.clone(), c.title, json))
|
|
}
|
|
|
|
/// Registers every first-party extension point.
|
|
pub fn register_builtin_points(
|
|
registry: &ExtensionRegistry,
|
|
ui: &Arc<UiStore>,
|
|
languages: &Arc<PluginLanguages>,
|
|
run_detectors: &Arc<super::run_detectors::RunDetectors>,
|
|
providers: &Arc<super::providers::Providers>,
|
|
) -> Result<(), String> {
|
|
registry.register(Arc::new(UiPoint::new(COMMANDS, ui.clone(), command)))?;
|
|
registry.register(Arc::new(super::theme::ThemesPoint::new(ui.clone())))?;
|
|
registry.register(Arc::new(LanguagesPoint::new(languages.clone())))?;
|
|
registry.register(Arc::new(UiPoint::new(super::icons::POINT_ID, ui.clone(), super::icons::icon_theme)))?;
|
|
registry.register(Arc::new(super::run_detectors::RunDetectorsPoint::new(run_detectors.clone())))?;
|
|
registry.register(Arc::new(super::providers::ProvidersPoint::new(providers.clone())))?;
|
|
registry.register(Arc::new(UiPoint::new(super::providers::SECRETS_POINT, ui.clone(), super::providers::secret)))?;
|
|
use super::ui_points as p;
|
|
registry.register(Arc::new(UiPoint::new(p::MENUS, ui.clone(), p::menu)))?;
|
|
registry.register(Arc::new(UiPoint::new(p::KEYMAPS, ui.clone(), p::keymap)))?;
|
|
registry.register(Arc::new(UiPoint::new(p::STATUS_BAR, ui.clone(), p::status_item)))?;
|
|
registry.register(Arc::new(UiPoint::new(p::SETTINGS, ui.clone(), p::settings)))?;
|
|
registry.register(Arc::new(UiPoint::new(p::VIEWS, ui.clone(), p::view)))?;
|
|
registry.register(Arc::new(UiPoint::new(p::CUSTOM_EDITORS, ui.clone(), p::custom_editor)))?;
|
|
registry.register(Arc::new(UiPoint::new(p::FILE_ICONS, ui.clone(), p::file_icon)))?;
|
|
registry.register(Arc::new(UiPoint::new(p::FILE_TEMPLATES, ui.clone(), p::file_template)))?;
|
|
Ok(())
|
|
}
|