diff --git a/crates/ide-extension/src/lib.rs b/crates/ide-extension/src/lib.rs index 9089067a..880f6e02 100644 --- a/crates/ide-extension/src/lib.rs +++ b/crates/ide-extension/src/lib.rs @@ -27,8 +27,8 @@ mod when; pub use capability::{Capability, CapabilityInfo, Risk, host_matches}; pub use index::ContributionIndex; pub use manifest::{ - ActivationEvent, Capabilities, ENGINE_VERSION, EVENT_KINDS, LEGACY_API, MANIFEST_FILE, Manifest, check_relative, - engine_compatible, validate_item_id, validate_plugin_id, validate_point_id, + ActivationEvent, CATEGORIES, Capabilities, ENGINE_VERSION, EVENT_KINDS, LEGACY_API, MANIFEST_FILE, Manifest, + check_relative, engine_compatible, validate_item_id, validate_plugin_id, validate_point_id, }; pub use point::{Commit, Disposable, ExtensionPoint, MAX_PACKAGE_FILE, Package, Placement, Prepared, decode}; pub use registry::{ContributionInfo, ExtensionRegistry, PreparedSet}; diff --git a/crates/ide-extension/src/manifest.rs b/crates/ide-extension/src/manifest.rs index 99820d5f..25d19c45 100644 --- a/crates/ide-extension/src/manifest.rs +++ b/crates/ide-extension/src/manifest.rs @@ -35,6 +35,11 @@ pub const MANIFEST_FILE: &str = "plugin.toml"; pub const ENGINE_VERSION: &str = "1.0.0"; /// The 0.1 API, still accepted for contribution-only packages and 0.1 components. pub const LEGACY_API: &str = "0.1"; +/// Marketplace categories, in the order they are shown. +pub const CATEGORIES: &[&str] = + &["languages", "themes", "icon-themes", "linters", "vcs", "ai", "frameworks", "tools", "other"]; +/// What a manifest may declare in `categories`. +const MAX_CATEGORIES: usize = 3; #[derive(Clone, Debug, PartialEq)] pub struct Manifest { @@ -47,6 +52,8 @@ pub struct Manifest { pub license: Option, /// Release notes of this version (Settings › Plugins › What's New). pub change_notes: Option, + /// Marketplace categories as declared (see [`Manifest::categories`]). + pub declared_categories: Vec, /// `engines.openidea` (a semver requirement), or the legacy `openidea-api`. pub engine: String, /// Written in the 0.1 format. @@ -166,6 +173,8 @@ struct Raw { #[serde(default)] change_notes: Option, #[serde(default)] + categories: Vec, + #[serde(default)] engines: Option, /// 0.1 format. #[serde(default)] @@ -243,6 +252,7 @@ impl Manifest { homepage: raw.homepage, license: raw.license, change_notes: raw.change_notes, + declared_categories: raw.categories, engine, legacy, wasm: raw.wasm, @@ -290,6 +300,12 @@ impl Manifest { return Err(format!("duplicate activation event {event}")); } } + if self.declared_categories.len() > MAX_CATEGORIES { + return Err(format!("`categories` lists at most {MAX_CATEGORIES}")); + } + for c in &self.declared_categories { + validate_item_id(c, "category")?; + } for (point, decls) in &self.contributes { validate_point_id(point)?; if !matches!(decls, toml::Value::Array(_) | toml::Value::Table(_)) { @@ -325,6 +341,33 @@ impl Manifest { } } +impl Manifest { + /// Its marketplace categories, in [`CATEGORIES`] order: those declared that + /// this IDE knows; without any, those its contributions imply (themes, + /// icon themes, languages), else `other`. + pub fn categories(&self) -> Vec { + let pick = |f: &dyn Fn(&str) -> bool| CATEGORIES.iter().filter(|c| f(c)).map(|c| c.to_string()).collect(); + let mut out: Vec = pick(&|c| self.declared_categories.iter().any(|d| d == c)); + if out.is_empty() { + out = pick(&|c| match c { + "themes" => self.contributes.contains_key("themes"), + "icon-themes" => self.contributes.contains_key("iconThemes"), + "languages" => self.contributes.contains_key("languages"), + _ => false, + }); + } + if out.is_empty() { + out.push("other".into()); + } + out + } + + /// Declared categories this IDE doesn't know (ignored). + pub fn unknown_categories(&self) -> Vec<&str> { + self.declared_categories.iter().map(String::as_str).filter(|c| !CATEGORIES.contains(c)).collect() + } +} + /// Whether this host can load a plugin written for `engine`. pub fn engine_compatible(engine: &str, legacy: bool) -> bool { if legacy { @@ -417,6 +460,24 @@ mod tests { assert!(ActivationEvent::parse("on-event:scheduled").is_err(), "declared with on-schedule"); } + #[test] + fn categories_are_declared_or_implied() { + let head = "id = \"acme.x\"\nname = \"X\"\nversion = \"1.0.0\"\nengines.openidea = \"^1.0\"\n"; + let m = |rest: &str| Manifest::parse(&format!("{head}{rest}")); + // Declared, in the catalogue's order; unknown ones (a newer IDE's) are ignored. + let declared = m("categories = [\"tools\", \"vcs\", \"robots\"]\n").unwrap(); + assert_eq!(declared.categories(), ["vcs", "tools"]); + assert_eq!(declared.unknown_categories(), ["robots"]); + // None declared: implied by the contributions, else `other`. + let theme = m("[[contributes.themes]]\nid = \"t\"\n[[contributes.languages]]\nid = \"l\"\n").unwrap(); + assert_eq!(theme.categories(), ["languages", "themes"]); + let declared_wins = m("categories = [\"tools\"]\n[[contributes.languages]]\nid = \"l\"\n").unwrap(); + assert_eq!(declared_wins.categories(), ["tools"]); + assert_eq!(m("").unwrap().categories(), ["other"]); + assert!(m("categories = [\"a\", \"b\", \"c\", \"d\"]\n").unwrap_err().contains("at most 3")); + assert!(m("categories = [\"Bad Name\"]\n").is_err()); + } + #[test] fn parses_the_02_format() { let m = Manifest::parse(FULL).unwrap(); diff --git a/crates/ide-extension/src/package.rs b/crates/ide-extension/src/package.rs index c793e0c6..5cd17277 100644 --- a/crates/ide-extension/src/package.rs +++ b/crates/ide-extension/src/package.rs @@ -288,6 +288,9 @@ pub struct IndexEntry { /// Empty: any. #[serde(default)] pub engine: String, + /// Marketplace categories (`CATEGORIES`); an IDE ignores those it doesn't know. + #[serde(default)] + pub categories: Vec, } impl IndexEntry { @@ -522,6 +525,7 @@ mod tests { digest: "d".into(), capabilities: Vec::new(), engine: String::new(), + categories: Vec::new(), } } diff --git a/crates/ide-host/src/bin/openidea-plugin/main.rs b/crates/ide-host/src/bin/openidea-plugin/main.rs index 481a5ed2..107d6149 100644 --- a/crates/ide-host/src/bin/openidea-plugin/main.rs +++ b/crates/ide-host/src/bin/openidea-plugin/main.rs @@ -364,6 +364,10 @@ fn check(dir: &Path) -> Result<(), String> { } else { println!(" code: none (contributions only)"); } + println!(" categories: {}", manifest.categories().join(", ")); + for c in manifest.unknown_categories() { + println!(" warning: unknown category {c:?} (ignored); use {}", ide_extension::CATEGORIES.join(", ")); + } if !manifest.activation.is_empty() { let events: Vec = manifest.activation.iter().map(ToString::to_string).collect(); println!(" activation: {}", events.join(", ")); @@ -616,6 +620,7 @@ fn cmd_index(args: &[String]) -> Result<(), String> { digest, capabilities: m.capabilities.all().map(|c| c.to_string()).collect(), engine: package::engine_requirement(&m.engine, built_for.as_ref()), + categories: m.categories(), }) })(); let entry = entry.map_err(|e| format!("{}: {e}", path.display()))?; @@ -790,6 +795,7 @@ mod tests { let index = Index::parse(&std::fs::read_to_string(&official_index).unwrap()).unwrap(); let listed: Vec<_> = index.plugins.iter().map(|e| (e.id.as_str(), e.publisher.as_str())).collect(); assert_eq!(listed, [("alice.dawn", "alice"), ("openidea.night", "openidea")]); + assert_eq!(index.plugins[1].categories, ["themes"], "implied by the theme"); assert_eq!(index.plugins[1].download, "openidea.night-0.1.0.oiplugin"); assert!(Path::new(&index.plugins[0].download).is_absolute(), "{}", index.plugins[0].download); diff --git a/crates/ide-host/src/bin/openidea-plugin/templates.rs b/crates/ide-host/src/bin/openidea-plugin/templates.rs index fc23cdd0..6c1c04e0 100644 --- a/crates/ide-host/src/bin/openidea-plugin/templates.rs +++ b/crates/ide-host/src/bin/openidea-plugin/templates.rs @@ -82,6 +82,9 @@ name = "__NAME__" version = "0.1.0" description = "Adds two sample commands." authors = [] +# Marketplace categories (up to 3): languages, themes, icon-themes, linters, +# vcs, ai, frameworks, tools, other. +categories = ["tools"] engines.openidea = "^1.0" # Built by `openidea-plugin build`. The code is loaded the first time one of # its commands runs; the commands below show in Find Action before that. diff --git a/crates/ide-host/src/plugins/mod.rs b/crates/ide-host/src/plugins/mod.rs index 6068c7b5..c32c028c 100644 --- a/crates/ide-host/src/plugins/mod.rs +++ b/crates/ide-host/src/plugins/mod.rs @@ -2214,6 +2214,7 @@ fn placeholder_manifest(dir: &Path) -> Manifest { homepage: None, license: None, change_notes: None, + declared_categories: Vec::new(), engine: ide_extension::ENGINE_VERSION.into(), legacy: false, wasm: None, diff --git a/crates/ide-host/tests/distribution.rs b/crates/ide-host/tests/distribution.rs index b6966c6a..e13ff8c3 100644 --- a/crates/ide-host/tests/distribution.rs +++ b/crates/ide-host/tests/distribution.rs @@ -151,6 +151,7 @@ fn signed_hello(dir: &Path, version: &str, key: &PublisherKey, host: &PluginHost digest, capabilities: vec![], engine: "^1.0".into(), + categories: vec!["tools".into()], }; (out, entry) } diff --git a/plugins/README.md b/plugins/README.md index 54ea96ae..9b05a83b 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -79,6 +79,7 @@ authors = ["You "] homepage = "https://example.com" # optional license = "MIT" # optional change-notes = "1.2.0 — …" # shown under What's New +categories = ["tools"] # marketplace categories, see below engines.openidea = "^1.0" # the plugin API this package needs wasm = "dist/plugin.wasm" # omit for data-only plugins activation = ["on-language:markdown"] # optional, see below @@ -94,6 +95,12 @@ category = "Word Tools" # group in Search Everywhere / Keymap when = "editorFocus" # optional: same expressions as built-in actions ``` +`categories` (up to 3) file the plugin in the marketplace: `languages`, +`themes`, `icon-themes`, `linters` (linters and formatters), `vcs`, `ai`, +`frameworks`, `tools`, `other`. Without them, a plugin contributing themes, +icon themes or languages is filed under those, any other under `other`. +Categories an IDE doesn't know are ignored (`check` warns about them). + All paths are relative to the package directory and may not leave it. A manifest is validated as a whole: if one contribution is invalid, nothing is installed or loaded. Contribution types the IDE does not know (a newer API) are @@ -782,7 +789,8 @@ and the publisher's key, then shows the install page. manifest's `engines.openidea`, narrowed to the API its component was built for), and a plugin may be listed in several versions. Settings › Plugins offers the newest version this IDE can load; when there is none, it shows the -plugin as incompatible and says which API it needs. Indexing keeps, of each +plugin as incompatible and says which API it needs. Entries also carry the +plugin's categories, which Settings › Plugins › Available filters by. Indexing keeps, of each plugin, the newest version per API requirement, so users of an older IDE keep the last version built for them. diff --git a/plugins/official/ai-completion/dist/plugin.wasm b/plugins/official/ai-completion/dist/plugin.wasm index ee11a42e..c51a0c67 100644 Binary files a/plugins/official/ai-completion/dist/plugin.wasm and b/plugins/official/ai-completion/dist/plugin.wasm differ diff --git a/plugins/official/ai-completion/dist/plugin.wasm.sources b/plugins/official/ai-completion/dist/plugin.wasm.sources index 43ce4cdf..8baa3d1f 100644 --- a/plugins/official/ai-completion/dist/plugin.wasm.sources +++ b/plugins/official/ai-completion/dist/plugin.wasm.sources @@ -1 +1 @@ -bf264a97dc918f24ac00f0e9e0d0373b68391a96921b44345b466c2a087c5283 +aa09a92c0ba9d85a01c56bbf4455e40ae44f005b443ce1002bbe8011408ff975 diff --git a/plugins/official/ai-completion/plugin.toml b/plugins/official/ai-completion/plugin.toml index 8cf1c563..bf21db21 100644 --- a/plugins/official/ai-completion/plugin.toml +++ b/plugins/official/ai-completion/plugin.toml @@ -5,6 +5,7 @@ description = "Inline code suggestions (ghost text) from a local model (Ollama) authors = ["OpenIDEA contributors"] license = "Apache-2.0" change-notes = "0.1.0 — first release: local (Ollama) and OpenAI backends; the key stays in the system keychain." +categories = ["ai"] engines.openidea = "^1.0" wasm = "dist/plugin.wasm" activation = ["on-command:*"] diff --git a/plugins/official/checkstyle/dist/plugin.wasm b/plugins/official/checkstyle/dist/plugin.wasm index b7508504..98fccd27 100644 Binary files a/plugins/official/checkstyle/dist/plugin.wasm and b/plugins/official/checkstyle/dist/plugin.wasm differ diff --git a/plugins/official/checkstyle/dist/plugin.wasm.sources b/plugins/official/checkstyle/dist/plugin.wasm.sources index 6192fb92..1c46bf44 100644 --- a/plugins/official/checkstyle/dist/plugin.wasm.sources +++ b/plugins/official/checkstyle/dist/plugin.wasm.sources @@ -1 +1 @@ -6095c7e30658b93d79270e6355d42941a4cfe13eeb8bada07acc7ae462194853 +908c2fd177ad2bb9a8a8492505151c3b81579191156c5ec53f705127f50a8ee9 diff --git a/plugins/official/checkstyle/plugin.toml b/plugins/official/checkstyle/plugin.toml index 444157b6..1732ad30 100644 --- a/plugins/official/checkstyle/plugin.toml +++ b/plugins/official/checkstyle/plugin.toml @@ -5,6 +5,7 @@ description = "Checks Java files with Checkstyle when you save them: problems in authors = ["OpenIDEA contributors"] license = "Apache-2.0" change-notes = "0.1.0 — first release: check on save (a newer save cancels the running check), Google style by default, quick fixes." +categories = ["linters"] engines.openidea = "^1.0" wasm = "dist/plugin.wasm" activation = ["on-event:file-saved", "on-command:*"] diff --git a/plugins/official/csv-editor/dist/plugin.wasm b/plugins/official/csv-editor/dist/plugin.wasm index 539a1dce..f3529166 100644 Binary files a/plugins/official/csv-editor/dist/plugin.wasm and b/plugins/official/csv-editor/dist/plugin.wasm differ diff --git a/plugins/official/csv-editor/dist/plugin.wasm.sources b/plugins/official/csv-editor/dist/plugin.wasm.sources index d1b11a17..fcd50761 100644 --- a/plugins/official/csv-editor/dist/plugin.wasm.sources +++ b/plugins/official/csv-editor/dist/plugin.wasm.sources @@ -1 +1 @@ -b96941af697ce7a6c1c8ef1539d34aa7c23d2d41dd1b2ccffdfda5648f90cf56 +64270fa0f5b3084c78764f09b8ce161742fdbff9c28fb90590da39594e558db1 diff --git a/plugins/official/csv-editor/plugin.toml b/plugins/official/csv-editor/plugin.toml index 06d920a5..e42c1d7a 100644 --- a/plugins/official/csv-editor/plugin.toml +++ b/plugins/official/csv-editor/plugin.toml @@ -5,6 +5,7 @@ description = "Edit CSV and TSV files as a table. Edits go to the file's text, s authors = ["OpenIDEA contributors"] license = "Apache-2.0" change-notes = "0.1.0 — first release: table editor for .csv and .tsv with cell editing, inserting and deleting rows; large files load the visible rows only." +categories = ["tools"] engines.openidea = "^1.0" wasm = "dist/plugin.wasm" activation = ["on-view:table"] diff --git a/plugins/official/excalidraw/plugin.toml b/plugins/official/excalidraw/plugin.toml index 70bf7ad0..fdfc495b 100644 --- a/plugins/official/excalidraw/plugin.toml +++ b/plugins/official/excalidraw/plugin.toml @@ -5,6 +5,7 @@ description = "Draw and edit Excalidraw diagrams (.excalidraw). The drawing is t authors = ["OpenIDEA contributors"] license = "Apache-2.0" change-notes = "0.1.0 — first release: the Excalidraw canvas next to (or instead of) the file's text, kept in step both ways; export to PNG and SVG through the save dialog; a file icon and a New › Excalidraw Drawing template. Based on the Excalidraw editor proposed by Deng Liu (#738)." +categories = ["tools"] engines.openidea = "^1.0" [capabilities] diff --git a/plugins/official/git-toolbox/dist/plugin.wasm b/plugins/official/git-toolbox/dist/plugin.wasm index 9479f183..c7d8ad40 100644 Binary files a/plugins/official/git-toolbox/dist/plugin.wasm and b/plugins/official/git-toolbox/dist/plugin.wasm differ diff --git a/plugins/official/git-toolbox/dist/plugin.wasm.sources b/plugins/official/git-toolbox/dist/plugin.wasm.sources index fd509401..0bcb7925 100644 --- a/plugins/official/git-toolbox/dist/plugin.wasm.sources +++ b/plugins/official/git-toolbox/dist/plugin.wasm.sources @@ -1 +1 @@ -ad545605b33c6f4e55a06a376333de3162739ed920b5d7d277942e50a4420083 +22876f842f727a2b9aa292986df3393f5b5f30c5c34319f5665f7d2701c8accf diff --git a/plugins/official/git-toolbox/plugin.toml b/plugins/official/git-toolbox/plugin.toml index ff5bbfce..d0f02117 100644 --- a/plugins/official/git-toolbox/plugin.toml +++ b/plugins/official/git-toolbox/plugin.toml @@ -5,6 +5,7 @@ description = "Fetches your repositories in the background and shows the branch authors = ["OpenIDEA contributors"] license = "Apache-2.0" change-notes = "0.1.0 — first release: auto-fetch every 5 minutes, ahead/behind in the status bar, Fetch Now." +categories = ["vcs"] engines.openidea = "^1.0" wasm = "dist/plugin.wasm" # Fetches on schedule (also after being reclaimed while idle) and refreshes diff --git a/plugins/official/graphviz/dist/plugin.wasm b/plugins/official/graphviz/dist/plugin.wasm index fa2c8114..337a9781 100644 Binary files a/plugins/official/graphviz/dist/plugin.wasm and b/plugins/official/graphviz/dist/plugin.wasm differ diff --git a/plugins/official/graphviz/dist/plugin.wasm.sources b/plugins/official/graphviz/dist/plugin.wasm.sources index 8e261ce0..f750b77c 100644 --- a/plugins/official/graphviz/dist/plugin.wasm.sources +++ b/plugins/official/graphviz/dist/plugin.wasm.sources @@ -1 +1 @@ -de353be96a6b66629901f55a8027a0f27107a7ac2fb9d891089db1b7fd716299 +14c60782eca79a25cf2c38d58c05be0eb97c4afc607d4cc83302d1445d094aab diff --git a/plugins/official/graphviz/plugin.toml b/plugins/official/graphviz/plugin.toml index 04f87a2e..8cf06eb4 100644 --- a/plugins/official/graphviz/plugin.toml +++ b/plugins/official/graphviz/plugin.toml @@ -5,6 +5,7 @@ description = "Graphviz DOT files (.dot, .gv) with a live preview beside the tex authors = ["OpenIDEA contributors"] license = "Apache-2.0" change-notes = "0.1.0 — first release: live preview (Graphviz compiled to WebAssembly), layout engines, zoom, errors as problems, SVG and PNG export." +categories = ["languages", "tools"] engines.openidea = "^1.0" wasm = "dist/plugin.wasm" activation = ["on-view:preview"] diff --git a/plugins/official/key-promoter/dist/plugin.wasm b/plugins/official/key-promoter/dist/plugin.wasm index daefc884..9ef9e3d0 100644 Binary files a/plugins/official/key-promoter/dist/plugin.wasm and b/plugins/official/key-promoter/dist/plugin.wasm differ diff --git a/plugins/official/key-promoter/dist/plugin.wasm.sources b/plugins/official/key-promoter/dist/plugin.wasm.sources index 9965326d..069068aa 100644 --- a/plugins/official/key-promoter/dist/plugin.wasm.sources +++ b/plugins/official/key-promoter/dist/plugin.wasm.sources @@ -1 +1 @@ -345c39f818e4950914bf3fcc795fac9e30ff2d87ad3e66f24c7bc00800f92980 +7f5aacde2b8a8bcee5365c8cbda91045195688899c6537dc2159037cca74a672 diff --git a/plugins/official/key-promoter/plugin.toml b/plugins/official/key-promoter/plugin.toml index 10f032b4..3a430fb9 100644 --- a/plugins/official/key-promoter/plugin.toml +++ b/plugins/official/key-promoter/plugin.toml @@ -5,6 +5,7 @@ description = "Shows the keyboard shortcut of an action you ran with the mouse, authors = ["OpenIDEA contributors"] license = "Apache-2.0" change-notes = "0.1.0 — first release: shortcut tips, statistics, per-action \"don't show again\"." +categories = ["tools"] engines.openidea = "^1.0" wasm = "dist/plugin.wasm" # Actions arrive in batches (a few seconds apart while the plugin is idle), diff --git a/plugins/official/license-header/dist/plugin.wasm b/plugins/official/license-header/dist/plugin.wasm index 6cd6d293..1c82e278 100644 Binary files a/plugins/official/license-header/dist/plugin.wasm and b/plugins/official/license-header/dist/plugin.wasm differ diff --git a/plugins/official/license-header/dist/plugin.wasm.sources b/plugins/official/license-header/dist/plugin.wasm.sources index 01859b4b..7dc3c5ab 100644 --- a/plugins/official/license-header/dist/plugin.wasm.sources +++ b/plugins/official/license-header/dist/plugin.wasm.sources @@ -1 +1 @@ -6a4210a3978f058b3526427eb41ef398924e8c011cc4dc53c19f5419d31501f3 +45a7ee993b5c234b0d25e5b05ef60e714fa3edf63d6bd0d6a29b13587c8d09ce diff --git a/plugins/official/license-header/plugin.toml b/plugins/official/license-header/plugin.toml index af970e2e..c32b4176 100644 --- a/plugins/official/license-header/plugin.toml +++ b/plugins/official/license-header/plugin.toml @@ -5,6 +5,7 @@ description = "Keeps a copyright and SPDX license header at the top of source fi authors = ["OpenIDEA contributors"] license = "Apache-2.0" change-notes = "0.1.0 — first release: missing headers reported on open and save, Add License Header quick fix and command, project scan in the background." +categories = ["tools"] engines.openidea = "^1.0" wasm = "dist/plugin.wasm" activation = ["on-event:file-opened", "on-event:file-saved", "on-command:*"] diff --git a/plugins/official/mybatis/dist/plugin.wasm b/plugins/official/mybatis/dist/plugin.wasm index 5ed3800a..ca729516 100644 Binary files a/plugins/official/mybatis/dist/plugin.wasm and b/plugins/official/mybatis/dist/plugin.wasm differ diff --git a/plugins/official/mybatis/dist/plugin.wasm.sources b/plugins/official/mybatis/dist/plugin.wasm.sources index 0241ff75..758553fc 100644 --- a/plugins/official/mybatis/dist/plugin.wasm.sources +++ b/plugins/official/mybatis/dist/plugin.wasm.sources @@ -1 +1 @@ -f7df58bb0ab2b92370b148de2614174ec1ff9b01c0078b5c20bd331f82298616 +d5cf81455ae8b2b0fd6d1402d8775913bd7999db62b9bdf87675890ce52b1e80 diff --git a/plugins/official/mybatis/plugin.toml b/plugins/official/mybatis/plugin.toml index fac11916..d5a55c69 100644 --- a/plugins/official/mybatis/plugin.toml +++ b/plugins/official/mybatis/plugin.toml @@ -5,6 +5,7 @@ description = "Jumps between MyBatis mapper interfaces and their XML statements authors = ["OpenIDEA contributors"] license = "Apache-2.0" change-notes = "0.1.0 — first release: navigation both ways, line markers, id and parameter completion." +categories = ["frameworks"] engines.openidea = "^1.0" wasm = "dist/plugin.wasm" activation = ["on-command:*"] diff --git a/plugins/official/regex-tester/dist/plugin.wasm b/plugins/official/regex-tester/dist/plugin.wasm index 9eed421b..1d39d764 100644 Binary files a/plugins/official/regex-tester/dist/plugin.wasm and b/plugins/official/regex-tester/dist/plugin.wasm differ diff --git a/plugins/official/regex-tester/dist/plugin.wasm.sources b/plugins/official/regex-tester/dist/plugin.wasm.sources index b9543105..013089c4 100644 --- a/plugins/official/regex-tester/dist/plugin.wasm.sources +++ b/plugins/official/regex-tester/dist/plugin.wasm.sources @@ -1 +1 @@ -d2e35f3e4ee6b699899ea43fdf96d5ec99f0730c502f94fd6f3246e605be99a7 +227c8150c6ad7a5a20236764f149c876cbbc3b082763bf2e7fc05de99726b740 diff --git a/plugins/official/regex-tester/plugin.toml b/plugins/official/regex-tester/plugin.toml index a889b96a..22e45435 100644 --- a/plugins/official/regex-tester/plugin.toml +++ b/plugins/official/regex-tester/plugin.toml @@ -5,6 +5,7 @@ description = "Try a regular expression against sample text: matches highlighted authors = ["OpenIDEA contributors"] license = "Apache-2.0" change-notes = "0.1.0 — first release: live matching with flags, capture groups, replacement preview, the editor's selection as test text." +categories = ["tools"] engines.openidea = "^1.0" wasm = "dist/plugin.wasm" activation = ["on-view:regex", "on-command:*"] diff --git a/plugins/official/solidity/dist/plugin.wasm b/plugins/official/solidity/dist/plugin.wasm index 3cb17fd7..4fd185f6 100644 Binary files a/plugins/official/solidity/dist/plugin.wasm and b/plugins/official/solidity/dist/plugin.wasm differ diff --git a/plugins/official/solidity/dist/plugin.wasm.sources b/plugins/official/solidity/dist/plugin.wasm.sources index d7a74f4c..20e8de03 100644 --- a/plugins/official/solidity/dist/plugin.wasm.sources +++ b/plugins/official/solidity/dist/plugin.wasm.sources @@ -1 +1 @@ -3893bd8ad06d3e19c0112081c6643b3bf3c67f5a184098432a6fd338426cd682 +bbadcaa7a86a3e0a3f8be6d86aed2229d5cdfd128fda3435b4d64c7e19ad1df2 diff --git a/plugins/official/solidity/plugin.toml b/plugins/official/solidity/plugin.toml index cc03892b..2ef3c864 100644 --- a/plugins/official/solidity/plugin.toml +++ b/plugins/official/solidity/plugin.toml @@ -5,6 +5,7 @@ description = "Solidity support: file detection, highlighting, structure view an authors = ["OpenIDEA contributors"] license = "Apache-2.0" change-notes = "0.1.0 — first release: .sol detection, highlighting, structure view, folding; language server via @nomicfoundation/solidity-language-server or solc." +categories = ["languages"] engines.openidea = "^1.0" # Highlighting, structure and folding come from this component; it is loaded # when the first Solidity file is opened. diff --git a/plugins/official/string-tools/dist/plugin.wasm b/plugins/official/string-tools/dist/plugin.wasm index 1c33e3e4..b38c2f9c 100644 Binary files a/plugins/official/string-tools/dist/plugin.wasm and b/plugins/official/string-tools/dist/plugin.wasm differ diff --git a/plugins/official/string-tools/dist/plugin.wasm.sources b/plugins/official/string-tools/dist/plugin.wasm.sources index 2dbf488f..7c78b969 100644 --- a/plugins/official/string-tools/dist/plugin.wasm.sources +++ b/plugins/official/string-tools/dist/plugin.wasm.sources @@ -1 +1 @@ -a88c39d5782369329efa55b12330daa906e5c8d91252bd57c7343648840bac79 +09cc92e8e731f8f59f3857a71f9a30cc3d9e04a1c32092920f573b0f26592855 diff --git a/plugins/official/string-tools/plugin.toml b/plugins/official/string-tools/plugin.toml index 18ff2bb5..9ff53b75 100644 --- a/plugins/official/string-tools/plugin.toml +++ b/plugins/official/string-tools/plugin.toml @@ -5,6 +5,7 @@ description = "Case conversion (camelCase, snake_case, kebab-case…), encoding authors = ["OpenIDEA contributors"] license = "Apache-2.0" change-notes = "0.1.0 — first release: 26 commands in four groups." +categories = ["tools"] engines.openidea = "^1.0" wasm = "dist/plugin.wasm" # The commands are declared below, so Find Action and the keymap list them diff --git a/src/bindings.ts b/src/bindings.ts index 0c7576fe..1e7529a6 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -4145,6 +4145,8 @@ export type IndexEntry = { * Empty: any. */ engine?: string, + /** Marketplace categories (`CATEGORIES`); an IDE ignores those it doesn't know. */ + categories?: string[], }; export type IndexStatus = { diff --git a/src/features/plugins/PluginsPage.tsx b/src/features/plugins/PluginsPage.tsx index 502791a3..1b0e9c01 100644 --- a/src/features/plugins/PluginsPage.tsx +++ b/src/features/plugins/PluginsPage.tsx @@ -18,6 +18,7 @@ import { IconButton, Input, SearchField, + Select, Spinner, Tabs, showContextMenu, @@ -572,6 +573,71 @@ function gearMenu(plugins: readonly PluginInfo[], actions: GearActions): MenuIte ]; } +const CATEGORIES = [ + "languages", + "themes", + "icon-themes", + "linters", + "vcs", + "ai", + "frameworks", + "tools", + "other", +] as const; +type Category = (typeof CATEGORIES)[number]; + +function categoryLabel(c: Category): string { + switch (c) { + case "languages": + return tx("Languages"); + case "themes": + return tx("Themes"); + case "icon-themes": + return tx("Icon Themes"); + case "linters": + return tx("Linters & Formatters"); + case "vcs": + return tx("Version Control"); + case "ai": + return tx("AI"); + case "frameworks": + return tx("Frameworks"); + case "tools": + return tx("Tools"); + case "other": + return tx("Other"); + } +} + +/** A marketplace plugin's categories this IDE knows; `other` without any. */ +function categoriesOf(m: MarketPlugin): Category[] { + const known = CATEGORIES.filter((c) => m.categories?.includes(c)); + return known.length > 0 ? known : ["other"]; +} + +/** Filters the marketplace by category; lists the categories that have plugins. */ +function CategoryFilter({ + market, + value, + onChange, +}: { + market: readonly MarketPlugin[]; + value: Category | "all"; + onChange: (value: Category | "all") => void; +}) { + const counts = new Map(); + for (const m of market) for (const c of categoriesOf(m)) counts.set(c, (counts.get(c) ?? 0) + 1); + const options = [ + { value: "all" as const, label: tx("All Categories"), detail: String(market.length) }, + ...CATEGORIES.filter((c) => counts.has(c)).map((c) => ({ + value: c, + label: categoryLabel(c), + detail: String(counts.get(c)), + })), + ]; + return