Capture language server stderr during startup/init and log if failure
zed2 electric boogaloo
This commit is contained in:
parent
06c22206af
commit
8a0fb668f7
8 changed files with 54 additions and 27 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1836,6 +1836,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"lsp2",
|
"lsp2",
|
||||||
"node_runtime",
|
"node_runtime",
|
||||||
|
"parking_lot 0.11.2",
|
||||||
"rpc",
|
"rpc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
|
@ -5907,6 +5908,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"lsp2",
|
"lsp2",
|
||||||
"node_runtime",
|
"node_runtime",
|
||||||
|
"parking_lot 0.11.2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -36,6 +36,7 @@ serde.workspace = true
|
||||||
serde_derive.workspace = true
|
serde_derive.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
parking_lot.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
|
|
|
@ -17,6 +17,7 @@ use language2::{
|
||||||
};
|
};
|
||||||
use lsp2::{LanguageServer, LanguageServerBinary, LanguageServerId};
|
use lsp2::{LanguageServer, LanguageServerBinary, LanguageServerId};
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
|
use parking_lot::Mutex;
|
||||||
use request::StatusNotification;
|
use request::StatusNotification;
|
||||||
use settings2::SettingsStore;
|
use settings2::SettingsStore;
|
||||||
use smol::{fs, io::BufReader, stream::StreamExt};
|
use smol::{fs, io::BufReader, stream::StreamExt};
|
||||||
|
@ -394,8 +395,15 @@ impl Copilot {
|
||||||
path: node_path,
|
path: node_path,
|
||||||
arguments,
|
arguments,
|
||||||
};
|
};
|
||||||
let server =
|
|
||||||
LanguageServer::new(new_server_id, binary, Path::new("/"), None, cx.clone())?;
|
let server = LanguageServer::new(
|
||||||
|
Arc::new(Mutex::new(None)),
|
||||||
|
new_server_id,
|
||||||
|
binary,
|
||||||
|
Path::new("/"),
|
||||||
|
None,
|
||||||
|
cx.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
server
|
server
|
||||||
.on_notification::<StatusNotification, _>(
|
.on_notification::<StatusNotification, _>(
|
||||||
|
|
|
@ -667,7 +667,7 @@ struct LanguageRegistryState {
|
||||||
|
|
||||||
pub struct PendingLanguageServer {
|
pub struct PendingLanguageServer {
|
||||||
pub server_id: LanguageServerId,
|
pub server_id: LanguageServerId,
|
||||||
pub task: Task<Result<Option<lsp2::LanguageServer>>>,
|
pub task: Task<Result<lsp2::LanguageServer>>,
|
||||||
pub container_dir: Option<Arc<Path>>,
|
pub container_dir: Option<Arc<Path>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -906,6 +906,7 @@ impl LanguageRegistry {
|
||||||
|
|
||||||
pub fn create_pending_language_server(
|
pub fn create_pending_language_server(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
|
stderr_capture: Arc<Mutex<Option<String>>>,
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
adapter: Arc<CachedLspAdapter>,
|
adapter: Arc<CachedLspAdapter>,
|
||||||
root_path: Arc<Path>,
|
root_path: Arc<Path>,
|
||||||
|
@ -945,7 +946,7 @@ impl LanguageRegistry {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
Ok(Some(server))
|
Ok(server)
|
||||||
});
|
});
|
||||||
|
|
||||||
return Some(PendingLanguageServer {
|
return Some(PendingLanguageServer {
|
||||||
|
@ -996,24 +997,23 @@ impl LanguageRegistry {
|
||||||
})
|
})
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
let binary = match entry.await.log_err() {
|
let binary = match entry.await {
|
||||||
Some(binary) => binary,
|
Ok(binary) => binary,
|
||||||
None => return Ok(None),
|
Err(err) => anyhow::bail!("{err}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
|
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
|
||||||
if task.await.log_err().is_none() {
|
task.await?;
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(lsp2::LanguageServer::new(
|
lsp2::LanguageServer::new(
|
||||||
|
stderr_capture,
|
||||||
server_id,
|
server_id,
|
||||||
binary,
|
binary,
|
||||||
&root_path,
|
&root_path,
|
||||||
adapter.code_action_kinds(),
|
adapter.code_action_kinds(),
|
||||||
cx,
|
cx,
|
||||||
)?))
|
)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -136,6 +136,7 @@ struct Error {
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
stderr_capture: Arc<Mutex<Option<String>>>,
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
binary: LanguageServerBinary,
|
binary: LanguageServerBinary,
|
||||||
root_path: &Path,
|
root_path: &Path,
|
||||||
|
@ -165,6 +166,7 @@ impl LanguageServer {
|
||||||
stdin,
|
stdin,
|
||||||
stdout,
|
stdout,
|
||||||
Some(stderr),
|
Some(stderr),
|
||||||
|
stderr_capture,
|
||||||
Some(server),
|
Some(server),
|
||||||
root_path,
|
root_path,
|
||||||
code_action_kinds,
|
code_action_kinds,
|
||||||
|
@ -197,6 +199,7 @@ impl LanguageServer {
|
||||||
stdin: Stdin,
|
stdin: Stdin,
|
||||||
stdout: Stdout,
|
stdout: Stdout,
|
||||||
stderr: Option<Stderr>,
|
stderr: Option<Stderr>,
|
||||||
|
stderr_capture: Arc<Mutex<Option<String>>>,
|
||||||
server: Option<Child>,
|
server: Option<Child>,
|
||||||
root_path: &Path,
|
root_path: &Path,
|
||||||
code_action_kinds: Option<Vec<CodeActionKind>>,
|
code_action_kinds: Option<Vec<CodeActionKind>>,
|
||||||
|
@ -237,7 +240,8 @@ impl LanguageServer {
|
||||||
let stderr_input_task = stderr
|
let stderr_input_task = stderr
|
||||||
.map(|stderr| {
|
.map(|stderr| {
|
||||||
let io_handlers = io_handlers.clone();
|
let io_handlers = io_handlers.clone();
|
||||||
cx.spawn(|_| Self::handle_stderr(stderr, io_handlers).log_err())
|
let stderr_captures = stderr_captures.clone();
|
||||||
|
cx.spawn(|_| Self::handle_stderr(stderr, io_handlers, stderr_captures).log_err())
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| Task::Ready(Some(None)));
|
.unwrap_or_else(|| Task::Ready(Some(None)));
|
||||||
let input_task = cx.spawn(|_| async move {
|
let input_task = cx.spawn(|_| async move {
|
||||||
|
@ -360,12 +364,14 @@ impl LanguageServer {
|
||||||
async fn handle_stderr<Stderr>(
|
async fn handle_stderr<Stderr>(
|
||||||
stderr: Stderr,
|
stderr: Stderr,
|
||||||
io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
|
io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
|
||||||
|
stderr_capture: Arc<Mutex<Option<String>>>,
|
||||||
) -> anyhow::Result<()>
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
Stderr: AsyncRead + Unpin + Send + 'static,
|
Stderr: AsyncRead + Unpin + Send + 'static,
|
||||||
{
|
{
|
||||||
let mut stderr = BufReader::new(stderr);
|
let mut stderr = BufReader::new(stderr);
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
stderr.read_until(b'\n', &mut buffer).await?;
|
stderr.read_until(b'\n', &mut buffer).await?;
|
||||||
|
@ -374,6 +380,10 @@ impl LanguageServer {
|
||||||
for handler in io_handlers.lock().values_mut() {
|
for handler in io_handlers.lock().values_mut() {
|
||||||
handler(IoKind::StdErr, message);
|
handler(IoKind::StdErr, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(stderr) = stderr_capture.lock().as_mut() {
|
||||||
|
stderr.push_str(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't starve the main thread when receiving lots of messages at once.
|
// Don't starve the main thread when receiving lots of messages at once.
|
||||||
|
@ -933,6 +943,7 @@ impl LanguageServer {
|
||||||
stdin_writer,
|
stdin_writer,
|
||||||
stdout_reader,
|
stdout_reader,
|
||||||
None::<async_pipe::PipeReader>,
|
None::<async_pipe::PipeReader>,
|
||||||
|
Arc::new(Mutex::new(None)),
|
||||||
None,
|
None,
|
||||||
Path::new("/"),
|
Path::new("/"),
|
||||||
None,
|
None,
|
||||||
|
@ -945,6 +956,7 @@ impl LanguageServer {
|
||||||
stdout_writer,
|
stdout_writer,
|
||||||
stdin_reader,
|
stdin_reader,
|
||||||
None::<async_pipe::PipeReader>,
|
None::<async_pipe::PipeReader>,
|
||||||
|
Arc::new(Mutex::new(None)),
|
||||||
None,
|
None,
|
||||||
Path::new("/"),
|
Path::new("/"),
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -27,6 +27,7 @@ serde_derive.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
parking_lot.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
language2 = { path = "../language2", features = ["test-support"] }
|
language2 = { path = "../language2", features = ["test-support"] }
|
||||||
|
|
|
@ -210,6 +210,7 @@ impl Prettier {
|
||||||
.spawn(async move { node.binary_path().await })
|
.spawn(async move { node.binary_path().await })
|
||||||
.await?;
|
.await?;
|
||||||
let server = LanguageServer::new(
|
let server = LanguageServer::new(
|
||||||
|
Arc::new(parking_lot::Mutex::new(None)),
|
||||||
server_id,
|
server_id,
|
||||||
LanguageServerBinary {
|
LanguageServerBinary {
|
||||||
path: node_path,
|
path: node_path,
|
||||||
|
|
|
@ -52,6 +52,7 @@ use lsp2::{
|
||||||
};
|
};
|
||||||
use lsp_command::*;
|
use lsp_command::*;
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
|
use parking_lot::Mutex;
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use prettier2::{LocateStart, Prettier, PRETTIER_SERVER_FILE, PRETTIER_SERVER_JS};
|
use prettier2::{LocateStart, Prettier, PRETTIER_SERVER_FILE, PRETTIER_SERVER_JS};
|
||||||
use project_settings::{LspSettings, ProjectSettings};
|
use project_settings::{LspSettings, ProjectSettings};
|
||||||
|
@ -2778,7 +2779,9 @@ impl Project {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let stderr_capture = Arc::new(Mutex::new(Some(String::new())));
|
||||||
let pending_server = match self.languages.create_pending_language_server(
|
let pending_server = match self.languages.create_pending_language_server(
|
||||||
|
stderr_capture.clone(),
|
||||||
language.clone(),
|
language.clone(),
|
||||||
adapter.clone(),
|
adapter.clone(),
|
||||||
worktree_path,
|
worktree_path,
|
||||||
|
@ -2824,10 +2827,14 @@ impl Project {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(server) => server,
|
Ok(server) => {
|
||||||
|
stderr_capture.lock().take();
|
||||||
|
server
|
||||||
|
}
|
||||||
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!("failed to start language server {:?}: {}", server_name, err);
|
log::error!("failed to start language server {:?}: {}", server_name, err);
|
||||||
|
log::error!("server stderr: {:?}", stderr_capture.lock().take());
|
||||||
|
|
||||||
if let Some(this) = this.upgrade() {
|
if let Some(this) = this.upgrade() {
|
||||||
if let Some(container_dir) = container_dir {
|
if let Some(container_dir) = container_dir {
|
||||||
|
@ -2931,19 +2938,16 @@ impl Project {
|
||||||
key: (WorktreeId, LanguageServerName),
|
key: (WorktreeId, LanguageServerName),
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<Option<Arc<LanguageServer>>> {
|
) -> Result<Option<Arc<LanguageServer>>> {
|
||||||
let setup = Self::setup_pending_language_server(
|
let language_server = Self::setup_pending_language_server(
|
||||||
this.clone(),
|
this.clone(),
|
||||||
initialization_options,
|
initialization_options,
|
||||||
pending_server,
|
pending_server,
|
||||||
adapter.clone(),
|
adapter.clone(),
|
||||||
server_id,
|
server_id,
|
||||||
cx,
|
cx,
|
||||||
);
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let language_server = match setup.await? {
|
|
||||||
Some(language_server) => language_server,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
let this = match this.upgrade() {
|
let this = match this.upgrade() {
|
||||||
Some(this) => this,
|
Some(this) => this,
|
||||||
None => return Err(anyhow!("failed to upgrade project handle")),
|
None => return Err(anyhow!("failed to upgrade project handle")),
|
||||||
|
@ -2960,7 +2964,7 @@ impl Project {
|
||||||
)
|
)
|
||||||
})??;
|
})??;
|
||||||
|
|
||||||
Ok(Some(language_server))
|
Ok(language_server)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup_pending_language_server(
|
async fn setup_pending_language_server(
|
||||||
|
@ -2970,12 +2974,9 @@ impl Project {
|
||||||
adapter: Arc<CachedLspAdapter>,
|
adapter: Arc<CachedLspAdapter>,
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<Option<Arc<LanguageServer>>> {
|
) -> Result<Arc<LanguageServer>> {
|
||||||
let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx))?.await;
|
let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx))?.await;
|
||||||
let language_server = match pending_server.task.await? {
|
let language_server = pending_server.task.await?;
|
||||||
Some(server) => server,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<lsp2::notification::PublishDiagnostics, _>({
|
.on_notification::<lsp2::notification::PublishDiagnostics, _>({
|
||||||
|
@ -3050,6 +3051,7 @@ impl Project {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_request::<lsp2::request::RegisterCapability, _, _>({
|
.on_request::<lsp2::request::RegisterCapability, _, _>({
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
|
@ -3138,7 +3140,7 @@ impl Project {
|
||||||
)
|
)
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
Ok(Some(language_server))
|
Ok(language_server)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_newly_running_language_server(
|
fn insert_newly_running_language_server(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue