diff --git a/CHANGELOG.md b/CHANGELOG.md index d92e967a9b..1e29a2cd6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features/Changes +- [#1643](https://github.com/lapce/lapce/pull/1643): Use https://plugins.lapce.dev/ as the plugin registry - [#1620](https://github.com/lapce/lapce/pull/1620): Added "Show Hover" keybinding that will trigger the hover at the cursor location - [#1619](https://github.com/lapce/lapce/pull/1619): - Add active/inactive tab colours @@ -11,7 +12,6 @@ - Replace custom drawn checkboxes with icons in source control - Add hover effect in source control panel - Add colour preview in settings -- [#1619](https://github.com/lapce/lapce/pull/1619): Add active/inactive tab colours, add primary button colour, replace custom drawn checkboxes with icons in source - [#1617](https://github.com/lapce/lapce/pull/1617): Fixed a stack overflow that would crash lapce when attempting to sort a large number of PaletteItems - [#1609](https://github.com/lapce/lapce/pull/1609): Add syntax highlighting for erlang - [#1590](https://github.com/lapce/lapce/pull/1590): Added ability to open file and file diff from source control context menu diff --git a/Cargo.lock b/Cargo.lock index c78efabe10..3061fd09eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2638,6 +2638,7 @@ dependencies = [ "crossbeam-channel", "directories", "dyn-clone", + "flate2", "git2", "globset", "grep-matcher", @@ -2661,6 +2662,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "tar", "toml_edit", "trash", "url", diff --git a/lapce-data/src/command.rs b/lapce-data/src/command.rs index 383edef3b8..4145ccf049 100644 --- a/lapce-data/src/command.rs +++ b/lapce-data/src/command.rs @@ -606,8 +606,9 @@ pub enum LapceUICommand { LoadPlugins(Vec), LoadPluginsFailed, VoltInstalled(VoltMetadata, bool), - VoltInstalling(VoltMetadata, String), + VoltInstalling(VoltInfo, String), VoltRemoving(VoltMetadata, String), + VoltInstallStatusClear(String), VoltRemoved(VoltInfo, bool), EnableVolt(VoltInfo), DisableVolt(VoltInfo), diff --git a/lapce-data/src/plugin.rs b/lapce-data/src/plugin.rs index b1ab6cc901..ed1410e170 100644 --- a/lapce-data/src/plugin.rs +++ b/lapce-data/src/plugin.rs @@ -7,8 +7,8 @@ use druid::{ExtEventSink, Target, WidgetId}; use indexmap::IndexMap; use lapce_proxy::plugin::{download_volt, wasi::find_all_volts}; use lapce_rpc::plugin::{VoltInfo, VoltMetadata}; -use lsp_types::Url; use plugin_install_status::PluginInstallStatus; +use serde::{Deserialize, Serialize}; use strum_macros::Display; use crate::{ @@ -86,6 +86,11 @@ pub enum PluginLoadStatus { Success, } +#[derive(Deserialize, Serialize)] +struct PluginsInfo { + plugins: Vec, +} + impl PluginData { pub fn new( tab_id: WidgetId, @@ -152,7 +157,7 @@ impl PluginData { if meta.version == volt.version { PluginStatus::Installed } else { - PluginStatus::Upgrade(volt.meta.clone()) + PluginStatus::Upgrade } } else { PluginStatus::Installed @@ -163,9 +168,10 @@ impl PluginData { } fn load_volts() -> Result> { - let volts: Vec = - reqwest::blocking::get("https://lapce.dev/volts2")?.json()?; - Ok(volts) + let plugins: PluginsInfo = + reqwest::blocking::get("https://plugins.lapce.dev/api/v1/plugins")? + .json()?; + Ok(plugins.plugins) } pub fn download_readme( @@ -174,8 +180,10 @@ impl PluginData { config: &LapceConfig, event_sink: ExtEventSink, ) -> Result<()> { - let url = Url::parse(&volt.meta)?; - let url = url.join("./README.md")?; + let url = format!( + "https://plugins.lapce.dev/api/v1/plugins/{}/{}/{}/readme", + volt.author, volt.name, volt.version + ); let resp = reqwest::blocking::get(url)?; if resp.status() != 200 { let text = parse_markdown("Plugin doesn't have a README", 2.0, config); @@ -197,27 +205,19 @@ impl PluginData { } pub fn install_volt(proxy: Arc, volt: VoltInfo) -> Result<()> { - let meta_str = reqwest::blocking::get(&volt.meta)?.text()?; - let meta: VoltMetadata = toml_edit::easy::from_str(&meta_str)?; + proxy.core_rpc.volt_installing(volt.clone(), "".to_string()); - proxy.core_rpc.volt_installing(meta.clone(), "".to_string()); - - if meta.wasm.is_some() { + if volt.wasm { proxy.proxy_rpc.install_volt(volt); } else { std::thread::spawn(move || -> Result<()> { - let download_volt_result = - download_volt(volt, false, &meta, &meta_str); + let download_volt_result = download_volt(&volt); if let Err(err) = download_volt_result { log::warn!("download_volt err: {err:?}"); proxy.core_rpc.volt_installing( - meta.clone(), - "Could not download Volt".to_string(), + volt.clone(), + "Could not download Plugin".to_string(), ); - std::thread::spawn(move || { - std::thread::sleep(std::time::Duration::from_secs(3)); - proxy.core_rpc.volt_installed(meta, true); - }); return Ok(()); } @@ -247,10 +247,6 @@ impl PluginData { meta.clone(), "Could not remove Plugin Directory".to_string(), ); - std::thread::spawn(move || { - std::thread::sleep(std::time::Duration::from_secs(3)); - proxy.core_rpc.volt_removed(meta.info(), true); - }); } else { proxy.core_rpc.volt_removed(meta.info(), false); } @@ -265,6 +261,6 @@ impl PluginData { pub enum PluginStatus { Installed, Install, - Upgrade(String), + Upgrade, Disabled, } diff --git a/lapce-proxy/Cargo.toml b/lapce-proxy/Cargo.toml index ad57b6aa40..becb16a1e2 100644 --- a/lapce-proxy/Cargo.toml +++ b/lapce-proxy/Cargo.toml @@ -5,6 +5,8 @@ authors = ["Dongdong Zhou "] edition = "2021" [dependencies] +flate2 = "1.0.24" +tar = "0.4.38" interprocess = "1.1.1" clap = { version = "3.2.17", features = ["derive"] } once_cell = "1.15" diff --git a/lapce-proxy/src/plugin/mod.rs b/lapce-proxy/src/plugin/mod.rs index 1a8a188814..d575c86077 100644 --- a/lapce-proxy/src/plugin/mod.rs +++ b/lapce-proxy/src/plugin/mod.rs @@ -6,7 +6,6 @@ pub mod wasi; use std::{ collections::HashMap, fs, - io::Write, path::{Path, PathBuf}, sync::{ atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}, @@ -18,6 +17,7 @@ use std::{ use anyhow::{anyhow, Result}; use crossbeam_channel::{Receiver, Sender}; use dyn_clone::DynClone; +use flate2::read::GzDecoder; use lapce_core::directory::Directory; use lapce_rpc::{ core::CoreRpcHandler, @@ -50,6 +50,7 @@ use lsp_types::{ use parking_lot::Mutex; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; +use tar::Archive; use self::{ catalog::PluginCatalog, @@ -930,79 +931,37 @@ pub enum PluginNotification { }, } -pub fn download_volt( - volt: VoltInfo, - wasm: bool, - meta: &VoltMetadata, - meta_str: &String, -) -> Result { - if meta.wasm.is_some() != wasm { - return Err(anyhow!("plugin type not fit")); +pub fn download_volt(volt: &VoltInfo) -> Result { + let url = format!( + "https://plugins.lapce.dev/api/v1/plugins/{}/{}/{}/download", + volt.author, volt.name, volt.version + ); + + let resp = reqwest::blocking::get(url)?; + if !resp.status().is_success() { + return Err(anyhow!("can't download plugin")); + } + + // this is the s3 url + let url = resp.text()?; + + let mut resp = reqwest::blocking::get(url)?; + if !resp.status().is_success() { + return Err(anyhow!("can't download plugin")); } let id = volt.id(); - let path = Directory::plugins_directory() + let plugin_dir = Directory::plugins_directory() .ok_or_else(|| anyhow!("can't get plugin directory"))? .join(&id); - let _ = fs::remove_dir_all(&path); + let _ = fs::remove_dir_all(&plugin_dir); + fs::create_dir_all(&plugin_dir)?; - fs::create_dir_all(&path)?; - let meta_path = path.join("volt.toml"); - { - let mut file = fs::OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(&meta_path)?; - file.write_all(meta_str.as_bytes())?; - } - let url = url::Url::parse(&volt.meta)?; - if let Some(wasm) = meta.wasm.as_ref() { - let url = url.join(wasm)?; - { - let mut resp = reqwest::blocking::get(url)?; - if let Some(path) = path.join(wasm).parent() { - if !path.exists() { - fs::DirBuilder::new().recursive(true).create(path)?; - } - } - let mut file = fs::OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(path.join(wasm))?; - std::io::copy(&mut resp, &mut file)?; - } - } - if let Some(themes) = meta.color_themes.as_ref() { - for theme in themes { - let url = url.join(theme)?; - { - let mut resp = reqwest::blocking::get(url)?; - let mut file = std::fs::OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(path.join(theme))?; - std::io::copy(&mut resp, &mut file)?; - } - } - } - if let Some(themes) = meta.icon_themes.as_ref() { - for theme in themes { - let url = url.join(theme)?; - { - let mut resp = reqwest::blocking::get(url)?; - let mut file = std::fs::OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(path.join(theme))?; - std::io::copy(&mut resp, &mut file)?; - } - } - } + let tar = GzDecoder::new(&mut resp); + let mut archive = Archive::new(tar); + archive.unpack(&plugin_dir)?; + let meta_path = plugin_dir.join("volt.toml"); let meta = load_volt(&meta_path)?; Ok(meta) } @@ -1013,31 +972,18 @@ pub fn install_volt( configurations: Option>, volt: VoltInfo, ) -> Result<()> { - let meta_str = reqwest::blocking::get(&volt.meta)?.text()?; - let meta: VoltMetadata = toml_edit::easy::from_str(&meta_str)?; - - thread::spawn(move || -> Result<()> { - let download_volt_result = download_volt(volt, true, &meta, &meta_str); - if download_volt_result.is_err() { - catalog_rpc.core_rpc.volt_installing( - meta.clone(), - "Could not download Volt".to_string(), - ); - std::thread::spawn(move || { - std::thread::sleep(std::time::Duration::from_secs(3)); - catalog_rpc.core_rpc.volt_installed(meta, true); - }); - return Ok(()); - } - - let meta = download_volt_result?; - let local_catalog_rpc = catalog_rpc.clone(); - let local_meta = meta.clone(); - - let _ = start_volt(workspace, configurations, local_catalog_rpc, local_meta); - catalog_rpc.core_rpc.volt_installed(meta, false); - Ok(()) - }); + let download_volt_result = download_volt(&volt); + if download_volt_result.is_err() { + catalog_rpc + .core_rpc + .volt_installing(volt, "Could not download Plugin".to_string()); + } + let meta = download_volt_result?; + let local_catalog_rpc = catalog_rpc.clone(); + let local_meta = meta.clone(); + + let _ = start_volt(workspace, configurations, local_catalog_rpc, local_meta); + catalog_rpc.core_rpc.volt_installed(meta, false); Ok(()) } @@ -1058,10 +1004,6 @@ pub fn remove_volt( volt.clone(), "Could not remove Plugin Directory".to_string(), ); - std::thread::spawn(move || { - std::thread::sleep(std::time::Duration::from_secs(3)); - catalog_rpc.core_rpc.volt_removed(volt.info(), true); - }); } else { catalog_rpc.core_rpc.volt_removed(volt.info(), false); } diff --git a/lapce-rpc/src/core.rs b/lapce-rpc/src/core.rs index 0967549ea8..afde05d704 100644 --- a/lapce-rpc/src/core.rs +++ b/lapce-rpc/src/core.rs @@ -76,7 +76,7 @@ pub enum CoreNotification { only_installing: bool, }, VoltInstalling { - volt: VoltMetadata, + volt: VoltInfo, error: String, }, VoltRemoving { @@ -240,7 +240,7 @@ impl CoreRpcHandler { }); } - pub fn volt_installing(&self, volt: VoltMetadata, error: String) { + pub fn volt_installing(&self, volt: VoltInfo, error: String) { self.notification(CoreNotification::VoltInstalling { volt, error }); } diff --git a/lapce-rpc/src/plugin.rs b/lapce-rpc/src/plugin.rs index 780583c965..1934ed1dfe 100644 --- a/lapce-rpc/src/plugin.rs +++ b/lapce-rpc/src/plugin.rs @@ -24,14 +24,14 @@ pub struct PluginConfiguration { } #[derive(Deserialize, Clone, Debug, Serialize)] -#[serde(rename_all = "kebab-case")] pub struct VoltInfo { pub name: String, pub version: String, pub display_name: String, pub author: String, pub description: String, - pub meta: String, + pub repository: Option, + pub wasm: bool, } impl VoltInfo { @@ -83,7 +83,8 @@ impl VoltMetadata { display_name: self.display_name.clone(), author: self.author.clone(), description: self.description.clone(), - meta: "".to_string(), + repository: self.repository.clone(), + wasm: self.wasm.is_some(), } } } diff --git a/lapce-ui/src/plugin.rs b/lapce-ui/src/plugin.rs index dac23a910b..e335fe63f7 100644 --- a/lapce-ui/src/plugin.rs +++ b/lapce-ui/src/plugin.rs @@ -83,7 +83,9 @@ impl Plugin { config: &LapceConfig, i: usize, ) { - let y = self.line_height * i as f64; + let rect = ctx.region().bounding_box(); + + let y = self.line_height * i as f64 + rect.y0; let x = 0.0; //0.5 * self.line_height; let text_layout = ctx @@ -310,7 +312,7 @@ impl Plugin { let color = match status { PluginStatus::Installed => LapceTheme::EDITOR_FOCUS, - PluginStatus::Upgrade(_) => LapceTheme::LAPCE_WARN, + PluginStatus::Upgrade => LapceTheme::LAPCE_WARN, _ => LapceTheme::EDITOR_DIM, }; @@ -594,6 +596,8 @@ pub struct PluginInfo { desc_text_layout: Option, author_text_layout: Option, version_text_layout: Option, + repo_text_layout: Option, + repo: Option<(Rect, String)>, line_height: f64, icon_width: f64, title_width: f64, @@ -616,6 +620,8 @@ impl PluginInfo { desc_text_layout: None, author_text_layout: None, version_text_layout: None, + repo_text_layout: None, + repo: None, icon_width: 0.0, title_width: 0.0, readme_layout, @@ -694,13 +700,27 @@ impl Widget for PluginInfo { } } Event::MouseMove(mouse_event) => { - if self.status_rect.contains(mouse_event.pos) { + let on_repo = self + .repo + .as_ref() + .map(|(r, _)| r.contains(mouse_event.pos)) + .unwrap_or(false); + if on_repo || self.status_rect.contains(mouse_event.pos) { ctx.set_cursor(&Cursor::Pointer); } else { ctx.clear_cursor(); } } Event::MouseDown(mouse_event) => { + if let Some((r, s)) = self.repo.as_ref() { + if r.contains(mouse_event.pos) { + ctx.submit_command(Command::new( + LAPCE_UI_COMMAND, + LapceUICommand::OpenURI(s.to_string()), + Target::Widget(data.id), + )); + } + } if self.status_rect.contains(mouse_event.pos) { status_on_click(ctx, data, &self.volt_id, mouse_event.pos); } @@ -855,7 +875,7 @@ impl Widget for PluginInfo { self.icon_width = self.name_text_layout.as_ref().unwrap().size().height * 2.0 - + self.line_height * 3.0; + + self.line_height * 4.0; self.title_width = self .name_text_layout .as_ref() @@ -892,10 +912,57 @@ impl Widget for PluginInfo { + self.readme_layout.size().height + self.gap; + self.repo_text_layout = { + let text = format!( + "Repository: {}", + volt.repository.as_deref().unwrap_or("") + ); + let layout = ctx + .text() + .new_text_layout(text) + .font( + data.config.ui.font_family(), + data.config.ui.font_size() as f64, + ) + .text_color( + data.config + .get_color_unchecked(LapceTheme::EDITOR_FOREGROUND) + .clone(), + ) + .range_attribute( + 12.., + TextAttribute::TextColor( + data.config + .get_color_unchecked(LapceTheme::EDITOR_LINK) + .clone(), + ), + ) + .build() + .unwrap(); + + if let Some(repo) = volt.repository.as_ref() { + let padding = self.get_margin(bc.max().width.max(info_width)); + let shift = layout.hit_test_text_position(12).point.x; + let x = padding + self.padding + self.icon_width + shift; + let y = self.gap + + self.name_text_layout.as_ref().unwrap().size().height + * 2.0 + + self.line_height; + let rect = + Size::new(layout.size().width - shift, self.line_height) + .to_rect() + .with_origin(Point::new(x, y)); + self.repo = Some((rect, repo.to_string())); + } + + Some(layout) + }; + (info_width, height) } else { (0.0, 0.0) }; + Size::new(bc.max().width.max(width), bc.max().height.max(height)) } @@ -912,6 +979,10 @@ impl Widget for PluginInfo { let desc_y = y; y += self.line_height; + let repo_text_layout = self.repo_text_layout.as_ref().unwrap(); + let repo_y = y; + y += self.line_height; + let author_text_layout = self.author_text_layout.as_ref().unwrap(); let author_y = y; y += self.line_height; @@ -954,6 +1025,13 @@ impl Widget for PluginInfo { author_y + author_text_layout.y_offset(self.line_height), ), ); + ctx.draw_text( + repo_text_layout, + Point::new( + padding + self.padding + self.icon_width, + repo_y + repo_text_layout.y_offset(self.line_height), + ), + ); ctx.draw_text( version_text_layout, @@ -1037,9 +1115,8 @@ fn status_on_click(ctx: &mut EventCtx, data: &LapceTabData, id: &str, pos: Point if let Some(meta) = data.plugin.installed.get(id) { let mut menu = druid::Menu::::new("Plugin"); - if let PluginStatus::Upgrade(meta_link) = status { - let mut info = meta.info(); - info.meta = meta_link; + if let PluginStatus::Upgrade = status { + let info = meta.info(); let proxy = data.proxy.clone(); let item = druid::MenuItem::new("Upgrade Plugin").on_activate( move |_ctx, _data, _env| { diff --git a/lapce-ui/src/tab.rs b/lapce-ui/src/tab.rs index abe2748cb9..e2b9010d04 100644 --- a/lapce-ui/src/tab.rs +++ b/lapce-ui/src/tab.rs @@ -1000,6 +1000,22 @@ impl LapceTab { LapceUICommand::VoltInstalling(volt, error) => { let plugin = Arc::make_mut(&mut data.plugin); + let event_sink = ctx.get_external_handle(); + let id = data.id; + let volt_id = volt.id(); + if !error.is_empty() { + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_secs( + 3, + )); + let _ = event_sink.submit_command( + LAPCE_UI_COMMAND, + LapceUICommand::VoltInstallStatusClear(volt_id), + Target::Widget(id), + ); + }); + } + if let Some(elem) = plugin.installing.get_mut(&volt.id()) { if !error.is_empty() { elem.set_error(error); @@ -1018,6 +1034,22 @@ impl LapceTab { LapceUICommand::VoltRemoving(volt, error) => { let plugin = Arc::make_mut(&mut data.plugin); + let event_sink = ctx.get_external_handle(); + let id = data.id; + let volt_id = volt.id(); + if !error.is_empty() { + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_secs( + 3, + )); + let _ = event_sink.submit_command( + LAPCE_UI_COMMAND, + LapceUICommand::VoltInstallStatusClear(volt_id), + Target::Widget(id), + ); + }); + } + if let Some(elem) = plugin.installing.get_mut(&volt.id()) { if !error.is_empty() { elem.set_error(error); @@ -1033,6 +1065,10 @@ impl LapceTab { ); } } + LapceUICommand::VoltInstallStatusClear(volt_id) => { + let plugin = Arc::make_mut(&mut data.plugin); + plugin.installing.remove(volt_id); + } LapceUICommand::VoltRemoved(volt, only_installing) => { let plugin = Arc::make_mut(&mut data.plugin); let id = volt.id(); @@ -2172,6 +2208,10 @@ impl Widget for LapceTab { ctx.request_paint(); } + if !old_data.plugin.same(&data.plugin) { + ctx.request_paint(); + } + if old_data.about.active != data.about.active { ctx.request_layout(); }