diff --git a/Cargo.lock b/Cargo.lock
index 7768dac710feff..e3bdc89f5f011e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6709,11 +6709,14 @@ version = "0.1.0"
dependencies = [
"anyhow",
"editor",
+ "file_finder",
+ "file_icons",
"fuzzy",
"gpui",
"language",
"picker",
"project",
+ "settings",
"ui",
"util",
"workspace",
diff --git a/assets/icons/file_icons/diff.svg b/assets/icons/file_icons/diff.svg
new file mode 100644
index 00000000000000..07c46f1799604f
--- /dev/null
+++ b/assets/icons/file_icons/diff.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json
index 5e927369d38d0b..89da63dddacd8d 100644
--- a/assets/icons/file_icons/file_types.json
+++ b/assets/icons/file_icons/file_types.json
@@ -34,6 +34,7 @@
"dat": "storage",
"db": "storage",
"dbf": "storage",
+ "diff": "diff",
"dll": "storage",
"doc": "document",
"docx": "document",
@@ -112,6 +113,7 @@
"mkv": "video",
"ml": "ocaml",
"mli": "ocaml",
+ "mod": "go",
"mov": "video",
"mp3": "audio",
"mp4": "video",
@@ -185,6 +187,7 @@
"wmv": "video",
"woff": "font",
"woff2": "font",
+ "work": "go",
"wv": "audio",
"xls": "document",
"xlsx": "document",
@@ -239,6 +242,9 @@
"default": {
"icon": "icons/file_icons/file.svg"
},
+ "diff": {
+ "icon": "icons/file_icons/diff.svg"
+ },
"docker": {
"icon": "icons/file_icons/docker.svg"
},
diff --git a/crates/extension/src/extension_host_proxy.rs b/crates/extension/src/extension_host_proxy.rs
index 8909a6082dee9c..3fa35597a83113 100644
--- a/crates/extension/src/extension_host_proxy.rs
+++ b/crates/extension/src/extension_host_proxy.rs
@@ -159,6 +159,7 @@ pub trait ExtensionLanguageProxy: Send + Sync + 'static {
language: LanguageName,
grammar: Option>,
matcher: LanguageMatcher,
+ hidden: bool,
load: Arc Result + Send + Sync + 'static>,
);
@@ -175,13 +176,14 @@ impl ExtensionLanguageProxy for ExtensionHostProxy {
language: LanguageName,
grammar: Option>,
matcher: LanguageMatcher,
+ hidden: bool,
load: Arc Result + Send + Sync + 'static>,
) {
let Some(proxy) = self.language_proxy.read().clone() else {
return;
};
- proxy.register_language(language, grammar, matcher, load)
+ proxy.register_language(language, grammar, matcher, hidden, load)
}
fn remove_languages(
diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs
index aab5c258f50fda..7ceb1fa7147cee 100644
--- a/crates/extension_host/src/extension_host.rs
+++ b/crates/extension_host/src/extension_host.rs
@@ -162,6 +162,7 @@ pub struct ExtensionIndexLanguageEntry {
pub extension: Arc,
pub path: PathBuf,
pub matcher: LanguageMatcher,
+ pub hidden: bool,
pub grammar: Option>,
}
@@ -1097,6 +1098,7 @@ impl ExtensionStore {
language_name.clone(),
language.grammar.clone(),
language.matcher.clone(),
+ language.hidden,
Arc::new(move || {
let config = std::fs::read_to_string(language_path.join("config.toml"))?;
let config: LanguageConfig = ::toml::from_str(&config)?;
@@ -1324,6 +1326,7 @@ impl ExtensionStore {
extension: extension_id.clone(),
path: relative_path,
matcher: config.matcher,
+ hidden: config.hidden,
grammar: config.grammar,
},
);
diff --git a/crates/extension_host/src/extension_store_test.rs b/crates/extension_host/src/extension_store_test.rs
index 1359b5b202843c..8b5a2a782149ab 100644
--- a/crates/extension_host/src/extension_store_test.rs
+++ b/crates/extension_host/src/extension_store_test.rs
@@ -203,6 +203,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
extension: "zed-ruby".into(),
path: "languages/erb".into(),
grammar: Some("embedded_template".into()),
+ hidden: false,
matcher: LanguageMatcher {
path_suffixes: vec!["erb".into()],
first_line_pattern: None,
@@ -215,6 +216,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
extension: "zed-ruby".into(),
path: "languages/ruby".into(),
grammar: Some("ruby".into()),
+ hidden: false,
matcher: LanguageMatcher {
path_suffixes: vec!["rb".into()],
first_line_pattern: None,
diff --git a/crates/extension_host/src/headless_host.rs b/crates/extension_host/src/headless_host.rs
index 19a574b9d4aa5c..687f05db478b04 100644
--- a/crates/extension_host/src/headless_host.rs
+++ b/crates/extension_host/src/headless_host.rs
@@ -156,6 +156,7 @@ impl HeadlessExtensionStore {
config.name.clone(),
None,
config.matcher.clone(),
+ config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs
index 6a758211f8b73d..62e0818b7434f6 100644
--- a/crates/file_finder/src/file_finder.rs
+++ b/crates/file_finder/src/file_finder.rs
@@ -1,7 +1,7 @@
#[cfg(test)]
mod file_finder_tests;
-mod file_finder_settings;
+pub mod file_finder_settings;
mod new_path_prompt;
mod open_path_prompt;
diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs
index 2725122990d9fe..e9590448f8c406 100644
--- a/crates/language/src/language.rs
+++ b/crates/language/src/language.rs
@@ -129,6 +129,10 @@ pub static PLAIN_TEXT: LazyLock> = LazyLock::new(|| {
LanguageConfig {
name: "Plain Text".into(),
soft_wrap: Some(SoftWrap::EditorWidth),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["txt".to_owned()],
+ first_line_pattern: None,
+ },
..Default::default()
},
None,
@@ -1418,6 +1422,10 @@ impl Language {
pub fn prettier_parser_name(&self) -> Option<&str> {
self.config.prettier_parser_name.as_deref()
}
+
+ pub fn config(&self) -> &LanguageConfig {
+ &self.config
+ }
}
impl LanguageScope {
diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs
index d8c2b0d5107816..e5f78153513891 100644
--- a/crates/language/src/language_registry.rs
+++ b/crates/language/src/language_registry.rs
@@ -130,6 +130,7 @@ pub struct AvailableLanguage {
name: LanguageName,
grammar: Option>,
matcher: LanguageMatcher,
+ hidden: bool,
load: Arc Result + 'static + Send + Sync>,
loaded: bool,
}
@@ -142,6 +143,9 @@ impl AvailableLanguage {
pub fn matcher(&self) -> &LanguageMatcher {
&self.matcher
}
+ pub fn hidden(&self) -> bool {
+ self.hidden
+ }
}
enum AvailableGrammar {
@@ -288,6 +292,7 @@ impl LanguageRegistry {
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
+ config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
@@ -436,6 +441,7 @@ impl LanguageRegistry {
name: LanguageName,
grammar_name: Option>,
matcher: LanguageMatcher,
+ hidden: bool,
load: Arc Result + 'static + Send + Sync>,
) {
let state = &mut *self.state.write();
@@ -455,6 +461,7 @@ impl LanguageRegistry {
grammar: grammar_name,
matcher,
load,
+ hidden,
loaded: false,
});
state.version += 1;
@@ -522,6 +529,7 @@ impl LanguageRegistry {
name: language.name(),
grammar: language.config.grammar.clone(),
matcher: language.config.matcher.clone(),
+ hidden: language.config.hidden,
load: Arc::new(|| Err(anyhow!("already loaded"))),
loaded: true,
});
@@ -590,15 +598,12 @@ impl LanguageRegistry {
async move { rx.await? }
}
- pub fn available_language_for_name(
- self: &Arc,
- name: &LanguageName,
- ) -> Option {
+ pub fn available_language_for_name(self: &Arc, name: &str) -> Option {
let state = self.state.read();
state
.available_languages
.iter()
- .find(|l| &l.name == name)
+ .find(|l| l.name.0.as_ref() == name)
.cloned()
}
diff --git a/crates/language_extension/src/language_extension.rs b/crates/language_extension/src/language_extension.rs
index d8ffc71d7c4ab0..59951c87e48293 100644
--- a/crates/language_extension/src/language_extension.rs
+++ b/crates/language_extension/src/language_extension.rs
@@ -34,10 +34,11 @@ impl ExtensionLanguageProxy for LanguageServerRegistryProxy {
language: LanguageName,
grammar: Option>,
matcher: LanguageMatcher,
+ hidden: bool,
load: Arc Result + Send + Sync + 'static>,
) {
self.language_registry
- .register_language(language, grammar, matcher, load);
+ .register_language(language, grammar, matcher, hidden, load);
}
fn remove_languages(
diff --git a/crates/language_selector/Cargo.toml b/crates/language_selector/Cargo.toml
index b864ffc31f3f4c..276e9b0d42bb7c 100644
--- a/crates/language_selector/Cargo.toml
+++ b/crates/language_selector/Cargo.toml
@@ -15,11 +15,14 @@ doctest = false
[dependencies]
anyhow.workspace = true
editor.workspace = true
+file_finder.workspace = true
+file_icons.workspace = true
fuzzy.workspace = true
gpui.workspace = true
language.workspace = true
picker.workspace = true
project.workspace = true
+settings.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs
index 489f6fd141b927..60da837baab770 100644
--- a/crates/language_selector/src/language_selector.rs
+++ b/crates/language_selector/src/language_selector.rs
@@ -3,15 +3,18 @@ mod active_buffer_language;
pub use active_buffer_language::ActiveBufferLanguage;
use anyhow::anyhow;
use editor::Editor;
+use file_finder::file_finder_settings::FileFinderSettings;
+use file_icons::FileIcons;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
actions, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
};
-use language::{Buffer, LanguageRegistry};
+use language::{Buffer, LanguageMatcher, LanguageName, LanguageRegistry};
use picker::{Picker, PickerDelegate};
use project::Project;
-use std::sync::Arc;
+use settings::Settings;
+use std::{ops::Not as _, path::Path, sync::Arc};
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::{ModalView, Workspace};
@@ -102,7 +105,13 @@ impl LanguageSelectorDelegate {
.language_names()
.into_iter()
.enumerate()
- .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
+ .filter_map(|(candidate_id, name)| {
+ language_registry
+ .available_language_for_name(&name)?
+ .hidden()
+ .not()
+ .then(|| StringMatchCandidate::new(candidate_id, name))
+ })
.collect::>();
Self {
@@ -115,13 +124,64 @@ impl LanguageSelectorDelegate {
selected_index: 0,
}
}
+
+ fn language_data_for_match(
+ &self,
+ mat: &StringMatch,
+ cx: &AppContext,
+ ) -> (String, Option) {
+ let mut label = mat.string.clone();
+ let buffer_language = self.buffer.read(cx).language();
+ let need_icon = FileFinderSettings::get_global(cx).file_icons;
+ if let Some(buffer_language) = buffer_language {
+ let buffer_language_name = buffer_language.name();
+ if buffer_language_name.0.as_ref() == mat.string.as_str() {
+ label.push_str(" (current)");
+ let icon = need_icon
+ .then(|| self.language_icon(&buffer_language.config().matcher, cx))
+ .flatten();
+ return (label, icon);
+ }
+ }
+
+ if need_icon {
+ let language_name = LanguageName::new(mat.string.as_str());
+ match self
+ .language_registry
+ .available_language_for_name(&language_name.0)
+ {
+ Some(available_language) => {
+ let icon = self.language_icon(available_language.matcher(), cx);
+ (label, icon)
+ }
+ None => (label, None),
+ }
+ } else {
+ (label, None)
+ }
+ }
+
+ fn language_icon(&self, matcher: &LanguageMatcher, cx: &AppContext) -> Option {
+ matcher
+ .path_suffixes
+ .iter()
+ .find_map(|extension| {
+ if extension.contains('.') {
+ None
+ } else {
+ FileIcons::get_icon(Path::new(&format!("file.{extension}")), cx)
+ }
+ })
+ .map(Icon::from_path)
+ .map(|icon| icon.color(Color::Muted))
+ }
}
impl PickerDelegate for LanguageSelectorDelegate {
type ListItem = ListItem;
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc {
- "Select a language...".into()
+ "Select a languageā¦".into()
}
fn match_count(&self) -> usize {
@@ -215,17 +275,13 @@ impl PickerDelegate for LanguageSelectorDelegate {
cx: &mut ViewContext>,
) -> Option {
let mat = &self.matches[ix];
- let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
- let mut label = mat.string.clone();
- if buffer_language_name.map(|n| n.0).as_deref() == Some(mat.string.as_str()) {
- label.push_str(" (current)");
- }
-
+ let (label, language_icon) = self.language_data_for_match(mat, cx);
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.selected(selected)
+ .start_slot::(language_icon)
.child(HighlightedLabel::new(label, mat.positions.clone())),
)
}
diff --git a/crates/languages/src/jsdoc/config.toml b/crates/languages/src/jsdoc/config.toml
index 444e657a3863f4..0aa0d361bdc45b 100644
--- a/crates/languages/src/jsdoc/config.toml
+++ b/crates/languages/src/jsdoc/config.toml
@@ -5,3 +5,4 @@ brackets = [
{ start = "{", end = "}", close = true, newline = false },
{ start = "[", end = "]", close = true, newline = false },
]
+hidden = true
diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs
index 776d47a5f775b2..5ba6f5c03439c2 100644
--- a/crates/languages/src/lib.rs
+++ b/crates/languages/src/lib.rs
@@ -62,6 +62,7 @@ pub fn init(languages: Arc, node_runtime: NodeRuntime, cx: &mu
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
+ config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
@@ -83,6 +84,7 @@ pub fn init(languages: Arc, node_runtime: NodeRuntime, cx: &mu
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
+ config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
@@ -104,6 +106,7 @@ pub fn init(languages: Arc, node_runtime: NodeRuntime, cx: &mu
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
+ config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
@@ -125,6 +128,7 @@ pub fn init(languages: Arc, node_runtime: NodeRuntime, cx: &mu
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
+ config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
diff --git a/crates/languages/src/regex/config.toml b/crates/languages/src/regex/config.toml
index d0938024d6e3a7..85f2e370d673f7 100644
--- a/crates/languages/src/regex/config.toml
+++ b/crates/languages/src/regex/config.toml
@@ -6,3 +6,4 @@ brackets = [
{ start = "{", end = "}", close = true, newline = false },
{ start = "[", end = "]", close = true, newline = false },
]
+hidden = true