Introduce LanguageRegistry object

* Include it, along with settings in `OpenParams` grouped under a new struct called `AppState`

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2021-05-20 12:00:17 -07:00 committed by Antonio Scandurra
parent eb345e7182
commit 2f378be1a8
10 changed files with 271 additions and 46 deletions

24
Cargo.lock generated
View File

@ -1202,7 +1202,7 @@ dependencies = [
"smallvec",
"smol",
"tiny-skia",
"tree-sitter",
"tree-sitter 0.17.1",
"usvg",
]
@ -2712,6 +2712,26 @@ dependencies = [
"regex",
]
[[package]]
name = "tree-sitter"
version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f41201fed3db3b520405a9c01c61773a250d4c3f43e9861c14b2bb232c981ab"
dependencies = [
"cc",
"regex",
]
[[package]]
name = "tree-sitter-rust"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784f7ef9cdbd4c895dc2d4bb785e95b4a5364a602eec803681db83d1927ddf15"
dependencies = [
"cc",
"tree-sitter 0.19.3",
]
[[package]]
name = "ttf-parser"
version = "0.9.0"
@ -2987,5 +3007,7 @@ dependencies = [
"smallvec",
"smol",
"tempdir",
"tree-sitter 0.19.3",
"tree-sitter-rust",
"unindent",
]

View File

@ -38,6 +38,8 @@ similar = "1.3"
simplelog = "0.9"
smallvec = {version = "1.6", features = ["union"]}
smol = "1.2.5"
tree-sitter = "0.19.3"
tree-sitter-rust = "0.19.0"
[dev-dependencies]
cargo-bundle = "0.5.0"

View File

@ -0,0 +1,6 @@
[
"else"
"fn"
"if"
"while"
] @keyword

View File

