Cargo.lock 🔗
@@ -3831,6 +3831,7 @@ dependencies = [
"rpc",
"serde 1.0.125",
"serde_json 1.0.64",
+ "simplelog",
"smol",
"sum_tree",
"tempdir",
Max Brunsfeld and Nathan Sobo created
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Cargo.lock | 1
crates/language/Cargo.toml | 8 +
crates/language/src/language.rs | 31 ++++++
crates/language/src/lib.rs | 5
crates/language/src/tests.rs | 8 -
crates/lsp/src/lib.rs | 7 +
crates/project/Cargo.toml | 1
crates/project/src/lib.rs | 9 -
crates/project/src/worktree.rs | 159 +++++++++++++++++++++-------------
crates/server/src/rpc.rs | 59 ++++++++----
crates/workspace/src/items.rs | 14 ++
crates/zed/src/language.rs | 2
12 files changed, 197 insertions(+), 107 deletions(-)
@@ -3831,6 +3831,7 @@ dependencies = [
"rpc",
"serde 1.0.125",
"serde_json 1.0.64",
+ "simplelog",
"smol",
"sum_tree",
"tempdir",
@@ -4,7 +4,12 @@ version = "0.1.0"
edition = "2018"
[features]
-test-support = ["rand", "buffer/test-support", "lsp/test-support"]
+test-support = [
+ "rand",
+ "buffer/test-support",
+ "lsp/test-support",
+ "tree-sitter-rust",
+]
[dependencies]
buffer = { path = "../buffer" }
@@ -25,6 +30,7 @@ serde = { version = "1", features = ["derive"] }
similar = "1.3"
smol = "1.2"
tree-sitter = "0.19.5"
+tree-sitter-rust = { version = "0.19.0", optional = true }
[dev-dependencies]
buffer = { path = "../buffer", features = ["test-support"] }
@@ -1,6 +1,7 @@
use crate::HighlightMap;
use anyhow::Result;
-use gpui::AppContext;
+use gpui::{executor::Background, AppContext};
+use lsp::LanguageServer;
use parking_lot::Mutex;
use serde::Deserialize;
use std::{collections::HashSet, path::Path, str, sync::Arc};
@@ -16,10 +17,13 @@ pub struct LanguageConfig {
pub language_server: Option<LanguageServerConfig>,
}
-#[derive(Deserialize)]
+#[derive(Default, Deserialize)]
pub struct LanguageServerConfig {
pub binary: String,
pub disk_based_diagnostic_sources: HashSet<String>,
+ #[cfg(any(test, feature = "test-support"))]
+ #[serde(skip)]
+ pub fake_server: Option<(Arc<LanguageServer>, Arc<std::sync::atomic::AtomicBool>)>,
}
#[derive(Clone, Debug, Deserialize)]
@@ -117,6 +121,12 @@ impl Language {
cx: &AppContext,
) -> Result<Option<Arc<lsp::LanguageServer>>> {
if let Some(config) = &self.config.language_server {
+ #[cfg(any(test, feature = "test-support"))]
+ if let Some((server, started)) = &config.fake_server {
+ started.store(true, std::sync::atomic::Ordering::SeqCst);
+ return Ok(Some(server.clone()));
+ }
+
const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
cx.platform()
@@ -151,6 +161,23 @@ impl Language {
}
}
+#[cfg(any(test, feature = "test-support"))]
+impl LanguageServerConfig {
+ pub async fn fake(executor: Arc<Background>) -> (Self, lsp::FakeLanguageServer) {
+ let (server, fake) = lsp::LanguageServer::fake(executor).await;
+ fake.started
+ .store(false, std::sync::atomic::Ordering::SeqCst);
+ let started = fake.started.clone();
+ (
+ Self {
+ fake_server: Some((server, started)),
+ ..Default::default()
+ },
+ fake,
+ )
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -6,7 +6,7 @@ mod tests;
pub use self::{
highlight_map::{HighlightId, HighlightMap},
- language::{BracketPair, Language, LanguageConfig, LanguageRegistry},
+ language::{BracketPair, Language, LanguageConfig, LanguageRegistry, LanguageServerConfig},
};
use anyhow::{anyhow, Result};
pub use buffer::{Buffer as TextBuffer, Operation as _, *};
@@ -37,6 +37,9 @@ use std::{
use tree_sitter::{InputEdit, Parser, QueryCursor, Tree};
use util::{post_inc, TryFutureExt as _};
+#[cfg(any(test, feature = "test-support"))]
+pub use tree_sitter_rust;
+
pub use lsp::DiagnosticSeverity;
thread_local! {
@@ -1,7 +1,6 @@
use super::*;
-use crate::language::LanguageServerConfig;
use gpui::{ModelHandle, MutableAppContext};
-use std::{iter::FromIterator, rc::Rc};
+use std::rc::Rc;
use unindent::Unindent as _;
#[gpui::test]
@@ -676,10 +675,7 @@ fn rust_lang() -> Option<Arc<Language>> {
LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
- language_server: Some(LanguageServerConfig {
- binary: "rust-analyzer".to_string(),
- disk_based_diagnostic_sources: HashSet::from_iter(vec!["rustc".to_string()]),
- }),
+ language_server: None,
..Default::default()
},
tree_sitter_rust::language(),
@@ -16,7 +16,7 @@ use std::{
io::Write,
str::FromStr,
sync::{
- atomic::{AtomicUsize, Ordering::SeqCst},
+ atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
Arc,
},
};
@@ -427,6 +427,7 @@ pub struct FakeLanguageServer {
buffer: Vec<u8>,
stdin: smol::io::BufReader<async_pipe::PipeReader>,
stdout: smol::io::BufWriter<async_pipe::PipeWriter>,
+ pub started: Arc<AtomicBool>,
}
#[cfg(any(test, feature = "test-support"))]
@@ -444,6 +445,7 @@ impl LanguageServer {
stdin: smol::io::BufReader::new(stdin.1),
stdout: smol::io::BufWriter::new(stdout.0),
buffer: Vec::new(),
+ started: Arc::new(AtomicBool::new(true)),
};
let server = Self::new_internal(stdin.0, stdout.1, Path::new("/"), executor).unwrap();
@@ -460,6 +462,9 @@ impl LanguageServer {
#[cfg(any(test, feature = "test-support"))]
impl FakeLanguageServer {
pub async fn notify<T: notification::Notification>(&mut self, params: T::Params) {
+ if !self.started.load(std::sync::atomic::Ordering::SeqCst) {
+ panic!("can't simulate an LSP notification before the server has been started");
+ }
let message = serde_json::to_vec(&Notification {
jsonrpc: JSON_RPC_VERSION,
method: T::METHOD,
@@ -40,5 +40,6 @@ lsp = { path = "../lsp", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
rand = "0.8.3"
+simplelog = "0.9"
tempdir = { version = "0.3.7" }
unindent = "0.1.7"
@@ -12,7 +12,7 @@ use std::{
path::Path,
sync::{atomic::AtomicBool, Arc},
};
-use util::{ResultExt, TryFutureExt as _};
+use util::TryFutureExt as _;
pub use fs::*;
pub use worktree::*;
@@ -73,13 +73,8 @@ impl Project {
let rpc = self.client.clone();
let languages = self.languages.clone();
let path = Arc::from(abs_path);
- let language_server = languages
- .get_language("Rust")
- .map(|language| language.start_server(&path, cx));
cx.spawn(|this, mut cx| async move {
- let language_server = language_server.and_then(|language| language.log_err().flatten());
- let worktree =
- Worktree::open_local(rpc, path, fs, languages, language_server, &mut cx).await?;
+ let worktree = Worktree::open_local(rpc, path, fs, languages, &mut cx).await?;
this.update(&mut cx, |this, cx| {
this.add_worktree(worktree.clone(), cx);
});
@@ -12,7 +12,7 @@ use gpui::{
executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
Task, UpgradeModelHandle, WeakModelHandle,
};
-use language::{Buffer, LanguageRegistry, Operation, Rope};
+use language::{Buffer, Language, LanguageRegistry, Operation, Rope};
use lazy_static::lazy_static;
use lsp::LanguageServer;
use parking_lot::Mutex;
@@ -98,17 +98,21 @@ impl Entity for Worktree {
) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
use futures::FutureExt;
- if let Some(server) = self.language_server() {
- if let Some(shutdown) = server.shutdown() {
- return Some(
- async move {
- shutdown.await.log_err();
- }
- .boxed(),
- );
- }
+ if let Self::Local(worktree) = self {
+ let shutdown_futures = worktree
+ .language_servers
+ .drain()
+ .filter_map(|(_, server)| server.shutdown())
+ .collect::<Vec<_>>();
+ Some(
+ async move {
+ futures::future::join_all(shutdown_futures).await;
+ }
+ .boxed(),
+ )
+ } else {
+ None
}
- None
}
}
@@ -118,11 +122,10 @@ impl Worktree {
path: impl Into<Arc<Path>>,
fs: Arc<dyn Fs>,
languages: Arc<LanguageRegistry>,
- language_server: Option<Arc<LanguageServer>>,
cx: &mut AsyncAppContext,
) -> Result<ModelHandle<Self>> {
let (tree, scan_states_tx) =
- LocalWorktree::new(rpc, path, fs.clone(), languages, language_server, cx).await?;
+ LocalWorktree::new(rpc, path, fs.clone(), languages, cx).await?;
tree.update(cx, |tree, cx| {
let tree = tree.as_local_mut().unwrap();
let abs_path = tree.snapshot.abs_path.clone();
@@ -315,13 +318,6 @@ impl Worktree {
}
}
- pub fn language_server(&self) -> Option<&Arc<LanguageServer>> {
- match self {
- Worktree::Local(worktree) => worktree.language_server.as_ref(),
- Worktree::Remote(_) => None,
- }
- }
-
pub fn handle_add_peer(
&mut self,
envelope: TypedEnvelope<proto::AddPeer>,
@@ -781,7 +777,7 @@ pub struct LocalWorktree {
languages: Arc<LanguageRegistry>,
rpc: Arc<Client>,
fs: Arc<dyn Fs>,
- language_server: Option<Arc<LanguageServer>>,
+ language_servers: HashMap<String, Arc<LanguageServer>>,
}
#[derive(Default, Deserialize)]
@@ -795,7 +791,6 @@ impl LocalWorktree {
path: impl Into<Arc<Path>>,
fs: Arc<dyn Fs>,
languages: Arc<LanguageRegistry>,
- language_server: Option<Arc<LanguageServer>>,
cx: &mut AsyncAppContext,
) -> Result<(ModelHandle<Worktree>, Sender<ScanState>)> {
let abs_path = path.into();
@@ -896,7 +891,7 @@ impl LocalWorktree {
languages,
rpc,
fs,
- language_server,
+ language_servers: Default::default(),
};
cx.spawn_weak(|this, mut cx| async move {
@@ -926,33 +921,57 @@ impl LocalWorktree {
})
.detach();
- if let Some(language_server) = &tree.language_server {
- let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
- language_server
- .on_notification::<lsp::notification::PublishDiagnostics, _>(move |params| {
- smol::block_on(diagnostics_tx.send(params)).ok();
- })
- .detach();
- cx.spawn_weak(|this, mut cx| async move {
- while let Ok(diagnostics) = diagnostics_rx.recv().await {
- if let Some(handle) = cx.read(|cx| this.upgrade(cx)) {
- handle.update(&mut cx, |this, cx| {
- this.update_diagnostics(diagnostics, cx).log_err();
- });
- } else {
- break;
- }
- }
- })
- .detach();
- }
-
Worktree::Local(tree)
});
Ok((tree, scan_states_tx))
}
+ pub fn languages(&self) -> &LanguageRegistry {
+ &self.languages
+ }
+
+ pub fn ensure_language_server(
+ &mut self,
+ language: &Language,
+ cx: &mut ModelContext<Worktree>,
+ ) -> Option<Arc<LanguageServer>> {
+ if let Some(server) = self.language_servers.get(language.name()) {
+ return Some(server.clone());
+ }
+
+ if let Some(language_server) = language
+ .start_server(self.abs_path(), cx)
+ .log_err()
+ .flatten()
+ {
+ let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
+ language_server
+ .on_notification::<lsp::notification::PublishDiagnostics, _>(move |params| {
+ smol::block_on(diagnostics_tx.send(params)).ok();
+ })
+ .detach();
+ cx.spawn_weak(|this, mut cx| async move {
+ while let Ok(diagnostics) = diagnostics_rx.recv().await {
+ if let Some(handle) = cx.read(|cx| this.upgrade(cx)) {
+ handle.update(&mut cx, |this, cx| {
+ this.update_diagnostics(diagnostics, cx).log_err();
+ });
+ } else {
+ break;
+ }
+ }
+ })
+ .detach();
+
+ self.language_servers
+ .insert(language.name().to_string(), language_server.clone());
+ Some(language_server.clone())
+ } else {
+ None
+ }
+ }
+
pub fn open_buffer(
&mut self,
path: &Path,
@@ -976,7 +995,6 @@ impl LocalWorktree {
});
let path = Arc::from(path);
- let language_server = self.language_server.clone();
cx.spawn(|this, mut cx| async move {
if let Some(existing_buffer) = existing_buffer {
Ok(existing_buffer)
@@ -988,11 +1006,14 @@ impl LocalWorktree {
use language::File;
this.languages().select_language(file.full_path()).cloned()
});
- let diagnostics = this.update(&mut cx, |this, _| {
- this.as_local_mut()
- .unwrap()
- .diagnostics
- .remove(path.as_ref())
+ let (diagnostics, language_server) = this.update(&mut cx, |this, cx| {
+ let this = this.as_local_mut().unwrap();
+ (
+ this.diagnostics.remove(path.as_ref()),
+ language
+ .as_ref()
+ .and_then(|language| this.ensure_language_server(language, cx)),
+ )
});
let buffer = cx.add_model(|cx| {
let mut buffer = Buffer::from_file(0, contents, Box::new(file), cx);
@@ -2925,7 +2946,8 @@ mod tests {
use buffer::Point;
use client::test::FakeServer;
use fs::RealFs;
- use language::Diagnostic;
+ use language::{tree_sitter_rust, LanguageServerConfig};
+ use language::{Diagnostic, LanguageConfig};
use lsp::Url;
use rand::prelude::*;
use serde_json::json;
@@ -2957,7 +2979,6 @@ mod tests {
Arc::from(Path::new("/root")),
Arc::new(fs),
Default::default(),
- None,
&mut cx.to_async(),
)
.await
@@ -2990,7 +3011,6 @@ mod tests {
dir.path(),
Arc::new(RealFs),
Default::default(),
- None,
&mut cx.to_async(),
)
.await
@@ -3021,7 +3041,6 @@ mod tests {
file_path.clone(),
Arc::new(RealFs),
Default::default(),
- None,
&mut cx.to_async(),
)
.await
@@ -3068,7 +3087,6 @@ mod tests {
dir.path(),
Arc::new(RealFs),
Default::default(),
- None,
&mut cx.to_async(),
)
.await
@@ -3229,7 +3247,6 @@ mod tests {
dir.path(),
Arc::new(RealFs),
Default::default(),
- None,
&mut cx.to_async(),
)
.await
@@ -3284,7 +3301,6 @@ mod tests {
"/path/to/the-dir".as_ref(),
fs,
Default::default(),
- None,
&mut cx.to_async(),
)
.await
@@ -3333,7 +3349,6 @@ mod tests {
dir.path(),
Arc::new(RealFs),
Default::default(),
- None,
&mut cx.to_async(),
)
.await
@@ -3467,7 +3482,6 @@ mod tests {
dir.path(),
Arc::new(RealFs),
Default::default(),
- None,
&mut cx.to_async(),
)
.await
@@ -3555,7 +3569,21 @@ mod tests {
#[gpui::test]
async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) {
- let (language_server, mut fake_lsp) = LanguageServer::fake(cx.background()).await;
+ simplelog::SimpleLogger::init(log::LevelFilter::Info, Default::default()).unwrap();
+
+ let (language_server_config, mut fake_server) =
+ LanguageServerConfig::fake(cx.background()).await;
+ let mut languages = LanguageRegistry::new();
+ languages.add(Arc::new(Language::new(
+ LanguageConfig {
+ name: "Rust".to_string(),
+ path_suffixes: vec!["rs".to_string()],
+ language_server: Some(language_server_config),
+ ..Default::default()
+ },
+ tree_sitter_rust::language(),
+ )));
+
let dir = temp_tree(json!({
"a.rs": "fn a() { A }",
"b.rs": "const y: i32 = 1",
@@ -3565,8 +3593,7 @@ mod tests {
Client::new(),
dir.path(),
Arc::new(RealFs),
- Default::default(),
- Some(language_server),
+ Arc::new(languages),
&mut cx.to_async(),
)
.await
@@ -3574,7 +3601,13 @@ mod tests {
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
- fake_lsp
+ // Cause worktree to start the fake language server
+ let _buffer = tree
+ .update(&mut cx, |tree, cx| tree.open_buffer("b.rs", cx))
+ .await
+ .unwrap();
+
+ fake_server
.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(),
version: None,
@@ -982,7 +982,10 @@ mod tests {
},
editor::{Editor, EditorSettings, Input},
fs::{FakeFs, Fs as _},
- language::{Diagnostic, LanguageRegistry, Point},
+ language::{
+ tree_sitter_rust, Diagnostic, Language, LanguageConfig, LanguageRegistry,
+ LanguageServerConfig, Point,
+ },
lsp,
people_panel::JoinWorktree,
project::{ProjectPath, Worktree},
@@ -1017,7 +1020,6 @@ mod tests {
"/a".as_ref(),
fs,
lang_registry.clone(),
- None,
&mut cx_a.to_async(),
)
.await
@@ -1126,7 +1128,6 @@ mod tests {
"/a".as_ref(),
fs,
lang_registry.clone(),
- None,
&mut cx_a.to_async(),
)
.await
@@ -1219,7 +1220,6 @@ mod tests {
"/a".as_ref(),
fs.clone(),
lang_registry.clone(),
- None,
&mut cx_a.to_async(),
)
.await
@@ -1356,7 +1356,6 @@ mod tests {
"/dir".as_ref(),
fs,
lang_registry.clone(),
- None,
&mut cx_a.to_async(),
)
.await
@@ -1441,7 +1440,6 @@ mod tests {
"/dir".as_ref(),
fs,
lang_registry.clone(),
- None,
&mut cx_a.to_async(),
)
.await
@@ -1508,7 +1506,6 @@ mod tests {
"/dir".as_ref(),
fs,
lang_registry.clone(),
- None,
&mut cx_a.to_async(),
)
.await
@@ -1570,7 +1567,6 @@ mod tests {
"/a".as_ref(),
fs,
lang_registry.clone(),
- None,
&mut cx_a.to_async(),
)
.await
@@ -1609,9 +1605,20 @@ mod tests {
mut cx_b: TestAppContext,
) {
cx_a.foreground().forbid_parking();
- let lang_registry = Arc::new(LanguageRegistry::new());
+ let (language_server_config, mut fake_language_server) =
+ LanguageServerConfig::fake(cx_a.background()).await;
+ let mut lang_registry = LanguageRegistry::new();
+ lang_registry.add(Arc::new(Language::new(
+ LanguageConfig {
+ name: "Rust".to_string(),
+ path_suffixes: vec!["rs".to_string()],
+ language_server: Some(language_server_config),
+ ..Default::default()
+ },
+ tree_sitter_rust::language(),
+ )));
- let (language_server, mut fake_lsp) = lsp::LanguageServer::fake(cx_a.background()).await;
+ let lang_registry = Arc::new(lang_registry);
// Connect to a server as 2 clients.
let mut server = TestServer::start().await;
@@ -1624,8 +1631,8 @@ mod tests {
"/a",
json!({
".zed.toml": r#"collaborators = ["user_b"]"#,
- "a.txt": "one two three",
- "b.txt": "b-contents",
+ "a.rs": "let one = two",
+ "other.rs": "",
}),
)
.await;
@@ -1634,7 +1641,6 @@ mod tests {
"/a".as_ref(),
fs,
lang_registry.clone(),
- Some(language_server),
&mut cx_a.to_async(),
)
.await
@@ -1647,21 +1653,33 @@ mod tests {
.await
.unwrap();
+ // Cause language server to start.
+ let _ = cx_a
+ .background()
+ .spawn(worktree_a.update(&mut cx_a, |worktree, cx| {
+ worktree.open_buffer("other.rs", cx)
+ }))
+ .await
+ .unwrap();
+
// Simulate a language server reporting errors for a file.
- fake_lsp
+ fake_language_server
.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
- uri: lsp::Url::from_file_path("/a/a.txt").unwrap(),
+ uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
version: None,
diagnostics: vec![
lsp::Diagnostic {
severity: Some(lsp::DiagnosticSeverity::ERROR),
- range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 3)),
+ range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
message: "message 1".to_string(),
..Default::default()
},
lsp::Diagnostic {
severity: Some(lsp::DiagnosticSeverity::WARNING),
- range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 13)),
+ range: lsp::Range::new(
+ lsp::Position::new(0, 10),
+ lsp::Position::new(0, 13),
+ ),
message: "message 2".to_string(),
..Default::default()
},
@@ -1682,7 +1700,7 @@ mod tests {
// Open the file with the errors.
let buffer_b = cx_b
.background()
- .spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.txt", cx)))
+ .spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.rs", cx)))
.await
.unwrap();
@@ -1693,14 +1711,14 @@ mod tests {
.collect::<Vec<_>>(),
&[
(
- Point::new(0, 0)..Point::new(0, 3),
+ Point::new(0, 4)..Point::new(0, 7),
&Diagnostic {
message: "message 1".to_string(),
severity: lsp::DiagnosticSeverity::ERROR,
}
),
(
- Point { row: 0, column: 8 }..Point { row: 0, column: 13 },
+ Point::new(0, 10)..Point::new(0, 13),
&Diagnostic {
severity: lsp::DiagnosticSeverity::WARNING,
message: "message 2".to_string()
@@ -2149,7 +2167,6 @@ mod tests {
"/a".as_ref(),
fs.clone(),
lang_registry.clone(),
- None,
&mut cx_a.to_async(),
)
.await
@@ -127,10 +127,16 @@ impl ItemView for Editor {
cx.spawn(|buffer, mut cx| async move {
save_as.await.map(|new_file| {
- let (language, language_server) = worktree.read_with(&cx, |worktree, _| {
- let language = worktree.languages().select_language(new_file.full_path());
- let language_server = worktree.language_server();
- (language.cloned(), language_server.cloned())
+ let (language, language_server) = worktree.update(&mut cx, |worktree, cx| {
+ let worktree = worktree.as_local_mut().unwrap();
+ let language = worktree
+ .languages()
+ .select_language(new_file.full_path())
+ .cloned();
+ let language_server = language
+ .as_ref()
+ .and_then(|language| worktree.ensure_language_server(language, cx));
+ (language, language_server.clone())
});
buffer.update(&mut cx, |buffer, cx| {
@@ -1,4 +1,4 @@
-pub use language::{Buffer, Diagnostic, Language, LanguageRegistry, Point};
+pub use language::*;
use rust_embed::RustEmbed;
use std::borrow::Cow;
use std::{str, sync::Arc};