use std::path::{Path, PathBuf};
use strop_picker::{Item, Kind, Payload};
use super::super::Editor;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ProjectStatusKind {
Ambiguous,
Attached,
NoServer,
Cancelled,
TrustRequired,
TrustError,
NotExecutable,
SpawnFailed,
RemoteIo,
}
impl ProjectStatusKind {
pub(crate) fn from_decision(decision: &super::super::lsp::attach::AttachDecision) -> Self {
use super::super::lsp::attach::AttachDecision as D;
match decision {
D::Attached => Self::Attached,
D::NoServer => Self::NoServer,
D::Cancelled => Self::Cancelled,
D::TrustRequired { .. } => Self::TrustRequired,
D::TrustError { .. } => Self::TrustError,
D::NotExecutable { .. } => Self::NotExecutable,
D::SpawnFailed { .. } => Self::SpawnFailed,
D::RemoteIo { .. } => Self::RemoteIo,
}
}
fn healthy(self) -> bool {
matches!(self, Self::Attached | Self::Cancelled)
}
fn badge(self) -> &'static str {
match self {
Self::Ambiguous => "cold",
Self::Attached | Self::Cancelled => "",
Self::NoServer => "no srv",
Self::TrustRequired => "trust",
Self::TrustError => "trust!",
Self::NotExecutable => "no exec",
Self::SpawnFailed => "spawn",
Self::RemoteIo => "remote",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ProjectStatus {
pub kind: ProjectStatusKind,
pub reason: String,
}
impl ProjectStatus {
pub(crate) fn from_decision(
decision: &super::super::lsp::attach::AttachDecision,
name: &str,
) -> Self {
let reason = match decision {
super::super::lsp::attach::AttachDecision::NoServer => {
format!("no language server for {name}")
}
other => super::super::explain::explain_decision(other),
};
Self {
kind: ProjectStatusKind::from_decision(decision),
reason,
}
}
pub(crate) fn ambiguous(marker: &str) -> Self {
Self {
kind: ProjectStatusKind::Ambiguous,
reason: format!(
"{marker} is ambiguous — open a file in this project to attach its server"
),
}
}
pub(crate) fn healthy() -> Self {
Self {
kind: ProjectStatusKind::Attached,
reason: String::new(),
}
}
}
impl Editor {
pub(crate) fn record_project_status(&mut self, root: PathBuf, status: ProjectStatus) {
{
let Some(glue) = self.picker.as_mut() else {
return;
};
if glue.picker.kind != Kind::WorkspaceSymbols {
return;
}
match glue
.project_statuses
.iter_mut()
.find(|(path, _)| *path == root)
{
Some((_, existing)) => *existing = status,
None => glue.project_statuses.push((root, status)),
}
}
let rows = self
.picker
.as_ref()
.map(|glue| {
glue.project_statuses
.iter()
.filter(|(_, status)| !status.kind.healthy())
.map(|(path, status)| project_status_row(path, status, &self.cwd))
.collect::<Vec<_>>()
})
.unwrap_or_default();
if let Some(glue) = self.picker.as_mut() {
glue.picker.set_pinned(rows);
}
self.request_picker_ranking();
}
}
fn project_status_row(root: &Path, status: &ProjectStatus, cwd: &Path) -> Item {
let name = root
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_else(|| root.display().to_string());
let shown = root
.strip_prefix(cwd)
.map(strop_picker::display_path)
.unwrap_or_else(|_| std::borrow::Cow::from(root.display().to_string()))
.into_owned();
Item {
badge: Some(status.kind.badge().into()),
text: format!("{name} {shown} · {}", status.reason),
payload: Payload::ProjectStatus(root.to_path_buf()),
}
}