@ -12,6 +12,7 @@ use similar::{ChangeTag, TextDiff};
use crate::{
editor::Bias,
language::Language,
operation_queue::{self, OperationQueue},
sum_tree::{self, FilterCursor, SeekBias, SumTree},
time::{self, ReplicaId},
@ -68,6 +69,7 @@ pub struct Buffer {
undo_map: UndoMap,
history: History,
file: Option<FileHandle>,
language: Option<Arc<Language>>,
selections: HashMap<SelectionSetId, Arc<[Selection]>>,
pub selections_last_update: SelectionsVersion,
deferred_ops: OperationQueue<Operation>,
@ -357,22 +359,24 @@ impl Buffer {
base_text: T,
ctx: &mut ModelContext<Self>,
) -> Self {
Self::build(replica_id, History::new(base_text.into()), None, ctx)
Self::build(replica_id, History::new(base_text.into()), None, None, ctx)
}
pub fn from_history(
replica_id: ReplicaId,
history: History,
file: Option<FileHandle>,
language: Option<Arc<Language>>,
ctx: &mut ModelContext<Self>,
) -> Self {
Self::build(replica_id, history, file, ctx)
Self::build(replica_id, history, file, language, ctx)
}
fn build(
replica_id: ReplicaId,
history: History,
file: Option<FileHandle>,
language: Option<Arc<Language>>,
ctx: &mut ModelContext<Self>,
) -> Self {
let saved_mtime;
@ -472,6 +476,7 @@ impl Buffer {
undo_map: Default::default(),
history,
file,
language,
saved_mtime,
selections: HashMap::default(),
selections_last_update: 0,
@ -1884,6 +1889,7 @@ impl Clone for Buffer {
selections_last_update: self.selections_last_update.clone(),
deferred_ops: self.deferred_ops.clone(),
file: self.file.clone(),
language: self.language.clone(),
deferred_replicas: self.deferred_replicas.clone(),
replica_id: self.replica_id,
local_clock: self.local_clock.clone(),
@ -2812,7 +2818,7 @@ mod tests {
let file1 = app.update(|ctx| tree.file("file1", ctx)).await;
let buffer1 = app.add_model(|ctx| {
Buffer::from_history(0, History::new("abc".into()), Some(file1), ctx)
Buffer::from_history(0, History::new("abc".into()), Some(file1), None, ctx)
});
let events = Rc::new(RefCell::new(Vec::new()));
@ -2877,7 +2883,7 @@ mod tests {
move |_, event, _| events.borrow_mut().push(event.clone())
});
Buffer::from_history(0, History::new("abc".into()), Some(file2), ctx)
Buffer::from_history(0, History::new("abc".into()), Some(file2), None, ctx)
});
fs::remove_file(dir.path().join("file2")).unwrap();
@ -2896,7 +2902,7 @@ mod tests {
move |_, event, _| events.borrow_mut().push(event.clone())
});
Buffer::from_history(0, History::new("abc".into()), Some(file3), ctx)
Buffer::from_history(0, History::new("abc".into()), Some(file3), None, ctx)
});
tree.flush_fs_events(&app).await;
@ -2923,7 +2929,13 @@ mod tests {
let abs_path = dir.path().join("the-file");
let file = app.update(|ctx| tree.file("the-file", ctx)).await;
let buffer = app.add_model(|ctx| {
Buffer::from_history(0, History::new(initial_contents.into()), Some(file), ctx)
Buffer::from_history(
0,
History::new(initial_contents.into()),
Some(file),
None,
ctx,
)
});
// Add a cursor at the start of each row.

View File

@ -458,7 +458,11 @@ impl FileFinder {
#[cfg(test)]
mod tests {
use super::*;
use crate::{editor, settings, test::temp_tree, workspace::Workspace};
use crate::{
editor,
test::{build_app_state, temp_tree},
workspace::Workspace,
};
use serde_json::json;
use std::fs;
use tempdir::TempDir;
@ -474,9 +478,10 @@ mod tests {
editor::init(ctx);
});
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (window_id, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
let mut workspace =
Workspace::new(0, app_state.settings, app_state.language_registry, ctx);
workspace.add_worktree(tmp_dir.path(), ctx);
workspace
});
@ -541,15 +546,21 @@ mod tests {
"hi": "",
"hiccup": "",
}));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (_, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings.clone(), ctx);
let mut workspace = Workspace::new(
0,
app_state.settings.clone(),
app_state.language_registry.clone(),
ctx,
);
workspace.add_worktree(tmp_dir.path(), ctx);
workspace
});
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
.await;
let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
let (_, finder) =
app.add_window(|ctx| FileFinder::new(app_state.settings, workspace.clone(), ctx));
let query = "hi".to_string();
finder
@ -598,15 +609,21 @@ mod tests {
fs::create_dir(&dir_path).unwrap();
fs::write(&file_path, "").unwrap();
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (_, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings.clone(), ctx);
let mut workspace = Workspace::new(
0,
app_state.settings.clone(),
app_state.language_registry.clone(),
ctx,
);
workspace.add_worktree(&file_path, ctx);
workspace
});
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
.await;
let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
let (_, finder) =
app.add_window(|ctx| FileFinder::new(app_state.settings, workspace.clone(), ctx));
// Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file.
@ -641,9 +658,17 @@ mod tests {
"dir1": { "a.txt": "" },
"dir2": { "a.txt": "" }
}));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, workspace) = app.add_window(|ctx| Workspace::new(0, settings.clone(), ctx));
let app_state = app.read(build_app_state);
let (_, workspace) = app.add_window(|ctx| {
Workspace::new(
0,
app_state.settings.clone(),
app_state.language_registry.clone(),
ctx,
)
});
workspace
.update(&mut app, |workspace, ctx| {
@ -656,7 +681,8 @@ mod tests {
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
.await;
let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
let (_, finder) =
app.add_window(|ctx| FileFinder::new(app_state.settings, workspace.clone(), ctx));
// Run a search that matches two files with the same relative path.
finder

105
zed/src/language.rs Normal file
View File

@ -0,0 +1,105 @@
use rust_embed::RustEmbed;
use std::{path::Path, sync::Arc};
use tree_sitter::{Language as Grammar, Query};
pub use tree_sitter::{Parser, Tree};
#[derive(RustEmbed)]
#[folder = "languages"]
pub struct LanguageDir;
pub struct Language {
name: String,
grammar: Grammar,
highlight_query: Query,
path_suffixes: Vec<String>,
}
pub struct LanguageRegistry {
languages: Vec<Arc<Language>>,
}
impl LanguageRegistry {
pub fn new() -> Self {
let grammar = tree_sitter_rust::language();
let rust_language = Language {
name: "Rust".to_string(),
grammar,
highlight_query: Query::new(
grammar,
std::str::from_utf8(LanguageDir::get("rust/highlights.scm").unwrap().as_ref())
.unwrap(),
)
.unwrap(),
path_suffixes: vec!["rs".to_string()],
};
Self {
languages: vec![Arc::new(rust_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
.path_suffixes
.iter()
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_select_language() {
let grammar = tree_sitter_rust::language();
let registry = LanguageRegistry {
languages: vec![
Arc::new(Language {
name: "Rust".to_string(),
grammar,
highlight_query: Query::new(grammar, "").unwrap(),
path_suffixes: vec!["rs".to_string()],
}),
Arc::new(Language {
name: "Make".to_string(),
grammar,
highlight_query: Query::new(grammar, "").unwrap(),
path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
}),
],
};
// matching file extension
assert_eq!(
registry.select_language("zed/lib.rs").map(get_name),
Some("Rust")
);
assert_eq!(
registry.select_language("zed/lib.mk").map(get_name),
Some("Make")
);
// matching filename
assert_eq!(
registry.select_language("zed/Makefile").map(get_name),
Some("Make")
);
// matching suffix that is not the full file extension or filename
assert_eq!(registry.select_language("zed/cars").map(get_name), None);
assert_eq!(registry.select_language("zed/a.cars").map(get_name), None);
assert_eq!(registry.select_language("zed/sumk").map(get_name), None);
fn get_name(language: &Arc<Language>) -> &str {
language.name.as_str()
}
}
}

View File

@ -1,6 +1,7 @@
pub mod assets;
pub mod editor;
pub mod file_finder;
pub mod language;
pub mod menus;
mod operation_queue;
pub mod settings;
@ -11,3 +12,9 @@ mod time;
mod util;
pub mod workspace;
mod worktree;
#[derive(Clone)]
pub struct AppState {
pub settings: postage::watch::Receiver<settings::Settings>,
pub language_registry: std::sync::Arc<language::LanguageRegistry>,
}

View File

@ -4,19 +4,27 @@
use fs::OpenOptions;
use log::LevelFilter;
use simplelog::SimpleLogger;
use std::{fs, path::PathBuf};
use std::{fs, path::PathBuf, sync::Arc};
use zed::{
assets, editor, file_finder, menus, settings,
assets, editor, file_finder, language, menus, settings,
workspace::{self, OpenParams},
AppState,
};
fn main() {
init_logger();
let app = gpui::App::new(assets::Assets).unwrap();
let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap();
let (_, settings) = settings::channel(&app.font_cache()).unwrap();
let language_registry = Arc::new(language::LanguageRegistry::new());
let app_state = AppState {
language_registry,
settings,
};
app.run(move |ctx| {
ctx.set_menus(menus::menus(settings_rx.clone()));
ctx.set_menus(menus::menus(app_state.settings.clone()));
workspace::init(ctx);
editor::init(ctx);
file_finder::init(ctx);
@ -31,7 +39,7 @@ fn main() {
"workspace:open_paths",
OpenParams {
paths,
settings: settings_rx,
app_state: app_state.clone(),
},
);
}

View File

@ -1,9 +1,11 @@
use crate::time::ReplicaId;
use crate::{language::LanguageRegistry, settings, time::ReplicaId, AppState};
use ctor::ctor;
use gpui::AppContext;
use rand::Rng;
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
sync::Arc,
};
use tempdir::TempDir;
@ -141,3 +143,12 @@ fn write_tree(path: &Path, tree: serde_json::Value) {
panic!("You must pass a JSON object to this helper")
}
}
pub fn build_app_state(ctx: &AppContext) -> AppState {
let settings = settings::channel(&ctx.font_cache()).unwrap().1;
let language_registry = Arc::new(LanguageRegistry::new());
AppState {
settings,
language_registry,
}
}

View File

@ -2,9 +2,11 @@ pub mod pane;
pub mod pane_group;
use crate::{
editor::{Buffer, BufferView},
language::LanguageRegistry,
settings::Settings,
time::ReplicaId,
worktree::{FileHandle, Worktree, WorktreeHandle},
AppState,
};
use futures_core::Future;
use gpui::{
@ -40,11 +42,11 @@ pub fn init(app: &mut MutableAppContext) {
pub struct OpenParams {
pub paths: Vec<PathBuf>,
pub settings: watch::Receiver<Settings>,
pub app_state: AppState,
}
fn open(settings: &watch::Receiver<Settings>, ctx: &mut MutableAppContext) {
let settings = settings.clone();
fn open(app_state: &AppState, ctx: &mut MutableAppContext) {
let app_state = app_state.clone();
ctx.prompt_for_paths(
PathPromptOptions {
files: true,
@ -53,7 +55,7 @@ fn open(settings: &watch::Receiver<Settings>, ctx: &mut MutableAppContext) {
},
move |paths, ctx| {
if let Some(paths) = paths {
ctx.dispatch_global_action("workspace:open_paths", OpenParams { paths, settings });
ctx.dispatch_global_action("workspace:open_paths", OpenParams { paths, app_state });
}
},
);
@ -84,7 +86,12 @@ fn open_paths(params: &OpenParams, app: &mut MutableAppContext) {
// Add a new workspace if necessary
app.add_window(|ctx| {
let mut view = Workspace::new(0, params.settings.clone(), ctx);
let mut view = Workspace::new(
0,
params.app_state.settings.clone(),
params.app_state.language_registry.clone(),
ctx,
);
let open_paths = view.open_paths(&params.paths, ctx);
ctx.foreground().spawn(open_paths).detach();
view
@ -284,6 +291,7 @@ pub struct State {
pub struct Workspace {
pub settings: watch::Receiver<Settings>,
language_registry: Arc<LanguageRegistry>,
modal: Option<AnyViewHandle>,
center: PaneGroup,
panes: Vec<ViewHandle<Pane>>,
@ -301,6 +309,7 @@ impl Workspace {
pub fn new(
replica_id: ReplicaId,
settings: watch::Receiver<Settings>,
language_registry: Arc<LanguageRegistry>,
ctx: &mut ViewContext<Self>,
) -> Self {
let pane = ctx.add_view(|_| Pane::new(settings.clone()));
@ -316,6 +325,7 @@ impl Workspace {
panes: vec![pane.clone()],
active_pane: pane.clone(),
settings,
language_registry,
replica_id,
worktrees: Default::default(),
items: Default::default(),
@ -503,6 +513,7 @@ impl Workspace {
let (mut tx, rx) = postage::watch::channel();
entry.insert(rx);
let replica_id = self.replica_id;
let language_registry = self.language_registry.clone();
ctx.as_mut()
.spawn(|mut ctx| async move {
@ -512,7 +523,14 @@ impl Workspace {
*tx.borrow_mut() = Some(match history {
Ok(history) => Ok(Box::new(ctx.add_model(|ctx| {
Buffer::from_history(replica_id, history, Some(file), ctx)
let language = language_registry.select_language(path);
Buffer::from_history(
replica_id,
history,
Some(file),
language.cloned(),
ctx,
)
}))),
Err(error) => Err(Arc::new(error)),
})
@ -757,14 +775,17 @@ impl WorkspaceHandle for ViewHandle<Workspace> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{editor::BufferView, settings, test::temp_tree};
use crate::{
editor::BufferView,
test::{build_app_state, temp_tree},
};
use serde_json::json;
use std::{collections::HashSet, fs};
use tempdir::TempDir;
#[gpui::test]
fn test_open_paths_action(app: &mut gpui::MutableAppContext) {
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = build_app_state(app.as_ref());
init(app);
@ -790,7 +811,7 @@ mod tests {
dir.path().join("a").to_path_buf(),
dir.path().join("b").to_path_buf(),
],
settings: settings.clone(),
app_state: app_state.clone(),
},
);
assert_eq!(app.window_ids().count(), 1);
@ -799,7 +820,7 @@ mod tests {
"workspace:open_paths",
OpenParams {
paths: vec![dir.path().join("a").to_path_buf()],
settings: settings.clone(),
app_state: app_state.clone(),
},
);
assert_eq!(app.window_ids().count(), 1);
@ -815,7 +836,7 @@ mod tests {
dir.path().join("b").to_path_buf(),
dir.path().join("c").to_path_buf(),
],
settings: settings.clone(),
app_state: app_state.clone(),
},
);
assert_eq!(app.window_ids().count(), 2);
@ -831,10 +852,11 @@ mod tests {
},
}));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (_, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
let mut workspace =
Workspace::new(0, app_state.settings, app_state.language_registry, ctx);
workspace.add_worktree(dir.path(), ctx);
workspace
});
@ -935,9 +957,10 @@ mod tests {
"b.txt": "",
}));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (_, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
let mut workspace =
Workspace::new(0, app_state.settings, app_state.language_registry, ctx);
workspace.add_worktree(dir1.path(), ctx);
workspace
});
@ -1003,9 +1026,10 @@ mod tests {
"a.txt": "",
}));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (window_id, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
let mut workspace =
Workspace::new(0, app_state.settings, app_state.language_registry, ctx);
workspace.add_worktree(dir.path(), ctx);
workspace
});
@ -1046,9 +1070,10 @@ mod tests {
#[gpui::test]
async fn test_open_and_save_new_file(mut app: gpui::TestAppContext) {
let dir = TempDir::new("test-new-file").unwrap();
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (_, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
let mut workspace =
Workspace::new(0, app_state.settings, app_state.language_registry, ctx);
workspace.add_worktree(dir.path(), ctx);
workspace
});
@ -1150,9 +1175,10 @@ mod tests {
},
}));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (window_id, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
let mut workspace =
Workspace::new(0, app_state.settings, app_state.language_registry, ctx);
workspace.add_worktree(dir.path(), ctx);
workspace
});