CleanupSecuritydocumentationenhancementgood first issuehelp wanted
Repository metrics
- Stars
- (348 stars)
- PR merge metrics
- (PR metrics pending)
Description
Intro
I'll be adding more here soon as the scope of this overhaul to the API becomes more clear. for now most detail is in sub-issues
pub trait EditorPlugin Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn version(&self) -> &str;
fn author(&self) -> &str;
fn license(&self) -> &str;
fn homepage(&self) -> Option<&str>;
fn dependencies(&self) -> Option<&[Dependency]>;
}
pub trait EditorPluginFiles: EditorPlugin {
/// Get all file types this plugin supports.
fn file_types(&self) -> Vec<FileTypeDefinition>;
/// Get all editor types this plugin provides.
///
/// The editors are each responsible for editing one or more file types.
/// The editors are also responsible for defining any AI tools they wish
/// to be registered with the AI chat system. All file type definitions
/// are used to determine which editor to use for a given file and as
/// those files are valid targets of their respective editors and the
/// AI tools of those editors.
fn editors(&self) -> Vec<EditorDefinition>;
/// Create a new editor instance for the given file.
fn create_editor(
&self,
editor_id: EditorId,
file_path: PathBuf,
window: &mut Window,
cx: &mut App,
) -> Result<Arc<dyn PanelView>, PluginError>;
}
pub trait EditorPluginStatusBar: EditorPlugin {
/// Get statusbar buttons this plugin wants to register.
///
/// This is optional - plugins that don't need statusbar buttons can use the default implementation.
///
/// # Returns
///
/// A vector of statusbar button definitions. Return an empty vector if no buttons are needed.
///
/// # Safety
///
/// It is safe to return function pointers in `StatusbarButtonDefinition` because
/// plugins are never unloaded. The function pointers will remain valid for the
/// process lifetime.
fn statusbar_buttons(&self) -> Vec<StatusbarButtonDefinition> {
Vec::new()
}
/// Get any statusbar badges this plugin wants to display.
///
/// This is optional - plugins that don't need statusbar badges can use the default implementation.
///
/// # Returns
///
/// A vector of statusbar badge definitions. Return an empty vector if no badges are needed.
fn statusbar_badges(&self) -> Vec<StatusbarBadgeDefinition> {
Vec::new()
}
}
trait EditorPluginComponents: EditorPlugin {
/// Returns all ComponentDefinitions for this plugin.
/// e.g. how do we allow a plugin to create abd add components to the engiune such as: crates\pulsar_physics\src\components\physics_component/*
fn component_definitions(&self) -> Vec<ComponentDefinition>;
}
/// Complete definition of a file type that a plugin supports.
#[derive(Debug, Clone)]
pub struct FileTypeDefinition {
/// Unique identifier for this file type
pub id: FileTypeId,
/// File extension (without the dot, e.g., "rs" not ".rs")
/// For folder-based files, this is the folder extension
pub extension: String,
/// Human-readable name for this file type
pub display_name: String,
/// Icon to show in the file drawer
pub icon: ui::IconName,
/// Color for the icon
pub color: gpui::Hsla,
/// Whether this is a standalone file or folder-based
pub structure: FileStructure,
/// Default content for new files (as JSON)
/// For folder-based files, this is the content of the marker file
pub default_content: serde_json::Value,
/// Default Editor
pub default_editor: EditorId,
/// Optional category path for organizing in the create menu
/// Examples: vec!["Data"], vec!["Data", "SQLite"], vec!["Scripts", "Web"]
/// Leave empty for top-level menu items
pub categories: Vec<String>,
}
/// Defines the structure of a file type.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FileStructure {
/// A single standalone file (e.g., script.rs)
Standalone {
/// TemplateFile
templates: Vec<TemplateFile>,
},
/// A folder that appears as a file in the drawer (e.g., MyClass.class/)
/// Contains the marker file name that identifies this folder as this type
/// For example, "graph_save.json" for blueprint classes
FolderBased {
/// The marker file that must exist in the folder
marker_file: String,
/// Additional files/folders that should be created in a new instance
template_structure: Vec<PathTemplate>,
/// The name of the folder to create
templates: Vec<TemplateFolder>,
},
}
pub struct TemplateFile {
/// The name of the file to create
pub name: String,
/// The content of the file (as JSON)
pub content: serde_json::Value,
}
pub struct TemplateFolder {
/// The name of the folder to create
pub name: String,
/// The content of the folder (as JSON)
pub content: serde_json::Value,
/// Additional files/folders that should be created in the folder
pub template_structure: Vec<PathTemplate>,
}
/// Template for creating files/folders in a folder-based file type.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PathTemplate {
/// Create a file with default content
File { path: String, content: String },
/// Create a folder
Folder { path: String },
}
// ============================================================================
// Plugin Error
// ============================================================================
/// Errors that can occur in plugin operations.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PluginError {
/// Failed to load file
FileLoadError { path: PathBuf, message: String },
/// Failed to save file
FileSaveError { path: PathBuf, message: String },
/// Invalid file format
InvalidFormat { expected: String, message: String },
/// Editor not found
EditorNotFound { editor_id: EditorId },
/// File type not supported
UnsupportedFileType { file_type_id: FileTypeId },
/// Version mismatch
VersionMismatch {
expected: VersionInfo,
actual: VersionInfo,
},
/// Generic error
Other { message: String },
/// Filesystem access denied by FsContext sandbox.
AccessDenied(String),
}
impl fmt::Display for PluginError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FileLoadError { path, message } => {
write!(f, "Failed to load file {:?}: {}", path, message)
}
Self::FileSaveError { path, message } => {
write!(f, "Failed to save file {:?}: {}", path, message)
}
Self::InvalidFormat { expected, message } => {
write!(f, "Invalid format (expected {}): {}", expected, message)
}
Self::EditorNotFound { editor_id } => {
write!(f, "Editor not found: {}", editor_id)
}
Self::UnsupportedFileType { file_type_id } => {
write!(f, "Unsupported file type: {}", file_type_id)
}
Self::VersionMismatch { expected, actual } => {
write!(
f,
"Version mismatch: expected {:?}, got {:?}",
expected, actual
)
}
Self::Other { message } => write!(f, "{}", message),
Self::AccessDenied(reason) => write!(f, "Access denied: {}", reason),
}
}
}
/// Metadata describing an editor that a plugin provides.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EditorMetadata {
/// Unique identifier for this editor
pub id: EditorId,
/// Human-readable name for this editor (Displayed in tabs)
pub display_name: String,
/// AI Tooling
pub ai_tooling: Option<AiTooling>,
}
/// AI Tooling configuration for an editor.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiTooling {
// Integrate with toolbelt_rs to allow registration of fns as AI tools via macros: https://github.com/Far-Beyond-Pulsar/ToolbeltRSc
}
// Arc<> is not at all FFI safe. Currently We have an issue in the plugin system where GPUI (WGPUI) *unavoidably* depends on Arc internally which cannot change.
//
// The leak:
//
// On frame a plugin is told "render me a frame of your UI" where it returns a heavily nested data structure including Arc data internally. This data is leaked
// across frames because when passed over FFI boundaries, the atomic counter disconnects creating a plugin side and engine side counter that never sync. This means
// when the engine is done its memory deallocates, the plugin side counter is never updated and the memory is leaked.