From e60123bbdc58c37ad5ec60429fc730d5f9b8fd90 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 14 Feb 2025 19:35:13 -0500 Subject: [PATCH] Allow icon themes to provide their own file associations (#24926) This PR adds the ability for icon themes to provide their own file associations. The old `file_types.json` that was previously used to make these associations has been removed in favor of storing them on the default theme. Icon themes have two new fields on them: - `file_stems`: A mapping of file stems to icon keys. - `file_suffixes`: A mapping of file suffixes to icon keys. These mappings produce icon keys which can then be used in `file_icons` to associate them to a particular icon: ```json { "file_stems": { "Makefile": "make" }, "file_suffixes": { "idr": "idris" }, "file_icons": { "idris": { "path": "./icons/idris.svg" }, "make": { "path": "./icons/make.svg" } } } ``` When loading an icon theme, the `file_stems` and `file_icons` fields will be merged with the ones from the base icon theme, with the values from the icon theme being loaded overriding ones in the base theme. Release Notes: - Added the ability for icon themes to provide their own file associations. --- CONTRIBUTING.md | 4 - Cargo.lock | 4 - assets/icons/file_icons/file_types.json | 260 ---------------------- crates/file_icons/Cargo.toml | 3 - crates/file_icons/src/file_icons.rs | 43 +--- crates/outline_panel/src/outline_panel.rs | 17 +- crates/project_panel/src/project_panel.rs | 11 +- crates/tasks_ui/src/tasks_ui.rs | 1 - crates/theme/src/icon_theme.rs | 239 +++++++++++++++++++- crates/theme/src/icon_theme_schema.rs | 4 + crates/theme/src/registry.rs | 23 +- crates/vim/src/test/vim_test_context.rs | 3 +- crates/zed/Cargo.toml | 1 - crates/zed/src/main.rs | 37 +-- crates/zed/src/zed.rs | 4 +- typos.toml | 2 +- 16 files changed, 286 insertions(+), 370 deletions(-) delete mode 100644 assets/icons/file_icons/file_types.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 843d6f33a1..91b1b75f82 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,10 +43,6 @@ Zed's default icon theme consists of icons that are hand-designed to fit togethe We do not accept PRs for file icons that are just an off-the-shelf SVG taken from somewhere else. -### File icon associations - -We will happily accept PRs that add new file icon associations to [`file_types.json`](assets/icons/file_icons/file_types.json) to allow them to be targeted by [icon themes](https://zed.dev/docs/extensions/icon-themes). - ### Adding new icons to the Zed icon theme If you would like to add a new icon to the Zed icon theme, [open a Discussion](https://github.com/zed-industries/zed/discussions/new?category=ux-and-design) and we can work with you on getting an icon designed and added to Zed. diff --git a/Cargo.lock b/Cargo.lock index 384ed58bf0..1d5b20bcf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4704,11 +4704,8 @@ dependencies = [ name = "file_icons" version = "0.1.0" dependencies = [ - "collections", "gpui", "serde", - "serde_derive", - "serde_json", "settings", "theme", "util", @@ -16680,7 +16677,6 @@ dependencies = [ "feature_flags", "feedback", "file_finder", - "file_icons", "fs", "futures 0.3.31", "git", diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json deleted file mode 100644 index dac7adbb5d..0000000000 --- a/assets/icons/file_icons/file_types.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "stems": { - "Dockerfile": "docker", - "Podfile": "ruby", - "Procfile": "heroku" - }, - "suffixes": { - "COMMIT_EDITMSG": "vcs", - "EDIT_DESCRIPTION": "vcs", - "Emakefile": "erlang", - "MERGE_MSG": "vcs", - "NOTES_EDITMSG": "vcs", - "R": "r", - "TAG_EDITMSG": "vcs", - "aac": "audio", - "accdb": "storage", - "app.src": "erlang", - "astro": "astro", - "avi": "video", - "avif": "image", - "bak": "backup", - "bash": "terminal", - "bash_aliases": "terminal", - "bash_logout": "terminal", - "bash_profile": "terminal", - "bashrc": "terminal", - "bicep": "bicep", - "bmp": "image", - "c": "c", - "c++": "cpp", - "cc": "cpp", - "cjs": "javascript", - "cjsx": "react", - "coffee": "coffeescript", - "conf": "settings", - "cpp": "cpp", - "cr": "crystal", - "cs": "csharp", - "csproj": "csproj", - "css": "css", - "csv": "storage", - "cts": "typescript", - "ctsx": "react", - "cue": "cue", - "cxx": "cpp", - "dart": "dart", - "dat": "storage", - "db": "storage", - "dbf": "storage", - "diff": "diff", - "dll": "storage", - "doc": "document", - "docx": "document", - "ecr": "crystal", - "eex": "elixir", - "elm": "elm", - "erl": "erlang", - "escript": "erlang", - "eslint.config.cjs": "eslint", - "eslint.config.cts": "eslint", - "eslint.config.js": "eslint", - "eslint.config.mjs": "eslint", - "eslint.config.mts": "eslint", - "eslint.config.ts": "eslint", - "eslintrc": "eslint", - "eslintrc.js": "eslint", - "eslintrc.json": "eslint", - "ex": "elixir", - "exs": "elixir", - "fish": "terminal", - "flac": "audio", - "fmp": "storage", - "fp7": "storage", - "frm": "storage", - "fs": "fsharp", - "fsproj": "fsproj", - "gdb": "storage", - "gif": "image", - "gitattributes": "vcs", - "gitignore": "vcs", - "gitkeep": "vcs", - "gitlab-ci.yml": "gitlab", - "gitmodules": "vcs", - "gleam": "gleam", - "go": "go", - "gql": "graphql", - "graphql": "graphql", - "graphqls": "graphql", - "h": "c", - "handlebars": "code", - "hbs": "template", - "hcl": "hcl", - "heex": "elixir", - "heic": "image", - "heif": "image", - "hh": "cpp", - "hpp": "cpp", - "hrl": "erlang", - "hs": "haskell", - "htm": "html", - "html": "html", - "hxx": "cpp", - "ib": "storage", - "ico": "image", - "ini": "settings", - "inl": "cpp", - "j2k": "image", - "java": "java", - "jfif": "image", - "jl": "julia", - "jp2": "image", - "jpeg": "image", - "jpg": "image", - "js": "javascript", - "json": "json", - "jsonc": "storage", - "jsx": "react", - "jxl": "image", - "kt": "kotlin", - "ldf": "storage", - "lock": "lock", - "lockb": "bun", - "log": "log", - "lua": "lua", - "luau": "luau", - "m4a": "audio", - "m4v": "video", - "markdown": "markdown", - "md": "markdown", - "mdb": "storage", - "mdf": "storage", - "mdx": "document", - "metadata": "code", - "metal": "metal", - "mjs": "javascript", - "mjsx": "react", - "mka": "audio", - "mkv": "video", - "ml": "ocaml", - "mli": "ocaml", - "mod": "go", - "mov": "video", - "mp3": "audio", - "mp4": "video", - "mts": "typescript", - "mtsx": "react", - "myd": "storage", - "myi": "storage", - "nim": "nim", - "nix": "nix", - "nu": "terminal", - "odp": "document", - "ods": "document", - "odt": "document", - "ogg": "audio", - "opus": "audio", - "otf": "font", - "pcss": "css", - "pdb": "storage", - "pdf": "document", - "php": "php", - "plist": "template", - "png": "image", - "postcss": "css", - "ppt": "document", - "pptx": "document", - "prettier.config.cjs": "prettier", - "prettier.config.js": "prettier", - "prettier.config.mjs": "prettier", - "prettierignore": "prettier", - "prettierrc": "prettier", - "prettierrc.cjs": "prettier", - "prettierrc.js": "prettier", - "prettierrc.json": "prettier", - "prettierrc.json5": "prettier", - "prettierrc.mjs": "prettier", - "prettierrc.toml": "prettier", - "prettierrc.yaml": "prettier", - "prettierrc.yml": "prettier", - "prisma": "prisma", - "profile": "terminal", - "ps1": "terminal", - "psd": "image", - "py": "python", - "qoi": "image", - "r": "r", - "rb": "ruby", - "rebar.config": "erlang", - "rkt": "code", - "roc": "roc", - "rs": "rust", - "rtf": "document", - "sass": "sass", - "sav": "storage", - "sc": "scala", - "scala": "scala", - "scm": "code", - "scss": "sass", - "sdf": "storage", - "sh": "terminal", - "sln": "vs_sln", - "sol": "solidity", - "sql": "storage", - "sqlite": "storage", - "stylelint.config.cjs": "stylelint", - "stylelint.config.js": "stylelint", - "stylelint.config.mjs": "stylelint", - "stylelintignore": "stylelint", - "stylelintrc": "stylelint", - "stylelintrc.cjs": "stylelint", - "stylelintrc.js": "stylelint", - "stylelintrc.json": "stylelint", - "stylelintrc.mjs": "stylelint", - "stylelintrc.yaml": "stylelint", - "stylelintrc.yml": "stylelint", - "suo": "vs_suo", - "svelte": "svelte", - "svg": "image", - "swift": "swift", - "tcl": "tcl", - "tf": "terraform", - "tfvars": "terraform", - "tiff": "image", - "toml": "toml", - "ts": "typescript", - "tsv": "storage", - "tsx": "react", - "ttf": "font", - "txt": "document", - "v": "v", - "vbproj": "vbproj", - "vsh": "v", - "vue": "vue", - "vv": "v", - "wav": "audio", - "webm": "video", - "webp": "image", - "wma": "audio", - "wmv": "video", - "woff": "font", - "woff2": "font", - "work": "go", - "wv": "audio", - "xls": "document", - "xlsx": "document", - "xml": "template", - "xrl": "erlang", - "yaml": "settings", - "yml": "settings", - "yrl": "erlang", - "zig": "zig", - "zlogin": "terminal", - "zsh": "terminal", - "zsh_aliases": "terminal", - "zsh_histfile": "terminal", - "zsh_profile": "terminal", - "zshenv": "terminal", - "zshrc": "terminal" - } -} diff --git a/crates/file_icons/Cargo.toml b/crates/file_icons/Cargo.toml index 32b73ce828..5f2740cb3f 100644 --- a/crates/file_icons/Cargo.toml +++ b/crates/file_icons/Cargo.toml @@ -13,11 +13,8 @@ path = "src/file_icons.rs" doctest = false [dependencies] -collections.workspace = true gpui.workspace = true serde.workspace = true -serde_derive.workspace = true -serde_json.workspace = true settings.workspace = true theme.workspace = true util.workspace = true diff --git a/crates/file_icons/src/file_icons.rs b/crates/file_icons/src/file_icons.rs index 37b0a6b225..2f159771b1 100644 --- a/crates/file_icons/src/file_icons.rs +++ b/crates/file_icons/src/file_icons.rs @@ -1,52 +1,33 @@ use std::sync::Arc; use std::{path::Path, str}; -use collections::HashMap; - -use gpui::{App, AssetSource, Global, SharedString}; -use serde_derive::Deserialize; +use gpui::{App, SharedString}; use settings::Settings; use theme::{IconTheme, ThemeRegistry, ThemeSettings}; use util::paths::PathExt; -#[derive(Deserialize, Debug)] +#[derive(Debug)] pub struct FileIcons { - stems: HashMap, - suffixes: HashMap, -} - -impl Global for FileIcons {} - -pub const FILE_TYPES_ASSET: &str = "icons/file_icons/file_types.json"; - -pub fn init(assets: impl AssetSource, cx: &mut App) { - cx.set_global(FileIcons::new(assets)) + icon_theme: Arc, } impl FileIcons { - pub fn get(cx: &App) -> &Self { - cx.global::() - } + pub fn get(cx: &App) -> Self { + let theme_settings = ThemeSettings::get_global(cx); - pub fn new(assets: impl AssetSource) -> Self { - assets - .load(FILE_TYPES_ASSET) - .ok() - .flatten() - .and_then(|file| serde_json::from_str::(str::from_utf8(&file).unwrap()).ok()) - .unwrap_or_else(|| FileIcons { - stems: HashMap::default(), - suffixes: HashMap::default(), - }) + Self { + icon_theme: theme_settings.active_icon_theme.clone(), + } } pub fn get_icon(path: &Path, cx: &App) -> Option { - let this = cx.try_global::()?; + let this = Self::get(cx); let get_icon_from_suffix = |suffix: &str| -> Option { - this.stems + this.icon_theme + .file_stems .get(suffix) - .or_else(|| this.suffixes.get(suffix)) + .or_else(|| this.icon_theme.file_suffixes.get(suffix)) .and_then(|typ| this.get_icon_for_type(typ, cx)) }; // TODO: Associate a type with the languages and have the file's language diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index bdad4b7d72..14d0b77d91 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -28,12 +28,12 @@ use file_icons::FileIcons; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{ actions, anchored, deferred, div, point, px, size, uniform_list, Action, AnyElement, App, - AppContext as _, AssetSource, AsyncWindowContext, Bounds, ClipboardItem, Context, DismissEvent, - Div, ElementId, Entity, EventEmitter, FocusHandle, Focusable, HighlightStyle, - InteractiveElement, IntoElement, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior, - MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render, ScrollStrategy, - SharedString, Stateful, StatefulInteractiveElement as _, Styled, Subscription, Task, - UniformListScrollHandle, WeakEntity, Window, + AppContext as _, AsyncWindowContext, Bounds, ClipboardItem, Context, DismissEvent, Div, + ElementId, Entity, EventEmitter, FocusHandle, Focusable, HighlightStyle, InteractiveElement, + IntoElement, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, + MouseDownEvent, ParentElement, Pixels, Point, Render, ScrollStrategy, SharedString, Stateful, + StatefulInteractiveElement as _, Styled, Subscription, Task, UniformListScrollHandle, + WeakEntity, Window, }; use itertools::Itertools; use language::{BufferId, BufferSnapshot, OffsetRangeExt, OutlineItem}; @@ -664,9 +664,8 @@ pub fn init_settings(cx: &mut App) { OutlinePanelSettings::register(cx); } -pub fn init(assets: impl AssetSource, cx: &mut App) { +pub fn init(cx: &mut App) { init_settings(cx); - file_icons::init(assets, cx); cx.observe_new(|workspace: &mut Workspace, _, _| { workspace.register_action(|workspace, _: &ToggleFocus, window, cx| { @@ -6641,7 +6640,7 @@ outline: struct OutlineEntryExcerpt workspace::init_settings(cx); Project::init_settings(cx); project_search::init(cx); - super::init((), cx); + super::init(cx); }); } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 538c7d1c42..758ae4f7c8 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -18,8 +18,8 @@ use file_icons::FileIcons; use git::status::GitSummary; use gpui::{ actions, anchored, deferred, div, impl_actions, point, px, size, uniform_list, Action, - AnyElement, App, AssetSource, AsyncWindowContext, Bounds, ClipboardItem, Context, DismissEvent, - Div, DragMoveEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, Hsla, + AnyElement, App, AsyncWindowContext, Bounds, ClipboardItem, Context, DismissEvent, Div, + DragMoveEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, Hsla, InteractiveElement, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, ScrollStrategy, Stateful, Styled, Subscription, Task, UniformListScrollHandle, WeakEntity, Window, @@ -225,9 +225,8 @@ pub fn init_settings(cx: &mut App) { ProjectPanelSettings::register(cx); } -pub fn init(assets: impl AssetSource, cx: &mut App) { +pub fn init(cx: &mut App) { init_settings(cx); - file_icons::init(assets, cx); cx.observe_new(|workspace: &mut Workspace, _, _| { workspace.register_action(|workspace, _: &ToggleFocus, window, cx| { @@ -9484,7 +9483,7 @@ mod tests { theme::init(theme::LoadThemes::JustBase, cx); language::init(cx); editor::init_settings(cx); - crate::init((), cx); + crate::init(cx); workspace::init_settings(cx); client::init_settings(cx); Project::init_settings(cx); @@ -9507,7 +9506,7 @@ mod tests { init_settings(cx); language::init(cx); editor::init(cx); - crate::init((), cx); + crate::init(cx); workspace::init(app_state.clone(), cx); Project::init_settings(cx); diff --git a/crates/tasks_ui/src/tasks_ui.rs b/crates/tasks_ui/src/tasks_ui.rs index 36cc140993..5a536c0927 100644 --- a/crates/tasks_ui/src/tasks_ui.rs +++ b/crates/tasks_ui/src/tasks_ui.rs @@ -455,7 +455,6 @@ mod tests { pub(crate) fn init_test(cx: &mut TestAppContext) -> Arc { cx.update(|cx| { let state = AppState::test(cx); - file_icons::init((), cx); language::init(cx); crate::init(cx); editor::init(cx); diff --git a/crates/theme/src/icon_theme.rs b/crates/theme/src/icon_theme.rs index ce69b4adf5..cce99208be 100644 --- a/crates/theme/src/icon_theme.rs +++ b/crates/theme/src/icon_theme.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, LazyLock}; + use collections::HashMap; use gpui::SharedString; @@ -28,7 +30,11 @@ pub struct IconTheme { pub directory_icons: DirectoryIcons, /// The icons used for chevrons. pub chevron_icons: ChevronIcons, - /// The mapping of file types to icon definitions. + /// The mapping of file stems to their associated icon keys. + pub file_stems: HashMap, + /// The mapping of file suffixes to their associated icon keys. + pub file_suffixes: HashMap, + /// The mapping of icon keys to icon definitions. pub file_icons: HashMap, } @@ -57,6 +63,209 @@ pub struct IconDefinition { pub path: SharedString, } +const FILE_STEMS_BY_ICON_KEY: &[(&str, &[&str])] = &[ + ("docker", &["Dockerfile"]), + ("ruby", &["Podfile"]), + ("heroku", &["Procfile"]), +]; + +const FILE_SUFFIXES_BY_ICON_KEY: &[(&str, &[&str])] = &[ + ("astro", &["astro"]), + ( + "audio", + &[ + "aac", "flac", "m4a", "mka", "mp3", "ogg", "opus", "wav", "wma", "wv", + ], + ), + ("backup", &["bak"]), + ("bicep", &["bicep"]), + ("bun", &["lockb"]), + ("c", &["c", "h"]), + ("code", &["handlebars", "metadata", "rkt", "scm"]), + ("coffeescript", &["coffee"]), + ( + "cpp", + &["c++", "cc", "cpp", "cxx", "hh", "hpp", "hxx", "inl"], + ), + ("crystal", &["cr", "ecr"]), + ("csharp", &["cs"]), + ("csproj", &["csproj"]), + ("css", &["css", "pcss", "postcss"]), + ("cue", &["cue"]), + ("dart", &["dart"]), + ("diff", &["diff"]), + ( + "document", + &[ + "doc", "docx", "mdx", "odp", "ods", "odt", "pdf", "ppt", "pptx", "rtf", "txt", "xls", + "xlsx", + ], + ), + ("elixir", &["eex", "ex", "exs", "heex"]), + ("elm", &["elm"]), + ( + "erlang", + &[ + "Emakefile", + "app.src", + "erl", + "escript", + "hrl", + "rebar.config", + "xrl", + "yrl", + ], + ), + ( + "eslint", + &[ + "eslint.config.cjs", + "eslint.config.cts", + "eslint.config.js", + "eslint.config.mjs", + "eslint.config.mts", + "eslint.config.ts", + "eslintrc", + "eslintrc.js", + "eslintrc.json", + ], + ), + ("font", &["otf", "ttf", "woff", "woff2"]), + ("fsharp", &["fs"]), + ("fsproj", &["fsproj"]), + ("gitlab", &["gitlab-ci.yml"]), + ("gleam", &["gleam"]), + ("go", &["go", "mod", "work"]), + ("graphql", &["gql", "graphql", "graphqls"]), + ("haskell", &["hs"]), + ("hcl", &["hcl"]), + ("html", &["htm", "html"]), + ( + "image", + &[ + "avif", "bmp", "gif", "heic", "heif", "ico", "j2k", "jfif", "jp2", "jpeg", "jpg", + "jxl", "png", "psd", "qoi", "svg", "tiff", "webp", + ], + ), + ("java", &["java"]), + ("javascript", &["cjs", "js", "mjs"]), + ("json", &["json"]), + ("julia", &["jl"]), + ("kotlin", &["kt"]), + ("lock", &["lock"]), + ("log", &["log"]), + ("lua", &["lua"]), + ("luau", &["luau"]), + ("markdown", &["markdown", "md"]), + ("metal", &["metal"]), + ("nim", &["nim"]), + ("nix", &["nix"]), + ("ocaml", &["ml", "mli"]), + ("php", &["php"]), + ( + "prettier", + &[ + "prettier.config.cjs", + "prettier.config.js", + "prettier.config.mjs", + "prettierignore", + "prettierrc", + "prettierrc.cjs", + "prettierrc.js", + "prettierrc.json", + "prettierrc.json5", + "prettierrc.mjs", + "prettierrc.toml", + "prettierrc.yaml", + "prettierrc.yml", + ], + ), + ("prisma", &["prisma"]), + ("python", &["py"]), + ("r", &["r", "R"]), + ("react", &["cjsx", "ctsx", "jsx", "mjsx", "mtsx", "tsx"]), + ("roc", &["roc"]), + ("ruby", &["rb"]), + ("rust", &["rs"]), + ("sass", &["sass", "scss"]), + ("scala", &["scala", "sc"]), + ("settings", &["conf", "ini", "yaml", "yml"]), + ("solidity", &["sol"]), + ( + "storage", + &[ + "accdb", "csv", "dat", "db", "dbf", "dll", "fmp", "fp7", "frm", "gdb", "ib", "jsonc", + "ldf", "mdb", "mdf", "myd", "myi", "pdb", "sav", "sdf", "sql", "sqlite", "tsv", + ], + ), + ( + "stylelint", + &[ + "stylelint.config.cjs", + "stylelint.config.js", + "stylelint.config.mjs", + "stylelintignore", + "stylelintrc", + "stylelintrc.cjs", + "stylelintrc.js", + "stylelintrc.json", + "stylelintrc.mjs", + "stylelintrc.yaml", + "stylelintrc.yml", + ], + ), + ("svelte", &["svelte"]), + ("swift", &["swift"]), + ("tcl", &["tcl"]), + ("template", &["hbs", "plist", "xml"]), + ( + "terminal", + &[ + "bash", + "bash_aliases", + "bash_logout", + "bash_profile", + "bashrc", + "fish", + "nu", + "profile", + "ps1", + "sh", + "zlogin", + "zsh", + "zsh_aliases", + "zsh_histfile", + "zsh_profile", + "zshenv", + "zshrc", + ], + ), + ("terraform", &["tf", "tfvars"]), + ("toml", &["toml"]), + ("typescript", &["cts", "mts", "ts"]), + ("v", &["v", "vsh", "vv"]), + ( + "vcs", + &[ + "COMMIT_EDITMSG", + "EDIT_DESCRIPTION", + "MERGE_MSG", + "NOTES_EDITMSG", + "TAG_EDITMSG", + "gitattributes", + "gitignore", + "gitkeep", + "gitmodules", + ], + ), + ("vbproj", &["vbproj"]), + ("video", &["avi", "m4v", "mkv", "mov", "mp4", "webm", "wmv"]), + ("vs_sln", &["sln"]), + ("vs_suo", &["suo"]), + ("vue", &["vue"]), + ("zig", &["zig"]), +]; + /// A mapping of a file type identifier to its corresponding icon. const FILE_ICONS: &[(&str, &str)] = &[ ("astro", "icons/file_icons/astro.svg"), @@ -141,12 +350,25 @@ const FILE_ICONS: &[(&str, &str)] = &[ ("zig", "icons/file_icons/zig.svg"), ]; +/// Returns a mapping of file associations to icon keys. +fn icon_keys_by_association( + associations_by_icon_key: &[(&str, &[&str])], +) -> HashMap { + let mut icon_keys_by_association = HashMap::default(); + for (icon_key, associations) in associations_by_icon_key { + for association in *associations { + icon_keys_by_association.insert(association.to_string(), icon_key.to_string()); + } + } + + icon_keys_by_association +} + /// The name of the default icon theme. pub(crate) const DEFAULT_ICON_THEME_NAME: &str = "Zed (Default)"; -/// Returns the default icon theme. -pub fn default_icon_theme() -> IconTheme { - IconTheme { +static DEFAULT_ICON_THEME: LazyLock> = LazyLock::new(|| { + Arc::new(IconTheme { id: "zed".into(), name: DEFAULT_ICON_THEME_NAME.into(), appearance: Appearance::Dark, @@ -158,6 +380,8 @@ pub fn default_icon_theme() -> IconTheme { collapsed: Some("icons/file_icons/chevron_right.svg".into()), expanded: Some("icons/file_icons/chevron_down.svg".into()), }, + file_stems: icon_keys_by_association(FILE_STEMS_BY_ICON_KEY), + file_suffixes: icon_keys_by_association(FILE_SUFFIXES_BY_ICON_KEY), file_icons: HashMap::from_iter(FILE_ICONS.into_iter().map(|(ty, path)| { ( ty.to_string(), @@ -166,5 +390,10 @@ pub fn default_icon_theme() -> IconTheme { }, ) })), - } + }) +}); + +/// Returns the default icon theme. +pub fn default_icon_theme() -> Arc { + DEFAULT_ICON_THEME.clone() } diff --git a/crates/theme/src/icon_theme_schema.rs b/crates/theme/src/icon_theme_schema.rs index 6e2888630c..f73938bc5c 100644 --- a/crates/theme/src/icon_theme_schema.rs +++ b/crates/theme/src/icon_theme_schema.rs @@ -23,6 +23,10 @@ pub struct IconThemeContent { #[serde(default)] pub chevron_icons: ChevronIconsContent, #[serde(default)] + pub file_stems: HashMap, + #[serde(default)] + pub file_suffixes: HashMap, + #[serde(default)] pub file_icons: HashMap, } diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs index b5e47b23bf..f90b6df559 100644 --- a/crates/theme/src/registry.rs +++ b/crates/theme/src/registry.rs @@ -11,8 +11,8 @@ use parking_lot::RwLock; use util::ResultExt; use crate::{ - read_icon_theme, read_user_theme, refine_theme_family, Appearance, AppearanceContent, - ChevronIcons, DirectoryIcons, IconDefinition, IconTheme, Theme, ThemeFamily, + default_icon_theme, read_icon_theme, read_user_theme, refine_theme_family, Appearance, + AppearanceContent, ChevronIcons, DirectoryIcons, IconDefinition, IconTheme, Theme, ThemeFamily, ThemeFamilyContent, DEFAULT_ICON_THEME_NAME, }; @@ -80,10 +80,11 @@ impl ThemeRegistry { registry.insert_theme_families([crate::fallback_themes::zed_default_themes()]); let default_icon_theme = crate::default_icon_theme(); - registry.state.write().icon_themes.insert( - default_icon_theme.name.clone(), - Arc::new(default_icon_theme), - ); + registry + .state + .write() + .icon_themes + .insert(default_icon_theme.name.clone(), default_icon_theme); registry } @@ -263,8 +264,16 @@ impl ThemeRegistry { .into() }; + let default_icon_theme = default_icon_theme(); + let mut state = self.state.write(); for icon_theme in icon_theme_family.themes { + let mut file_stems = default_icon_theme.file_stems.clone(); + file_stems.extend(icon_theme.file_stems); + + let mut file_suffixes = default_icon_theme.file_suffixes.clone(); + file_suffixes.extend(icon_theme.file_suffixes); + let icon_theme = IconTheme { id: uuid::Uuid::new_v4().to_string(), name: icon_theme.name.into(), @@ -280,6 +289,8 @@ impl ThemeRegistry { collapsed: icon_theme.chevron_icons.collapsed.map(resolve_icon_path), expanded: icon_theme.chevron_icons.expanded.map(resolve_icon_path), }, + file_stems, + file_suffixes, file_icons: icon_theme .file_icons .into_iter() diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 7e24756c23..8a24b3e572 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -1,6 +1,5 @@ use std::ops::{Deref, DerefMut}; -use assets::Assets; use editor::test::editor_lsp_test_context::EditorLspTestContext; use gpui::{Context, Entity, SemanticVersion, UpdateGlobal}; use search::{project_search::ProjectSearchBar, BufferSearchBar}; @@ -21,7 +20,7 @@ impl VimTestContext { cx.set_global(settings); release_channel::init(SemanticVersion::default(), cx); command_palette::init(cx); - project_panel::init(Assets, cx); + project_panel::init(cx); git_ui::init(cx); crate::init(cx); search::init(cx); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index bb7c5d3c29..2d779c6fcd 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -51,7 +51,6 @@ extensions_ui.workspace = true feature_flags.workspace = true feedback.workspace = true file_finder.workspace = true -file_icons.workspace = true fs.workspace = true futures.workspace = true git.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e69226d97c..0ca2d93bc3 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -488,9 +488,9 @@ fn main() { tab_switcher::init(cx); outline::init(cx); project_symbols::init(cx); - project_panel::init(Assets, cx); + project_panel::init(cx); git_ui::git_panel::init(cx); - outline_panel::init(Assets, cx); + outline_panel::init(cx); component_preview::init(cx); tasks_ui::init(cx); snippets_ui::init(cx); @@ -555,7 +555,6 @@ fn main() { load_user_themes_in_background(fs.clone(), cx); watch_themes(fs.clone(), cx); watch_languages(fs.clone(), app_state.languages.clone(), cx); - watch_file_types(fs.clone(), cx); cx.set_menus(app_menus()); initialize_workspace(app_state.clone(), prompt_builder, cx); @@ -1158,35 +1157,3 @@ fn watch_languages(fs: Arc, languages: Arc, cx: &m #[cfg(not(debug_assertions))] fn watch_languages(_fs: Arc, _languages: Arc, _cx: &mut App) {} - -#[cfg(debug_assertions)] -fn watch_file_types(fs: Arc, cx: &mut App) { - use std::time::Duration; - - use file_icons::FileIcons; - use gpui::UpdateGlobal; - - let path = { - let p = Path::new("assets").join(file_icons::FILE_TYPES_ASSET); - let Ok(full_path) = p.canonicalize() else { - return; - }; - full_path - }; - - cx.spawn(|cx| async move { - let (mut events, _) = fs.watch(path.as_path(), Duration::from_millis(100)).await; - while (events.next().await).is_some() { - cx.update(|cx| { - FileIcons::update_global(cx, |file_types, _cx| { - *file_types = file_icons::FileIcons::new(Assets); - }); - }) - .ok(); - } - }) - .detach() -} - -#[cfg(not(debug_assertions))] -fn watch_file_types(_fs: Arc, _cx: &mut App) {} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 19a3347870..dc632d166d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -4198,8 +4198,8 @@ mod tests { editor::init(cx); collab_ui::init(&app_state, cx); git_ui::init(cx); - project_panel::init((), cx); - outline_panel::init((), cx); + project_panel::init(cx); + outline_panel::init(cx); terminal_view::init(cx); copilot::copilot_chat::init( app_state.fs.clone(), diff --git a/typos.toml b/typos.toml index 6e4cb4a7b8..8deed5882d 100644 --- a/typos.toml +++ b/typos.toml @@ -8,7 +8,7 @@ extend-exclude = [ ".mailmap", # File suffixes aren't typos. - "assets/icons/file_icons/file_types.json", + "crates/theme/src/icon_theme.rs", "crates/extensions_ui/src/extension_suggest.rs", # Some countries codes are flagged as typos.