Skip to main content

fresh/services/plugins/
plugin_dev_workspace.rs

1//! Plugin development workspace for LSP support
2//!
3//! When a user loads a plugin from a buffer, this module creates a temporary
4//! workspace directory containing:
5//! - The buffer content as a `.ts` file
6//! - A copy of `fresh.d.ts` for type definitions
7//! - A `tsconfig.json` configured for the plugin environment
8//!
9//! This allows `typescript-language-server` to provide autocomplete, type checking,
10//! and hover documentation for plugin buffers — including unsaved/unnamed buffers.
11
12use std::path::{Path, PathBuf};
13
14/// Manages a temporary workspace for plugin development LSP support.
15pub struct PluginDevWorkspace {
16    /// Root directory of the workspace (e.g., `~/.cache/fresh/plugin-dev/{buffer_id}/`)
17    dir: PathBuf,
18    /// Path to the plugin source file within the workspace
19    pub plugin_file: PathBuf,
20}
21
22/// The tsconfig.json content for plugin development.
23/// - No DOM lib (plugins run in QuickJS, not a browser)
24/// - `types: []` prevents picking up @types/node or other ambient types
25/// - `skipLibCheck: true` avoids checking fresh.d.ts itself
26const TSCONFIG_CONTENT: &str = r#"{
27  "compilerOptions": {
28    "target": "ES2020",
29    "module": "ES2020",
30    "moduleResolution": "node",
31    "strict": true,
32    "noEmit": true,
33    "skipLibCheck": true,
34    "lib": ["ES2020"],
35    "types": []
36  },
37  "files": ["fresh.d.ts", "plugin.ts"]
38}
39"#;
40
41impl PluginDevWorkspace {
42    /// Create a new plugin dev workspace for the given buffer.
43    ///
44    /// This creates a temp directory, copies `fresh.d.ts` from the source,
45    /// writes `tsconfig.json`, and writes the buffer content to `plugin.ts`.
46    ///
47    /// # Arguments
48    /// * `buffer_id` - The buffer ID (used to create a unique directory)
49    /// * `content` - The current buffer content
50    /// * `fresh_dts_source` - Path to the `fresh.d.ts` file to copy
51    pub fn create(
52        buffer_id: usize,
53        content: &str,
54        fresh_dts_source: &Path,
55    ) -> Result<Self, String> {
56        let cache_dir =
57            dirs::cache_dir().ok_or_else(|| "Could not determine cache directory".to_string())?;
58        let dir = cache_dir
59            .join("fresh")
60            .join("plugin-dev")
61            .join(buffer_id.to_string());
62
63        // Create directory
64        std::fs::create_dir_all(&dir)
65            .map_err(|e| format!("Failed to create plugin dev workspace: {}", e))?;
66
67        // Copy fresh.d.ts
68        let dest_dts = dir.join("fresh.d.ts");
69        std::fs::copy(fresh_dts_source, &dest_dts)
70            .map_err(|e| format!("Failed to copy fresh.d.ts: {}", e))?;
71
72        // Write tsconfig.json
73        let tsconfig_path = dir.join("tsconfig.json");
74        std::fs::write(&tsconfig_path, TSCONFIG_CONTENT)
75            .map_err(|e| format!("Failed to write tsconfig.json: {}", e))?;
76
77        // Write buffer content as plugin.ts
78        let plugin_file = dir.join("plugin.ts");
79        std::fs::write(&plugin_file, content)
80            .map_err(|e| format!("Failed to write plugin.ts: {}", e))?;
81
82        tracing::info!(
83            "Created plugin dev workspace at {:?} for buffer {}",
84            dir,
85            buffer_id
86        );
87
88        Ok(Self { dir, plugin_file })
89    }
90
91    /// Get the path to the workspace directory.
92    pub fn dir(&self) -> &Path {
93        &self.dir
94    }
95
96    /// Clean up the workspace directory.
97    pub fn cleanup(&self) {
98        if self.dir.exists() {
99            if let Err(e) = std::fs::remove_dir_all(&self.dir) {
100                tracing::warn!(
101                    "Failed to clean up plugin dev workspace {:?}: {}",
102                    self.dir,
103                    e
104                );
105            } else {
106                tracing::debug!("Cleaned up plugin dev workspace {:?}", self.dir);
107            }
108        }
109    }
110}
111
112impl Drop for PluginDevWorkspace {
113    fn drop(&mut self) {
114        self.cleanup();
115    }
116}