Move adapters to remote (#18359)

Release Notes:

- ssh remoting: run LSP Adapters on host

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Conrad Irwin 2024-09-25 16:29:04 -06:00 committed by GitHub
parent 40408e731e
commit 64532e94e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 76 additions and 548 deletions

1
Cargo.lock generated
View file

@ -9122,6 +9122,7 @@ dependencies = [
"gpui",
"http_client",
"language",
"languages",
"log",
"lsp",
"node_runtime",

View file

@ -10,6 +10,25 @@ workspace = true
[features]
test-support = []
load-grammars = [
"tree-sitter-bash",
"tree-sitter-c",
"tree-sitter-cpp",
"tree-sitter-css",
"tree-sitter-go",
"tree-sitter-go-mod",
"tree-sitter-gowork",
"tree-sitter-jsdoc",
"tree-sitter-json",
"tree-sitter-md",
"protols-tree-sitter-proto",
"tree-sitter-python",
"tree-sitter-regex",
"tree-sitter-rust",
"tree-sitter-typescript",
"tree-sitter-yaml",
"tree-sitter"
]
[dependencies]
anyhow.workspace = true
@ -36,25 +55,26 @@ settings.workspace = true
smol.workspace = true
task.workspace = true
toml.workspace = true
tree-sitter-bash.workspace = true
tree-sitter-c.workspace = true
tree-sitter-cpp.workspace = true
tree-sitter-css.workspace = true
tree-sitter-go.workspace = true
tree-sitter-go-mod.workspace = true
tree-sitter-gowork.workspace = true
tree-sitter-jsdoc.workspace = true
tree-sitter-json.workspace = true
tree-sitter-md.workspace = true
protols-tree-sitter-proto.workspace = true
tree-sitter-python.workspace = true
tree-sitter-regex.workspace = true
tree-sitter-rust.workspace = true
tree-sitter-typescript.workspace = true
tree-sitter-yaml.workspace = true
tree-sitter.workspace = true
util.workspace = true
tree-sitter-bash = {workspace = true, optional = true}
tree-sitter-c = {workspace = true, optional = true}
tree-sitter-cpp = {workspace = true, optional = true}
tree-sitter-css = {workspace = true, optional = true}
tree-sitter-go = {workspace = true, optional = true}
tree-sitter-go-mod = {workspace = true, optional = true}
tree-sitter-gowork = {workspace = true, optional = true}
tree-sitter-jsdoc = {workspace = true, optional = true}
tree-sitter-json = {workspace = true, optional = true}
tree-sitter-md = {workspace = true, optional = true}
protols-tree-sitter-proto = {workspace = true, optional = true}
tree-sitter-python = {workspace = true, optional = true}
tree-sitter-regex = {workspace = true, optional = true}
tree-sitter-rust = {workspace = true, optional = true}
tree-sitter-typescript = {workspace = true, optional = true}
tree-sitter-yaml = {workspace = true, optional = true}
tree-sitter = {workspace = true, optional = true}
[dev-dependencies]
text.workspace = true
theme = { workspace = true, features = ["test-support"] }

View file

@ -31,6 +31,7 @@ mod yaml;
struct LanguageDir;
pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mut AppContext) {
#[cfg(feature = "load-grammars")]
languages.register_native_grammars([
("bash", tree_sitter_bash::LANGUAGE),
("c", tree_sitter_c::LANGUAGE),
@ -282,9 +283,21 @@ fn load_config(name: &str) -> LanguageConfig {
)
.unwrap();
::toml::from_str(&config_toml)
#[allow(unused_mut)]
let mut config: LanguageConfig = ::toml::from_str(&config_toml)
.with_context(|| format!("failed to load config.toml for language {name:?}"))
.unwrap()
.unwrap();
#[cfg(not(feature = "load-grammars"))]
{
config = LanguageConfig {
name: config.name,
matcher: config.matcher,
..Default::default()
}
}
config
}
fn load_queries(name: &str) -> LanguageQueries {

View file

@ -36,10 +36,10 @@ use language::{
markdown, point_to_lsp, prepare_completion_documentation,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageConfig,
LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerBinaryStatus,
LanguageServerName, LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16,
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageName,
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName, LocalFile, LspAdapter,
LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
Unclipped,
};
use lsp::{
CodeActionKind, CompletionContext, DiagnosticSeverity, DiagnosticTag,
@ -53,7 +53,7 @@ use parking_lot::{Mutex, RwLock};
use postage::watch;
use rand::prelude::*;
use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient};
use rpc::AnyProtoClient;
use serde::Serialize;
use settings::{Settings, SettingsLocation, SettingsStore};
use sha2::{Digest, Sha256};
@ -644,16 +644,15 @@ pub struct RemoteLspStore {
impl RemoteLspStore {}
pub struct SshLspStore {
upstream_client: AnyProtoClient,
current_lsp_settings: HashMap<LanguageServerName, LspSettings>,
}
// pub struct SshLspStore {
// upstream_client: AnyProtoClient,
// current_lsp_settings: HashMap<LanguageServerName, LspSettings>,
// }
#[allow(clippy::large_enum_variant)]
pub enum LspStoreMode {
Local(LocalLspStore), // ssh host and collab host
Remote(RemoteLspStore), // collab guest
Ssh(SshLspStore), // ssh client
}
impl LspStoreMode {
@ -661,10 +660,6 @@ impl LspStoreMode {
matches!(self, LspStoreMode::Local(_))
}
fn is_ssh(&self) -> bool {
matches!(self, LspStoreMode::Ssh(_))
}
fn is_remote(&self) -> bool {
matches!(self, LspStoreMode::Remote(_))
}
@ -787,13 +782,6 @@ impl LspStore {
}
}
pub fn as_ssh(&self) -> Option<&SshLspStore> {
match &self.mode {
LspStoreMode::Ssh(ssh_lsp_store) => Some(ssh_lsp_store),
_ => None,
}
}
pub fn as_local(&self) -> Option<&LocalLspStore> {
match &self.mode {
LspStoreMode::Local(local_lsp_store) => Some(local_lsp_store),
@ -810,9 +798,6 @@ impl LspStore {
pub fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> {
match &self.mode {
LspStoreMode::Ssh(SshLspStore {
upstream_client, ..
}) => Some((upstream_client.clone(), SSH_PROJECT_ID)),
LspStoreMode::Remote(RemoteLspStore {
upstream_client,
upstream_project_id,
@ -827,11 +812,7 @@ impl LspStore {
new_settings: HashMap<LanguageServerName, LspSettings>,
) -> Option<HashMap<LanguageServerName, LspSettings>> {
match &mut self.mode {
LspStoreMode::Ssh(SshLspStore {
current_lsp_settings,
..
})
| LspStoreMode::Local(LocalLspStore {
LspStoreMode::Local(LocalLspStore {
current_lsp_settings,
..
}) => {
@ -919,43 +900,6 @@ impl LspStore {
})
}
pub fn new_ssh(
buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>,
languages: Arc<LanguageRegistry>,
upstream_client: AnyProtoClient,
cx: &mut ModelContext<Self>,
) -> Self {
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
.detach();
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
.detach();
cx.observe_global::<SettingsStore>(Self::on_settings_changed)
.detach();
Self {
mode: LspStoreMode::Ssh(SshLspStore {
upstream_client,
current_lsp_settings: Default::default(),
}),
downstream_client: None,
buffer_store,
worktree_store,
languages: languages.clone(),
language_server_ids: Default::default(),
language_server_statuses: Default::default(),
nonce: StdRng::from_entropy().gen(),
buffer_snapshots: Default::default(),
next_diagnostic_group_id: Default::default(),
diagnostic_summaries: Default::default(),
diagnostics: Default::default(),
active_entry: None,
_maintain_workspace_config: Self::maintain_workspace_config(cx),
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
}
}
pub fn new_remote(
buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>,
@ -3697,11 +3641,11 @@ impl LspStore {
mut cx: AsyncAppContext,
) -> Result<proto::MultiLspQueryResponse> {
let response_from_ssh = this.update(&mut cx, |this, _| {
let ssh = this.as_ssh()?;
let (upstream_client, project_id) = this.upstream_client()?;
let mut payload = envelope.payload.clone();
payload.project_id = SSH_PROJECT_ID;
payload.project_id = project_id;
Some(ssh.upstream_client.request(payload))
Some(upstream_client.request(payload))
})?;
if let Some(response_from_ssh) = response_from_ssh {
return response_from_ssh.await;
@ -5009,165 +4953,6 @@ impl LspStore {
Ok(proto::Ack {})
}
pub async fn handle_create_language_server(
this: Model<Self>,
envelope: TypedEnvelope<proto::CreateLanguageServer>,
mut cx: AsyncAppContext,
) -> Result<proto::Ack> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let server_name = LanguageServerName::from_proto(envelope.payload.name);
let binary = envelope
.payload
.binary
.ok_or_else(|| anyhow!("missing binary"))?;
let binary = LanguageServerBinary {
path: PathBuf::from(binary.path),
env: None,
arguments: binary.arguments.into_iter().map(Into::into).collect(),
};
let language = envelope
.payload
.language
.ok_or_else(|| anyhow!("missing language"))?;
let language_name = LanguageName::from_proto(language.name);
let matcher: LanguageMatcher = serde_json::from_str(&language.matcher)?;
this.update(&mut cx, |this, cx| {
let Some(worktree) = this
.worktree_store
.read(cx)
.worktree_for_id(worktree_id, cx)
else {
return Err(anyhow!("worktree not found"));
};
this.languages
.register_language(language_name.clone(), None, matcher.clone(), {
let language_name = language_name.clone();
move || {
Ok((
LanguageConfig {
name: language_name.clone(),
matcher: matcher.clone(),
..Default::default()
},
Default::default(),
Default::default(),
))
}
});
cx.background_executor()
.spawn(this.languages.language_for_name(language_name.0.as_ref()))
.detach();
// host
let adapter = this.languages.get_or_register_lsp_adapter(
language_name.clone(),
server_name.clone(),
|| {
Arc::new(SshLspAdapter::new(
server_name,
binary,
envelope.payload.initialization_options,
envelope.payload.code_action_kinds,
))
},
);
this.start_language_server(&worktree, adapter, language_name, cx);
Ok(())
})??;
Ok(proto::Ack {})
}
pub async fn handle_which_command(
this: Model<Self>,
envelope: TypedEnvelope<proto::WhichCommand>,
mut cx: AsyncAppContext,
) -> Result<proto::WhichCommandResponse> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let command = PathBuf::from(envelope.payload.command);
let response = this
.update(&mut cx, |this, cx| {
let worktree = this.worktree_for_id(worktree_id, cx)?;
let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
anyhow::Ok(
cx.spawn(|_, _| async move { delegate.which(command.as_os_str()).await }),
)
})??
.await;
Ok(proto::WhichCommandResponse {
path: response.map(|path| path.to_string_lossy().to_string()),
})
}
pub async fn handle_shell_env(
this: Model<Self>,
envelope: TypedEnvelope<proto::ShellEnv>,
mut cx: AsyncAppContext,
) -> Result<proto::ShellEnvResponse> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let response = this
.update(&mut cx, |this, cx| {
let worktree = this.worktree_for_id(worktree_id, cx)?;
let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
anyhow::Ok(cx.spawn(|_, _| async move { delegate.shell_env().await }))
})??
.await;
Ok(proto::ShellEnvResponse {
env: response.into_iter().collect(),
})
}
pub async fn handle_try_exec(
this: Model<Self>,
envelope: TypedEnvelope<proto::TryExec>,
mut cx: AsyncAppContext,
) -> Result<proto::Ack> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let binary = envelope
.payload
.binary
.ok_or_else(|| anyhow!("missing binary"))?;
let binary = LanguageServerBinary {
path: PathBuf::from(binary.path),
env: None,
arguments: binary.arguments.into_iter().map(Into::into).collect(),
};
this.update(&mut cx, |this, cx| {
let worktree = this.worktree_for_id(worktree_id, cx)?;
let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
anyhow::Ok(cx.spawn(|_, _| async move { delegate.try_exec(binary).await }))
})??
.await?;
Ok(proto::Ack {})
}
pub async fn handle_read_text_file(
this: Model<Self>,
envelope: TypedEnvelope<proto::ReadTextFile>,
mut cx: AsyncAppContext,
) -> Result<proto::ReadTextFileResponse> {
let path = envelope
.payload
.path
.ok_or_else(|| anyhow!("missing path"))?;
let worktree_id = WorktreeId::from_proto(path.worktree_id);
let path = PathBuf::from(path.path);
let response = this
.update(&mut cx, |this, cx| {
let worktree = this.worktree_for_id(worktree_id, cx)?;
let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
anyhow::Ok(cx.spawn(|_, _| async move { delegate.read_text_file(path).await }))
})??
.await?;
Ok(proto::ReadTextFileResponse { text: response })
}
async fn handle_apply_additional_edits_for_completion(
this: Model<Self>,
envelope: TypedEnvelope<proto::ApplyCompletionAdditionalEdits>,
@ -5388,89 +5173,6 @@ impl LspStore {
.reorder_language_servers(&language, enabled_lsp_adapters);
}
fn start_language_server_on_ssh_host(
&mut self,
worktree: &Model<Worktree>,
adapter: Arc<CachedLspAdapter>,
language: LanguageName,
cx: &mut ModelContext<Self>,
) {
let ssh = self.as_ssh().unwrap();
let delegate = Arc::new(SshLspAdapterDelegate {
lsp_store: cx.handle().downgrade(),
worktree: worktree.read(cx).snapshot(),
upstream_client: ssh.upstream_client.clone(),
language_registry: self.languages.clone(),
}) as Arc<dyn LspAdapterDelegate>;
let Some((upstream_client, project_id)) = self.upstream_client() else {
return;
};
let worktree_id = worktree.read(cx).id().to_proto();
let name = adapter.name().to_string();
let Some(available_language) = self.languages.available_language_for_name(&language) else {
log::error!("failed to find available language {language}");
return;
};
let user_binary_task =
self.get_language_server_binary(adapter.clone(), delegate.clone(), false, cx);
let task = cx.spawn(|_, _| async move {
let binary = user_binary_task.await?;
let name = adapter.name();
let code_action_kinds = adapter
.adapter
.code_action_kinds()
.map(|kinds| serde_json::to_string(&kinds))
.transpose()?;
let get_options = adapter.adapter.clone().initialization_options(&delegate);
let initialization_options = get_options
.await?
.map(|options| serde_json::to_string(&options))
.transpose()?;
let language_server_command = proto::LanguageServerCommand {
path: binary.path.to_string_lossy().to_string(),
arguments: binary
.arguments
.iter()
.map(|args| args.to_string_lossy().to_string())
.collect(),
env: binary.env.unwrap_or_default().into_iter().collect(),
};
upstream_client
.request(proto::CreateLanguageServer {
project_id,
worktree_id,
name: name.0.to_string(),
binary: Some(language_server_command),
initialization_options,
code_action_kinds,
language: Some(proto::AvailableLanguage {
name: language.to_proto(),
matcher: serde_json::to_string(&available_language.matcher())?,
}),
})
.await
});
cx.spawn(|this, mut cx| async move {
if let Err(e) = task.await {
this.update(&mut cx, |_this, cx| {
cx.emit(LspStoreEvent::Notification(format!(
"failed to start {}: {}",
name, e
)))
})
.ok();
}
})
.detach();
}
fn get_language_server_binary(
&self,
adapter: Arc<CachedLspAdapter>,
@ -5558,11 +5260,6 @@ impl LspStore {
return;
}
if self.mode.is_ssh() {
self.start_language_server_on_ssh_host(worktree_handle, adapter, language, cx);
return;
}
let project_settings = ProjectSettings::get(
Some(SettingsLocation {
worktree_id,
@ -5852,9 +5549,6 @@ impl LspStore {
} else {
Task::ready(Vec::new())
}
} else if self.mode.is_ssh() {
// TODO ssh
Task::ready(Vec::new())
} else {
Task::ready(Vec::new())
}
@ -7905,116 +7599,6 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
}
}
struct SshLspAdapterDelegate {
lsp_store: WeakModel<LspStore>,
worktree: worktree::Snapshot,
upstream_client: AnyProtoClient,
language_registry: Arc<LanguageRegistry>,
}
#[async_trait]
impl LspAdapterDelegate for SshLspAdapterDelegate {
fn show_notification(&self, message: &str, cx: &mut AppContext) {
self.lsp_store
.update(cx, |_, cx| {
cx.emit(LspStoreEvent::Notification(message.to_owned()))
})
.ok();
}
async fn npm_package_installed_version(
&self,
_package_name: &str,
) -> Result<Option<(PathBuf, String)>> {
Ok(None)
}
fn http_client(&self) -> Arc<dyn HttpClient> {
Arc::new(BlockedHttpClient)
}
fn worktree_id(&self) -> WorktreeId {
self.worktree.id()
}
fn worktree_root_path(&self) -> &Path {
self.worktree.abs_path().as_ref()
}
async fn shell_env(&self) -> HashMap<String, String> {
use rpc::proto::SSH_PROJECT_ID;
self.upstream_client
.request(proto::ShellEnv {
project_id: SSH_PROJECT_ID,
worktree_id: self.worktree_id().to_proto(),
})
.await
.map(|response| response.env.into_iter().collect())
.unwrap_or_default()
}
async fn which(&self, command: &OsStr) -> Option<PathBuf> {
use rpc::proto::SSH_PROJECT_ID;
self.upstream_client
.request(proto::WhichCommand {
project_id: SSH_PROJECT_ID,
worktree_id: self.worktree_id().to_proto(),
command: command.to_string_lossy().to_string(),
})
.await
.log_err()
.and_then(|response| response.path)
.map(PathBuf::from)
}
async fn try_exec(&self, command: LanguageServerBinary) -> Result<()> {
self.upstream_client
.request(proto::TryExec {
project_id: rpc::proto::SSH_PROJECT_ID,
worktree_id: self.worktree.id().to_proto(),
binary: Some(proto::LanguageServerCommand {
path: command.path.to_string_lossy().to_string(),
arguments: command
.arguments
.into_iter()
.map(|s| s.to_string_lossy().to_string())
.collect(),
env: command.env.unwrap_or_default().into_iter().collect(),
}),
})
.await?;
Ok(())
}
async fn language_server_download_dir(&self, _: &LanguageServerName) -> Option<Arc<Path>> {
None
}
fn update_status(
&self,
server_name: LanguageServerName,
status: language::LanguageServerBinaryStatus,
) {
self.language_registry
.update_lsp_status(server_name, status);
}
async fn read_text_file(&self, path: PathBuf) -> Result<String> {
self.upstream_client
.request(proto::ReadTextFile {
project_id: rpc::proto::SSH_PROJECT_ID,
path: Some(proto::ProjectPath {
worktree_id: self.worktree.id().to_proto(),
path: path.to_string_lossy().to_string(),
}),
})
.await
.map(|r| r.text)
}
}
async fn populate_labels_for_symbols(
symbols: Vec<CoreSymbol>,
language_registry: &Arc<LanguageRegistry>,

View file

@ -706,11 +706,12 @@ impl Project {
let environment = ProjectEnvironment::new(&worktree_store, None, cx);
let lsp_store = cx.new_model(|cx| {
LspStore::new_ssh(
LspStore::new_remote(
buffer_store.clone(),
worktree_store.clone(),
languages.clone(),
ssh.clone().into(),
SSH_PROJECT_ID,
cx,
)
});

View file

@ -283,18 +283,6 @@ message Envelope {
CloseBuffer close_buffer = 245;
UpdateUserSettings update_user_settings = 246;
CreateLanguageServer create_language_server = 247;
WhichCommand which_command = 248;
WhichCommandResponse which_command_response = 249;
ShellEnv shell_env = 250;
ShellEnvResponse shell_env_response = 251;
TryExec try_exec = 252;
ReadTextFile read_text_file = 253;
ReadTextFileResponse read_text_file_response = 254;
CheckFileExists check_file_exists = 255;
CheckFileExistsResponse check_file_exists_response = 256; // current max
}
@ -302,6 +290,7 @@ message Envelope {
reserved 158 to 161;
reserved 166 to 169;
reserved 224 to 229;
reserved 247 to 254;
}
// Messages
@ -2517,67 +2506,6 @@ message UpdateUserSettings {
string content = 2;
}
message LanguageServerCommand {
string path = 1;
repeated string arguments = 2;
map<string, string> env = 3;
}
message AvailableLanguage {
string name = 7;
string matcher = 8;
}
message CreateLanguageServer {
uint64 project_id = 1;
uint64 worktree_id = 2;
string name = 3;
LanguageServerCommand binary = 4;
optional string initialization_options = 5;
optional string code_action_kinds = 6;
AvailableLanguage language = 7;
}
message WhichCommand {
uint64 project_id = 1;
uint64 worktree_id = 2;
string command = 3;
}
message WhichCommandResponse {
optional string path = 1;
}
message ShellEnv {
uint64 project_id = 1;
uint64 worktree_id = 2;
}
message ShellEnvResponse {
map<string, string> env = 1;
}
message ReadTextFile {
uint64 project_id = 1;
ProjectPath path = 2;
}
message ReadTextFileResponse {
string text = 1;
}
message TryExec {
uint64 project_id = 1;
uint64 worktree_id = 2;
LanguageServerCommand binary = 3;
}
message TryExecResponse {
string text = 1;
}
message CheckFileExists {
uint64 project_id = 1;
string path = 2;

View file

@ -365,14 +365,6 @@ messages!(
(FindSearchCandidatesResponse, Background),
(CloseBuffer, Foreground),
(UpdateUserSettings, Foreground),
(CreateLanguageServer, Foreground),
(WhichCommand, Foreground),
(WhichCommandResponse, Foreground),
(ShellEnv, Foreground),
(ShellEnvResponse, Foreground),
(TryExec, Foreground),
(ReadTextFile, Foreground),
(ReadTextFileResponse, Foreground),
(CheckFileExists, Background),
(CheckFileExistsResponse, Background)
);
@ -498,11 +490,6 @@ request_messages!(
(SynchronizeContexts, SynchronizeContextsResponse),
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
(AddWorktree, AddWorktreeResponse),
(CreateLanguageServer, Ack),
(WhichCommand, WhichCommandResponse),
(ShellEnv, ShellEnvResponse),
(ReadTextFile, ReadTextFileResponse),
(TryExec, Ack),
(CheckFileExists, CheckFileExistsResponse)
);
@ -577,11 +564,6 @@ entity_messages!(
SynchronizeContexts,
LspExtSwitchSourceHeader,
UpdateUserSettings,
CreateLanguageServer,
WhichCommand,
ShellEnv,
TryExec,
ReadTextFile,
CheckFileExists,
);

View file

@ -39,6 +39,7 @@ shellexpand.workspace = true
smol.workspace = true
worktree.workspace = true
language.workspace = true
languages.workspace = true
util.workspace = true
[dev-dependencies]

View file

@ -44,6 +44,10 @@ impl HeadlessProject {
pub fn new(session: Arc<SshSession>, fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
let node_runtime = NodeRuntime::unavailable();
languages::init(languages.clone(), node_runtime.clone(), cx);
let worktree_store = cx.new_model(|cx| {
let mut store = WorktreeStore::local(true, fs.clone());
store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
@ -56,7 +60,7 @@ impl HeadlessProject {
});
let prettier_store = cx.new_model(|cx| {
PrettierStore::new(
NodeRuntime::unavailable(),
node_runtime,
fs.clone(),
languages.clone(),
worktree_store.clone(),
@ -116,12 +120,6 @@ impl HeadlessProject {
client.add_model_request_handler(BufferStore::handle_update_buffer);
client.add_model_message_handler(BufferStore::handle_close_buffer);
client.add_model_request_handler(LspStore::handle_create_language_server);
client.add_model_request_handler(LspStore::handle_which_command);
client.add_model_request_handler(LspStore::handle_shell_env);
client.add_model_request_handler(LspStore::handle_try_exec);
client.add_model_request_handler(LspStore::handle_read_text_file);
BufferStore::init(&client);
WorktreeStore::init(&client);
SettingsObserver::init(&client);

View file

@ -64,7 +64,7 @@ language.workspace = true
language_model.workspace = true
language_selector.workspace = true
language_tools.workspace = true
languages.workspace = true
languages = {workspace = true, features = ["load-grammars"] }
libc.workspace = true
log.workspace = true
markdown_preview.workspace = true