Detailed changes
@@ -864,7 +864,7 @@ mod tests {
let language = Arc::new(
Language::new(
LanguageConfig {
- name: "Test".to_string(),
+ name: "Test".into(),
path_suffixes: vec![".test".to_string()],
..Default::default()
},
@@ -951,7 +951,7 @@ mod tests {
let language = Arc::new(
Language::new(
LanguageConfig {
- name: "Test".to_string(),
+ name: "Test".into(),
path_suffixes: vec![".test".to_string()],
..Default::default()
},
@@ -16,7 +16,7 @@ use futures::{
use gpui::{AppContext, Task};
use highlight_map::HighlightMap;
use lazy_static::lazy_static;
-use parking_lot::Mutex;
+use parking_lot::{Mutex, RwLock};
use serde::Deserialize;
use std::{
cell::RefCell,
@@ -45,7 +45,7 @@ thread_local! {
lazy_static! {
pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
LanguageConfig {
- name: "Plain Text".to_string(),
+ name: "Plain Text".into(),
path_suffixes: Default::default(),
brackets: Default::default(),
line_comment: None,
@@ -92,15 +92,27 @@ pub struct CodeLabel {
pub filter_range: Range<usize>,
}
-#[derive(Default, Deserialize)]
+#[derive(Deserialize)]
pub struct LanguageConfig {
- pub name: String,
+ pub name: Arc<str>,
pub path_suffixes: Vec<String>,
pub brackets: Vec<BracketPair>,
pub line_comment: Option<String>,
pub language_server: Option<LanguageServerConfig>,
}
+impl Default for LanguageConfig {
+ fn default() -> Self {
+ Self {
+ name: "".into(),
+ path_suffixes: Default::default(),
+ brackets: Default::default(),
+ line_comment: Default::default(),
+ language_server: Default::default(),
+ }
+ }
+}
+
#[derive(Default, Deserialize)]
pub struct LanguageServerConfig {
pub disk_based_diagnostic_sources: HashSet<String>,
@@ -151,7 +163,7 @@ pub enum LanguageServerBinaryStatus {
}
pub struct LanguageRegistry {
- languages: Vec<Arc<Language>>,
+ languages: RwLock<Vec<Arc<Language>>>,
language_server_download_dir: Option<Arc<Path>>,
lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc<Language>, LanguageServerBinaryStatus)>,
@@ -168,12 +180,12 @@ impl LanguageRegistry {
}
}
- pub fn add(&mut self, language: Arc<Language>) {
- self.languages.push(language.clone());
+ pub fn add(&self, language: Arc<Language>) {
+ self.languages.write().push(language.clone());
}
pub fn set_theme(&self, theme: &SyntaxTheme) {
- for language in &self.languages {
+ for language in self.languages.read().iter() {
language.set_theme(theme);
}
}
@@ -182,24 +194,30 @@ impl LanguageRegistry {
self.language_server_download_dir = Some(path.into());
}
- pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
+ pub fn get_language(&self, name: &str) -> Option<Arc<Language>> {
self.languages
+ .read()
.iter()
- .find(|language| language.name() == name)
+ .find(|language| language.name().as_ref() == name)
+ .cloned()
}
- pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
+ pub fn select_language(&self, path: impl AsRef<Path>) -> Option<Arc<Language>> {
let path = path.as_ref();
let filename = path.file_name().and_then(|name| name.to_str());
let extension = path.extension().and_then(|name| name.to_str());
let path_suffixes = [extension, filename];
- self.languages.iter().find(|language| {
- language
- .config
- .path_suffixes
- .iter()
- .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
- })
+ self.languages
+ .read()
+ .iter()
+ .find(|language| {
+ language
+ .config
+ .path_suffixes
+ .iter()
+ .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
+ })
+ .cloned()
}
pub fn start_language_server(
@@ -401,8 +419,8 @@ impl Language {
self
}
- pub fn name(&self) -> &str {
- self.config.name.as_str()
+ pub fn name(&self) -> Arc<str> {
+ self.config.name.clone()
}
pub fn line_comment_prefix(&self) -> Option<&str> {
@@ -24,10 +24,10 @@ fn init_logger() {
#[gpui::test]
fn test_select_language() {
- let mut registry = LanguageRegistry::new();
+ let registry = LanguageRegistry::new();
registry.add(Arc::new(Language::new(
LanguageConfig {
- name: "Rust".to_string(),
+ name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
@@ -35,7 +35,7 @@ fn test_select_language() {
)));
registry.add(Arc::new(Language::new(
LanguageConfig {
- name: "Make".to_string(),
+ name: "Make".into(),
path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
..Default::default()
},
@@ -45,17 +45,17 @@ fn test_select_language() {
// matching file extension
assert_eq!(
registry.select_language("zed/lib.rs").map(|l| l.name()),
- Some("Rust")
+ Some("Rust".into())
);
assert_eq!(
registry.select_language("zed/lib.mk").map(|l| l.name()),
- Some("Make")
+ Some("Make".into())
);
// matching filename
assert_eq!(
registry.select_language("zed/Makefile").map(|l| l.name()),
- Some("Make")
+ Some("Make".into())
);
// matching suffix that is not the full file extension or filename
@@ -1354,7 +1354,7 @@ impl Buffer {
fn rust_lang() -> Language {
Language::new(
LanguageConfig {
- name: "Rust".to_string(),
+ name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
language_server: None,
..Default::default()
@@ -387,6 +387,11 @@ impl Project {
.any(|buffer| matches!(buffer, OpenBuffer::Loading(_)))
}
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn languages(&self) -> &Arc<LanguageRegistry> {
+ &self.languages
+ }
+
pub fn fs(&self) -> &Arc<dyn Fs> {
&self.fs
}
@@ -817,7 +822,7 @@ impl Project {
};
// If the buffer has a language, set it and start/assign the language server
- if let Some(language) = self.languages.select_language(&full_path).cloned() {
+ if let Some(language) = self.languages.select_language(&full_path) {
buffer.update(cx, |buffer, cx| {
buffer.set_language(Some(language.clone()), cx);
});
@@ -3386,7 +3391,7 @@ mod tests {
let language = Arc::new(Language::new(
LanguageConfig {
- name: "Rust".to_string(),
+ name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
language_server: Some(language_server_config),
..Default::default()
@@ -3532,7 +3537,7 @@ mod tests {
let (language_server_config, mut fake_servers) = LanguageServerConfig::fake();
let language = Arc::new(Language::new(
LanguageConfig {
- name: "Rust".to_string(),
+ name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
language_server: Some(language_server_config),
..Default::default()
@@ -4425,7 +4430,7 @@ mod tests {
let (language_server_config, mut fake_servers) = LanguageServerConfig::fake();
let language = Arc::new(Language::new(
LanguageConfig {
- name: "Rust".to_string(),
+ name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
language_server: Some(language_server_config),
..Default::default()
@@ -2035,7 +2035,7 @@ mod tests {
.unwrap()
.add(Arc::new(Language::new(
LanguageConfig {
- name: "Rust".to_string(),
+ name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
language_server: Some(language_server_config),
..Default::default()
@@ -2266,7 +2266,7 @@ mod tests {
.unwrap()
.add(Arc::new(Language::new(
LanguageConfig {
- name: "Rust".to_string(),
+ name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
language_server: Some(language_server_config),
..Default::default()
@@ -2468,7 +2468,7 @@ mod tests {
.unwrap()
.add(Arc::new(Language::new(
LanguageConfig {
- name: "Rust".to_string(),
+ name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
language_server: Some(language_server_config),
..Default::default()
@@ -2585,7 +2585,7 @@ mod tests {
.unwrap()
.add(Arc::new(Language::new(
LanguageConfig {
- name: "Rust".to_string(),
+ name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
language_server: Some(language_server_config),
..Default::default()
@@ -2733,7 +2733,7 @@ mod tests {
.unwrap()
.add(Arc::new(Language::new(
LanguageConfig {
- name: "Rust".to_string(),
+ name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
language_server: Some(language_server_config),
..Default::default()
@@ -2834,7 +2834,7 @@ mod tests {
.unwrap()
.add(Arc::new(Language::new(
LanguageConfig {
- name: "Rust".to_string(),
+ name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
language_server: Some(language_server_config),
..Default::default()
@@ -3073,7 +3073,7 @@ mod tests {
.unwrap()
.add(Arc::new(Language::new(
LanguageConfig {
- name: "Rust".to_string(),
+ name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
language_server: Some(language_server_config),
..Default::default()
@@ -3842,50 +3842,8 @@ mod tests {
let rng = Rc::new(RefCell::new(rng));
- let mut host_lang_registry = Arc::new(LanguageRegistry::new());
let guest_lang_registry = Arc::new(LanguageRegistry::new());
-
- // Set up a fake language server.
- let (mut language_server_config, _fake_language_servers) = LanguageServerConfig::fake();
- language_server_config.set_fake_initializer(|fake_server| {
- fake_server.handle_request::<lsp::request::Completion, _>(|_| {
- Some(lsp::CompletionResponse::Array(vec![lsp::CompletionItem {
- text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
- range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
- new_text: "the-new-text".to_string(),
- })),
- ..Default::default()
- }]))
- });
-
- fake_server.handle_request::<lsp::request::CodeActionRequest, _>(|_| {
- Some(vec![lsp::CodeActionOrCommand::CodeAction(
- lsp::CodeAction {
- title: "the-code-action".to_string(),
- ..Default::default()
- },
- )])
- });
-
- fake_server.handle_request::<lsp::request::PrepareRenameRequest, _>(|params| {
- Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
- params.position,
- params.position,
- )))
- });
- });
-
- Arc::get_mut(&mut host_lang_registry)
- .unwrap()
- .add(Arc::new(Language::new(
- LanguageConfig {
- name: "Rust".to_string(),
- path_suffixes: vec!["rs".to_string()],
- language_server: Some(language_server_config),
- ..Default::default()
- },
- None,
- )));
+ let (language_server_config, _fake_language_servers) = LanguageServerConfig::fake();
let fs = FakeFs::new(cx.background());
fs.insert_tree(
@@ -3914,7 +3872,7 @@ mod tests {
Project::local(
host.client.clone(),
host.user_store.clone(),
- host_lang_registry.clone(),
+ Arc::new(LanguageRegistry::new()),
fs.clone(),
cx,
)
@@ -3939,6 +3897,7 @@ mod tests {
clients.push(cx.foreground().spawn(host.simulate_host(
host_project.clone(),
+ language_server_config,
operations.clone(),
max_operations,
rng.clone(),
@@ -4268,113 +4227,160 @@ mod tests {
)
}
- async fn simulate_host(
+ fn simulate_host(
mut self,
project: ModelHandle<Project>,
+ mut language_server_config: LanguageServerConfig,
operations: Rc<Cell<usize>>,
max_operations: usize,
rng: Rc<RefCell<StdRng>>,
mut cx: TestAppContext,
- ) -> (Self, TestAppContext) {
- let fs = project.read_with(&cx, |project, _| project.fs().clone());
- let mut files: Vec<PathBuf> = Default::default();
- while operations.get() < max_operations {
- operations.set(operations.get() + 1);
+ ) -> impl Future<Output = (Self, TestAppContext)> {
+ // Set up a fake language server.
+ language_server_config.set_fake_initializer(|fake_server| {
+ fake_server.handle_request::<lsp::request::Completion, _>(|_| {
+ Some(lsp::CompletionResponse::Array(vec![lsp::CompletionItem {
+ text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+ range: lsp::Range::new(
+ lsp::Position::new(0, 0),
+ lsp::Position::new(0, 0),
+ ),
+ new_text: "the-new-text".to_string(),
+ })),
+ ..Default::default()
+ }]))
+ });
- let distribution = rng.borrow_mut().gen_range(0..100);
- match distribution {
- 0..=20 if !files.is_empty() => {
- let mut path = files.choose(&mut *rng.borrow_mut()).unwrap().as_path();
- while let Some(parent_path) = path.parent() {
- path = parent_path;
- if rng.borrow_mut().gen() {
- break;
+ fake_server.handle_request::<lsp::request::CodeActionRequest, _>(|_| {
+ Some(vec![lsp::CodeActionOrCommand::CodeAction(
+ lsp::CodeAction {
+ title: "the-code-action".to_string(),
+ ..Default::default()
+ },
+ )])
+ });
+
+ fake_server.handle_request::<lsp::request::PrepareRenameRequest, _>(|params| {
+ Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
+ params.position,
+ params.position,
+ )))
+ });
+ });
+
+ project.update(&mut cx, |project, _| {
+ project.languages().add(Arc::new(Language::new(
+ LanguageConfig {
+ name: "Rust".into(),
+ path_suffixes: vec!["rs".to_string()],
+ language_server: Some(language_server_config),
+ ..Default::default()
+ },
+ None,
+ )));
+ });
+
+ async move {
+ let fs = project.read_with(&cx, |project, _| project.fs().clone());
+ let mut files: Vec<PathBuf> = Default::default();
+ while operations.get() < max_operations {
+ operations.set(operations.get() + 1);
+
+ let distribution = rng.borrow_mut().gen_range(0..100);
+ match distribution {
+ 0..=20 if !files.is_empty() => {
+ let mut path = files.choose(&mut *rng.borrow_mut()).unwrap().as_path();
+ while let Some(parent_path) = path.parent() {
+ path = parent_path;
+ if rng.borrow_mut().gen() {
+ break;
+ }
}
- }
- log::info!("Host: find/create local worktree {:?}", path);
- project
- .update(&mut cx, |project, cx| {
- project.find_or_create_local_worktree(path, false, cx)
- })
- .await
- .unwrap();
- }
- 10..=80 if !files.is_empty() => {
- let buffer = if self.buffers.is_empty() || rng.borrow_mut().gen() {
- let file = files.choose(&mut *rng.borrow_mut()).unwrap();
- let (worktree, path) = project
- .update(&mut cx, |project, cx| {
- project.find_or_create_local_worktree(file, false, cx)
- })
- .await
- .unwrap();
- let project_path =
- worktree.read_with(&cx, |worktree, _| (worktree.id(), path));
- log::info!("Host: opening path {:?}", project_path);
- let buffer = project
+ log::info!("Host: find/create local worktree {:?}", path);
+ project
.update(&mut cx, |project, cx| {
- project.open_buffer(project_path, cx)
+ project.find_or_create_local_worktree(path, false, cx)
})
.await
.unwrap();
- self.buffers.insert(buffer.clone());
- buffer
- } else {
- self.buffers
- .iter()
- .choose(&mut *rng.borrow_mut())
- .unwrap()
- .clone()
- };
-
- if rng.borrow_mut().gen_bool(0.1) {
- cx.update(|cx| {
- log::info!(
- "Host: dropping buffer {:?}",
- buffer.read(cx).file().unwrap().full_path(cx)
- );
- self.buffers.remove(&buffer);
- drop(buffer);
- });
- } else {
- buffer.update(&mut cx, |buffer, cx| {
- log::info!(
- "Host: updating buffer {:?}",
- buffer.file().unwrap().full_path(cx)
- );
- buffer.randomly_edit(&mut *rng.borrow_mut(), 5, cx)
- });
- }
- }
- _ => loop {
- let path_component_count = rng.borrow_mut().gen_range(1..=5);
- let mut path = PathBuf::new();
- path.push("/");
- for _ in 0..path_component_count {
- let letter = rng.borrow_mut().gen_range(b'a'..=b'z');
- path.push(std::str::from_utf8(&[letter]).unwrap());
}
- path.set_extension("rs");
- let parent_path = path.parent().unwrap();
-
- log::info!("Host: creating file {:?}", path);
- if fs.create_dir(&parent_path).await.is_ok()
- && fs.create_file(&path, Default::default()).await.is_ok()
- {
- files.push(path);
- break;
- } else {
- log::info!("Host: cannot create file");
+ 10..=80 if !files.is_empty() => {
+ let buffer = if self.buffers.is_empty() || rng.borrow_mut().gen() {
+ let file = files.choose(&mut *rng.borrow_mut()).unwrap();
+ let (worktree, path) = project
+ .update(&mut cx, |project, cx| {
+ project.find_or_create_local_worktree(file, false, cx)
+ })
+ .await
+ .unwrap();
+ let project_path =
+ worktree.read_with(&cx, |worktree, _| (worktree.id(), path));
+ log::info!("Host: opening path {:?}", project_path);
+ let buffer = project
+ .update(&mut cx, |project, cx| {
+ project.open_buffer(project_path, cx)
+ })
+ .await
+ .unwrap();
+ self.buffers.insert(buffer.clone());
+ buffer
+ } else {
+ self.buffers
+ .iter()
+ .choose(&mut *rng.borrow_mut())
+ .unwrap()
+ .clone()
+ };
+
+ if rng.borrow_mut().gen_bool(0.1) {
+ cx.update(|cx| {
+ log::info!(
+ "Host: dropping buffer {:?}",
+ buffer.read(cx).file().unwrap().full_path(cx)
+ );
+ self.buffers.remove(&buffer);
+ drop(buffer);
+ });
+ } else {
+ buffer.update(&mut cx, |buffer, cx| {
+ log::info!(
+ "Host: updating buffer {:?}",
+ buffer.file().unwrap().full_path(cx)
+ );
+ buffer.randomly_edit(&mut *rng.borrow_mut(), 5, cx)
+ });
+ }
}
- },
+ _ => loop {
+ let path_component_count = rng.borrow_mut().gen_range(1..=5);
+ let mut path = PathBuf::new();
+ path.push("/");
+ for _ in 0..path_component_count {
+ let letter = rng.borrow_mut().gen_range(b'a'..=b'z');
+ path.push(std::str::from_utf8(&[letter]).unwrap());
+ }
+ path.set_extension("rs");
+ let parent_path = path.parent().unwrap();
+
+ log::info!("Host: creating file {:?}", path);
+ if fs.create_dir(&parent_path).await.is_ok()
+ && fs.create_file(&path, Default::default()).await.is_ok()
+ {
+ files.push(path);
+ break;
+ } else {
+ log::info!("Host: cannot create file");
+ }
+ },
+ }
+
+ cx.background().simulate_random_delay().await;
}
- cx.background().simulate_random_delay().await;
+ self.project = Some(project);
+ (self, cx)
}
-
- self.project = Some(project);
- (self, cx)
}
pub async fn simulate_guest(
@@ -37,7 +37,7 @@ impl LspStatus {
&mut this.downloading,
&mut this.failed,
] {
- vector.retain(|name| name != language.name());
+ vector.retain(|name| name != language.name().as_ref());
}
match event {
@@ -11,7 +11,7 @@ pub struct Settings {
pub tab_size: usize,
soft_wrap: SoftWrap,
preferred_line_length: u32,
- overrides: HashMap<String, Override>,
+ overrides: HashMap<Arc<str>, Override>,
pub theme: Arc<Theme>,
}
@@ -50,21 +50,25 @@ impl Settings {
self
}
- pub fn with_overrides(mut self, language_name: impl Into<String>, overrides: Override) -> Self {
+ pub fn with_overrides(
+ mut self,
+ language_name: impl Into<Arc<str>>,
+ overrides: Override,
+ ) -> Self {
self.overrides.insert(language_name.into(), overrides);
self
}
pub fn soft_wrap(&self, language: Option<&Arc<Language>>) -> SoftWrap {
language
- .and_then(|language| self.overrides.get(language.name()))
+ .and_then(|language| self.overrides.get(language.name().as_ref()))
.and_then(|settings| settings.soft_wrap)
.unwrap_or(self.soft_wrap)
}
pub fn preferred_line_length(&self, language: Option<&Arc<Language>>) -> u32 {
language
- .and_then(|language| self.overrides.get(language.name()))
+ .and_then(|language| self.overrides.get(language.name().as_ref()))
.and_then(|settings| settings.preferred_line_length)
.unwrap_or(self.preferred_line_length)
}
@@ -25,18 +25,15 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
let http = FakeHttpClient::with_404_response();
let client = Client::new(http.clone());
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
- let mut languages = LanguageRegistry::new();
- languages.add(
- Arc::new(language::Language::new(
- language::LanguageConfig {
- name: "Rust".to_string(),
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- )),
-
- );
+ let languages = LanguageRegistry::new();
+ languages.add(Arc::new(language::Language::new(
+ language::LanguageConfig {
+ name: "Rust".into(),
+ path_suffixes: vec!["rs".to_string()],
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::language()),
+ )));
Arc::new(AppState {
settings_tx: Arc::new(Mutex::new(settings_tx)),
settings,
@@ -534,7 +534,7 @@ mod tests {
editor.read_with(&cx, |editor, cx| {
assert!(!editor.is_dirty(cx));
assert_eq!(editor.title(cx), "the-new-name.rs");
- assert_eq!(editor.language(cx).unwrap().name(), "Rust");
+ assert_eq!(editor.language(cx).unwrap().name().as_ref(), "Rust");
});
// Edit the file and save it again. This time, there is no filename prompt.
@@ -614,7 +614,7 @@ mod tests {
// The buffer is not dirty anymore and the language is assigned based on the path.
editor.read_with(&cx, |editor, cx| {
assert!(!editor.is_dirty(cx));
- assert_eq!(editor.language(cx).unwrap().name(), "Rust")
+ assert_eq!(editor.language(cx).unwrap().name().as_ref(), "Rust")
});
}