mirror of
https://github.com/oxalica/nil.git
synced 2025-12-23 09:19:49 +00:00
Load locked flake inputs
This commit is contained in:
parent
0eb313e73b
commit
6e55610257
3 changed files with 110 additions and 14 deletions
|
|
@ -35,6 +35,17 @@ impl VfsPath {
|
|||
Ok(Self(s))
|
||||
}
|
||||
|
||||
/// Append a path segment at the end and return the new path.
|
||||
/// Panic if it is empty or contains `/`.
|
||||
pub fn join_segment(&self, segment: &str) -> Self {
|
||||
assert!(!segment.is_empty() && !segment.contains('/'));
|
||||
let mut buf = String::with_capacity(self.0.len() + 1 + segment.len());
|
||||
buf += &self.0;
|
||||
buf += "/";
|
||||
buf += segment;
|
||||
Self(buf)
|
||||
}
|
||||
|
||||
/// Assume another VfsPath as relative and append it to this one.
|
||||
pub fn append(&mut self, relative: &Self) {
|
||||
self.0.push_str(&relative.0);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use crate::config::{Config, CONFIG_KEY};
|
|||
use crate::{convert, handler, LspError, Vfs};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use ide::{Analysis, AnalysisHost, Cancelled};
|
||||
use ide::{Analysis, AnalysisHost, Cancelled, FileId, VfsPath};
|
||||
use lsp_server::{ErrorCode, Message, Notification, ReqQueue, Request, RequestId, Response};
|
||||
use lsp_types::notification::Notification as _;
|
||||
use lsp_types::{
|
||||
|
|
@ -10,12 +10,13 @@ use lsp_types::{
|
|||
InitializeParams, MessageType, NumberOrString, PublishDiagnosticsParams, ShowMessageParams,
|
||||
Url,
|
||||
};
|
||||
use nix_interop::{flake_lock, FLAKE_FILE, FLAKE_LOCK_FILE};
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
use std::panic::UnwindSafe;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Once, RwLock};
|
||||
use std::{panic, thread};
|
||||
use std::{fs, panic, thread};
|
||||
|
||||
type ReqHandler = fn(&mut Server, Response);
|
||||
|
||||
|
|
@ -29,6 +30,15 @@ enum Event {
|
|||
diagnostics: Vec<Diagnostic>,
|
||||
},
|
||||
ClientExited,
|
||||
LoadFlake(Result<LoadFlakeResult>),
|
||||
}
|
||||
|
||||
enum LoadFlakeResult {
|
||||
IsFlake {
|
||||
flake_file: FileId,
|
||||
input_store_paths: HashMap<String, String>,
|
||||
},
|
||||
NotFlake,
|
||||
}
|
||||
|
||||
pub struct Server {
|
||||
|
|
@ -72,9 +82,9 @@ impl Server {
|
|||
tracing::info!("Started {worker_cnt} workers");
|
||||
|
||||
Self {
|
||||
host: Default::default(),
|
||||
host: AnalysisHost::default(),
|
||||
vfs: Arc::new(RwLock::new(Vfs::new())),
|
||||
opened_files: Default::default(),
|
||||
opened_files: HashMap::default(),
|
||||
config: Arc::new(Config::new(root_path)),
|
||||
is_shutdown: false,
|
||||
version_counter: 0,
|
||||
|
|
@ -139,6 +149,9 @@ impl Server {
|
|||
});
|
||||
}
|
||||
|
||||
// TODO: Register file watcher for flake.lock.
|
||||
self.load_flake();
|
||||
|
||||
loop {
|
||||
crossbeam_channel::select! {
|
||||
recv(lsp_rx) -> msg => {
|
||||
|
|
@ -194,6 +207,25 @@ impl Server {
|
|||
Event::ClientExited => {
|
||||
bail!("The process initializing this server is exited. Exit now")
|
||||
}
|
||||
Event::LoadFlake(ret) => match ret {
|
||||
Err(err) => {
|
||||
self.show_message(
|
||||
MessageType::ERROR,
|
||||
format!("Failed to load workspace: {err}"),
|
||||
);
|
||||
}
|
||||
Ok(LoadFlakeResult::IsFlake {
|
||||
flake_file: _flake_file,
|
||||
input_store_paths,
|
||||
}) => {
|
||||
tracing::info!("Workspace is a flake with inputs {input_store_paths:?}");
|
||||
// TODO
|
||||
}
|
||||
Ok(LoadFlakeResult::NotFlake) => {
|
||||
tracing::info!("Workspace is not a flake");
|
||||
// TODO
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -311,6 +343,55 @@ impl Server {
|
|||
.finish()
|
||||
}
|
||||
|
||||
/// Enqueue a task to reload the flake.{nix,lock} and the locked inputs.
|
||||
fn load_flake(&self) {
|
||||
tracing::info!("Loading flake configuration");
|
||||
|
||||
let flake_path = self.config.root_path.join(FLAKE_FILE);
|
||||
let lock_path = self.config.root_path.join(FLAKE_LOCK_FILE);
|
||||
let vfs = self.vfs.clone();
|
||||
let task = move || {
|
||||
if !flake_path.exists() {
|
||||
// No flake found.
|
||||
return Ok(LoadFlakeResult::NotFlake);
|
||||
}
|
||||
|
||||
let flake_vpath = VfsPath::try_from(&*flake_path)?;
|
||||
let flake_src = fs::read_to_string(&flake_path)
|
||||
.with_context(|| format!("Failed to read flake root {flake_path:?}"))?;
|
||||
let flake_file = {
|
||||
let mut vfs = vfs.write().unwrap();
|
||||
match vfs.file_for_path(&flake_vpath) {
|
||||
// If the file is already opened (transferred from client),
|
||||
// prefer the managed one. It contains more recent unsaved changes.
|
||||
Ok(file) => file,
|
||||
// Otherwise, cache the file content from disk.
|
||||
Err(_) => vfs.set_path_content(flake_vpath, flake_src)?,
|
||||
}
|
||||
};
|
||||
|
||||
let lock_src = fs::read(&lock_path)?;
|
||||
|
||||
// TODO: Customize the command to Nix binary.
|
||||
let inputs = flake_lock::resolve_flake_locked_inputs("nix".as_ref(), &lock_src)
|
||||
.context("Failed to resolve flake inputs from lock file")?;
|
||||
|
||||
// We only need the map for input -> store path.
|
||||
let input_store_paths = inputs
|
||||
.into_iter()
|
||||
.map(|(key, input)| (key, input.store_path))
|
||||
.collect();
|
||||
|
||||
Ok(LoadFlakeResult::IsFlake {
|
||||
flake_file,
|
||||
input_store_paths,
|
||||
})
|
||||
};
|
||||
self.task_tx
|
||||
.send(Box::new(move || Event::LoadFlake(task())))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn send_request<R: req::Request>(
|
||||
&mut self,
|
||||
params: R::Params,
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ impl Vfs {
|
|||
|
||||
pub fn set_uri_content(&mut self, uri: &Url, text: String) -> Result<()> {
|
||||
let vpath = uri.to_vfs_path()?;
|
||||
self.set_path_content(vpath, text);
|
||||
self.set_path_content(vpath, text)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_path_content(&mut self, path: VfsPath, text: String) {
|
||||
pub fn set_path_content(&mut self, path: VfsPath, text: String) -> Result<FileId> {
|
||||
// For invalid files (currently, too large), we store them as empty files in database,
|
||||
// but remove them from `local_file_set`. Thus any interactions on them would fail.
|
||||
let (text, line_map, is_valid) = LineMap::normalize(text)
|
||||
|
|
@ -60,18 +60,19 @@ impl Vfs {
|
|||
self.local_file_set.remove_file(file);
|
||||
self.root_changed = true;
|
||||
}
|
||||
Ok(file)
|
||||
}
|
||||
None => {
|
||||
if !is_valid {
|
||||
return;
|
||||
}
|
||||
// FIXME: Somehow get rid of this validity check from Vfs.
|
||||
ensure!(is_valid, "File is not valid");
|
||||
let file = FileId(u32::try_from(self.files.len()).expect("Length overflow"));
|
||||
self.local_file_set.insert(file, path);
|
||||
self.root_changed = true;
|
||||
self.files.push((text.clone(), line_map));
|
||||
self.change.change_file(file, text);
|
||||
Ok(file)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_file_content(
|
||||
|
|
@ -102,11 +103,14 @@ impl Vfs {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn file_for_uri(&self, uri: &Url) -> Result<FileId> {
|
||||
let vpath = uri.to_vfs_path()?;
|
||||
pub fn file_for_path(&self, path: &VfsPath) -> Result<FileId> {
|
||||
self.local_file_set
|
||||
.file_for_path(&vpath)
|
||||
.with_context(|| format!("URI not found: {uri}"))
|
||||
.file_for_path(path)
|
||||
.with_context(|| format!("File not loaded: {path:?}"))
|
||||
}
|
||||
|
||||
pub fn file_for_uri(&self, uri: &Url) -> Result<FileId> {
|
||||
self.file_for_path(&uri.to_vfs_path()?)
|
||||
}
|
||||
|
||||
pub fn uri_for_file(&self, file: FileId) -> Url {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue