use crate::deps;
use ahash::AHashMap;
use anyhow::{Context, Result};
use arcstr::ArcStr;
use enumflags2::BitFlags;
use graphix_compiler::{
env::Env,
expr::{BufferOverrides, ModuleResolver, Source},
CFlag, ExecCtx,
};
use graphix_lsp::{LspBackend, TypecheckResult};
use graphix_rt::{CheckResult, GXConfig, GXEvent, GXHandle, GXRt, NoExt};
use lsp_types::{InitializeParams, Uri};
use netidx::InternalOnly;
use parking_lot::Mutex;
use poolshark::global::GPooled;
use std::{
path::{Path, PathBuf},
sync::Arc as StdArc,
};
use tokio::{
runtime::{Handle, Runtime},
sync::mpsc,
task,
};
use triomphe::Arc;
pub fn run() -> Result<()> {
let rt = Runtime::new().context("building tokio runtime")?;
let result = graphix_lsp::serve(|init| {
let roots = project_roots(init);
rt.block_on(build_backend(roots))
});
drop(rt);
result
}
async fn build_backend(roots: Vec<PathBuf>) -> Result<StdArc<dyn LspBackend>> {
let netidx = InternalOnly::new().await.context("starting internal netidx")?;
let publisher = netidx.publisher().clone();
let subscriber = netidx.subscriber().clone();
let mut ctx = ExecCtx::new(GXRt::<NoExt>::new(publisher, subscriber))
.context("creating graphix context")?;
let mut vfs = AHashMap::default();
let res = deps::register::<NoExt>(&mut ctx, &mut vfs)
.context("registering stdlib modules")?;
let mut resolvers: Vec<ModuleResolver> = vec![ModuleResolver::VFS(vfs)];
let base_resolvers = resolvers.clone();
for root in roots {
resolvers.push(ModuleResolver::Files { base: root, overrides: None });
}
let flags = CFlag::WarnUnhandled | CFlag::WarnUnused;
let (tx, rx) = mpsc::channel(100);
task::spawn(drain(rx));
let gx = GXConfig::builder(ctx, tx)
.flags(BitFlags::from(flags))
.root(res.root)
.resolvers(resolvers)
.lsp_mode(true)
.build()
.context("building runtime config")?
.start()
.await
.context("loading stdlib")?;
let _keep_netidx = StdArc::new(netidx);
let buffer_overrides: BufferOverrides = Arc::new(Mutex::new(AHashMap::default()));
Ok(StdArc::new(ShellLspBackend {
gx,
rt_handle: Handle::current(),
_keep_netidx,
base_resolvers,
buffer_overrides,
}))
}
async fn drain(mut rx: mpsc::Receiver<GPooled<Vec<GXEvent>>>) {
while rx.recv().await.is_some() {}
}
fn project_roots(init: &InitializeParams) -> Vec<PathBuf> {
let mut roots = Vec::new();
if let Some(folders) = &init.workspace_folders {
for folder in folders {
if let Some(p) = file_uri_to_path(&folder.uri) {
roots.push(p);
}
}
}
if roots.is_empty() {
#[allow(deprecated)]
if let Some(uri) = &init.root_uri {
if let Some(p) = file_uri_to_path(uri) {
roots.push(p);
}
}
}
if roots.is_empty() {
#[allow(deprecated)]
if let Some(p) = init.root_path.as_ref() {
roots.push(PathBuf::from(p));
}
}
roots
}
fn file_uri_to_path(uri: &Uri) -> Option<PathBuf> {
graphix_lsp::uri::uri_to_path(uri)
}
struct ShellLspBackend {
gx: GXHandle<NoExt>,
rt_handle: Handle,
_keep_netidx: StdArc<InternalOnly>,
base_resolvers: Vec<ModuleResolver>,
buffer_overrides: BufferOverrides,
}
impl ShellLspBackend {
fn resolvers_for(&self, file: &Path) -> Vec<ModuleResolver> {
let mut resolvers = self.base_resolvers.clone();
if let Some(parent) = file.parent() {
resolvers.push(ModuleResolver::Files {
base: parent.to_path_buf(),
overrides: Some(self.buffer_overrides.clone()),
});
}
resolvers
}
}
impl LspBackend for ShellLspBackend {
fn env(&self) -> Env {
self.rt_handle.block_on(self.gx.get_env()).unwrap_or_default()
}
fn buffer_overrides(&self) -> BufferOverrides {
self.buffer_overrides.clone()
}
fn typecheck_project(
&self,
root: &Path,
initial_scope: Option<ArcStr>,
) -> Result<TypecheckResult> {
let CheckResult { env, references, module_references, scope_map, lsp } =
self.rt_handle.block_on(self.gx.check_with_resolvers(
Source::File(root.to_path_buf()),
self.resolvers_for(root),
initial_scope,
))?;
Ok(TypecheckResult { env, references, module_references, scope_map, lsp })
}
}