mirror of
https://github.com/oxalica/nil.git
synced 2025-12-23 09:19:49 +00:00
Proper error handling for LSP
This commit is contained in:
parent
dabcd8b962
commit
dd476b39e7
12 changed files with 186 additions and 116 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -312,7 +312,6 @@ dependencies = [
|
|||
name = "nil"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossbeam-channel",
|
||||
"ide",
|
||||
"ignore",
|
||||
|
|
|
|||
|
|
@ -99,8 +99,8 @@ impl FileSet {
|
|||
self.files.get(path).copied()
|
||||
}
|
||||
|
||||
pub fn get_path_for_file(&self, file: FileId) -> Option<&VfsPath> {
|
||||
self.paths.get(&file)
|
||||
pub fn path_for_file(&self, file: FileId) -> &VfsPath {
|
||||
&self.paths[&file]
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (FileId, &'_ VfsPath)> + '_ {
|
||||
|
|
@ -130,8 +130,8 @@ impl SourceRoot {
|
|||
self.file_set.get_file_for_path(path)
|
||||
}
|
||||
|
||||
pub fn get_path_for_file(&self, file: FileId) -> Option<&VfsPath> {
|
||||
self.file_set.get_path_for_file(file)
|
||||
pub fn path_for_file(&self, file: FileId) -> &VfsPath {
|
||||
self.file_set.path_for_file(file)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (FileId, &'_ VfsPath)> + '_ {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ impl Path {
|
|||
};
|
||||
let sid = db.file_source_root(file);
|
||||
let root = db.source_root(sid);
|
||||
let mut vpath = root.get_path_for_file(file)?.clone();
|
||||
let mut vpath = root.path_for_file(file).clone();
|
||||
for _ in 0..(data.supers.saturating_add(1)) {
|
||||
vpath.pop()?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::base::SourceDatabaseStorage;
|
|||
use crate::def::DefDatabaseStorage;
|
||||
use crate::{Change, Diagnostic, FileId, FilePos, FileRange};
|
||||
use rowan::TextRange;
|
||||
use salsa::{Cancelled, Database, Durability, ParallelDatabase};
|
||||
use salsa::{Database, Durability, ParallelDatabase};
|
||||
use std::fmt;
|
||||
|
||||
pub use completion::{CompletionItem, CompletionItemKind};
|
||||
|
|
@ -20,6 +20,7 @@ pub struct NavigationTarget {
|
|||
pub focus_range: TextRange,
|
||||
}
|
||||
|
||||
pub use salsa::Cancelled;
|
||||
pub type Cancellable<T> = Result<T, Cancelled>;
|
||||
|
||||
#[salsa::database(SourceDatabaseStorage, DefDatabaseStorage)]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ mod ide;
|
|||
mod tests;
|
||||
|
||||
pub use self::ide::{
|
||||
Analysis, AnalysisHost, CompletionItem, CompletionItemKind, NavigationTarget, RootDatabase,
|
||||
Analysis, AnalysisHost, Cancelled, CompletionItem, CompletionItemKind, NavigationTarget,
|
||||
RootDatabase,
|
||||
};
|
||||
pub use base::{
|
||||
Change, FileId, FilePos, FileRange, FileSet, InFile, SourceDatabase, SourceRoot, SourceRootId,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ edition = "2021"
|
|||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.58"
|
||||
crossbeam-channel = "0.5.6"
|
||||
ide = { path = "../ide" }
|
||||
ignore = "0.4.18"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{LineMap, StateSnapshot, Vfs};
|
||||
use crate::{LineMap, Result, StateSnapshot, Vfs};
|
||||
use ide::{CompletionItem, CompletionItemKind, Diagnostic, FileId, FilePos, FileRange, Severity};
|
||||
use lsp_types::{
|
||||
self as lsp, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location,
|
||||
|
|
@ -6,31 +6,31 @@ use lsp_types::{
|
|||
};
|
||||
use text_size::{TextRange, TextSize};
|
||||
|
||||
pub(crate) fn from_file(snap: &StateSnapshot, doc: &TextDocumentIdentifier) -> Option<FileId> {
|
||||
pub(crate) fn from_file(snap: &StateSnapshot, doc: &TextDocumentIdentifier) -> Result<FileId> {
|
||||
let vfs = snap.vfs.read().unwrap();
|
||||
vfs.get_file_for_uri(&doc.uri)
|
||||
}
|
||||
|
||||
pub(crate) fn from_pos(snap: &StateSnapshot, file: FileId, pos: Position) -> Option<TextSize> {
|
||||
pub(crate) fn from_pos(snap: &StateSnapshot, file: FileId, pos: Position) -> Result<TextSize> {
|
||||
let vfs = snap.vfs.read().unwrap();
|
||||
let line_map = vfs.get_line_map(file)?;
|
||||
let line_map = vfs.file_line_map(file);
|
||||
let pos = line_map.pos(pos.line, pos.character);
|
||||
Some(pos)
|
||||
Ok(pos)
|
||||
}
|
||||
|
||||
pub(crate) fn from_file_pos(
|
||||
snap: &StateSnapshot,
|
||||
params: &TextDocumentPositionParams,
|
||||
) -> Option<FilePos> {
|
||||
) -> Result<FilePos> {
|
||||
let file = from_file(snap, ¶ms.text_document)?;
|
||||
let pos = from_pos(snap, file, params.position)?;
|
||||
Some(FilePos::new(file, pos))
|
||||
Ok(FilePos::new(file, pos))
|
||||
}
|
||||
|
||||
pub(crate) fn to_location(vfs: &Vfs, frange: FileRange) -> Option<Location> {
|
||||
let uri = vfs.get_uri_for_file(frange.file_id)?;
|
||||
let line_map = vfs.get_line_map(frange.file_id)?;
|
||||
Some(Location::new(uri, to_range(line_map, frange.range)))
|
||||
pub(crate) fn to_location(vfs: &Vfs, frange: FileRange) -> Location {
|
||||
let uri = vfs.uri_for_file(frange.file_id);
|
||||
let line_map = vfs.file_line_map(frange.file_id);
|
||||
Location::new(uri, to_range(line_map, frange.range))
|
||||
}
|
||||
|
||||
pub(crate) fn to_range(line_map: &LineMap, range: TextRange) -> Range {
|
||||
|
|
@ -43,8 +43,8 @@ pub(crate) fn to_diagnostics(
|
|||
vfs: &Vfs,
|
||||
file: FileId,
|
||||
diags: &[Diagnostic],
|
||||
) -> Option<Vec<lsp::Diagnostic>> {
|
||||
let line_map = vfs.get_line_map(file)?;
|
||||
) -> Vec<lsp::Diagnostic> {
|
||||
let line_map = vfs.file_line_map(file);
|
||||
let mut ret = Vec::with_capacity(diags.len() * 2);
|
||||
for diag in diags {
|
||||
let primary_diag = lsp::Diagnostic {
|
||||
|
|
@ -62,11 +62,9 @@ pub(crate) fn to_diagnostics(
|
|||
Some(
|
||||
diag.notes
|
||||
.iter()
|
||||
.filter_map(|(frange, msg)| {
|
||||
Some(DiagnosticRelatedInformation {
|
||||
location: to_location(vfs, *frange)?,
|
||||
message: msg.to_owned(),
|
||||
})
|
||||
.map(|(frange, msg)| DiagnosticRelatedInformation {
|
||||
location: to_location(vfs, *frange),
|
||||
message: msg.to_owned(),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
|
|
@ -99,8 +97,7 @@ pub(crate) fn to_diagnostics(
|
|||
source: primary_diag.source.clone(),
|
||||
message: msg.into(),
|
||||
related_information: Some(vec![DiagnosticRelatedInformation {
|
||||
location: to_location(vfs, FileRange::new(file, diag.range))
|
||||
.expect("Checked by get_line_map"),
|
||||
location: to_location(vfs, FileRange::new(file, diag.range)),
|
||||
message: "original diagnostic".into(),
|
||||
}]),
|
||||
tags: None,
|
||||
|
|
@ -110,13 +107,11 @@ pub(crate) fn to_diagnostics(
|
|||
|
||||
ret.push(primary_diag);
|
||||
}
|
||||
Some(ret)
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn to_completion_item(
|
||||
line_map: &LineMap,
|
||||
item: CompletionItem,
|
||||
) -> Option<lsp::CompletionItem> {
|
||||
pub(crate) fn to_completion_item(line_map: &LineMap, item: CompletionItem) -> lsp::CompletionItem {
|
||||
let kind = match item.kind {
|
||||
CompletionItemKind::Keyword => lsp::CompletionItemKind::KEYWORD,
|
||||
CompletionItemKind::Param => lsp::CompletionItemKind::VARIABLE,
|
||||
|
|
@ -126,7 +121,7 @@ pub(crate) fn to_completion_item(
|
|||
CompletionItemKind::BuiltinFunction => lsp::CompletionItemKind::FUNCTION,
|
||||
CompletionItemKind::BuiltinAttrset => lsp::CompletionItemKind::CLASS,
|
||||
};
|
||||
Some(lsp::CompletionItem {
|
||||
lsp::CompletionItem {
|
||||
label: item.label.into(),
|
||||
kind: Some(kind),
|
||||
insert_text: None,
|
||||
|
|
@ -139,5 +134,5 @@ pub(crate) fn to_completion_item(
|
|||
})),
|
||||
// TODO
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{convert, StateSnapshot};
|
||||
use crate::{convert, Result, StateSnapshot};
|
||||
use ide::FileRange;
|
||||
use lsp_types::{
|
||||
CompletionOptions, CompletionParams, CompletionResponse, GotoDefinitionParams,
|
||||
|
|
@ -31,65 +31,77 @@ pub(crate) fn server_capabilities() -> ServerCapabilities {
|
|||
pub(crate) fn goto_definition(
|
||||
snap: StateSnapshot,
|
||||
params: GotoDefinitionParams,
|
||||
) -> Option<GotoDefinitionResponse> {
|
||||
) -> Result<Option<GotoDefinitionResponse>> {
|
||||
let fpos = convert::from_file_pos(&snap, ¶ms.text_document_position_params)?;
|
||||
let targets = snap.analysis.goto_definition(fpos).ok()??;
|
||||
let targets = match snap.analysis.goto_definition(fpos)? {
|
||||
None => return Ok(None),
|
||||
Some(targets) => targets,
|
||||
};
|
||||
let vfs = snap.vfs.read().unwrap();
|
||||
let targets = targets
|
||||
.into_iter()
|
||||
.filter_map(|target| {
|
||||
.map(|target| {
|
||||
convert::to_location(&vfs, FileRange::new(target.file_id, target.focus_range))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Some(GotoDefinitionResponse::Array(targets))
|
||||
Ok(Some(GotoDefinitionResponse::Array(targets)))
|
||||
}
|
||||
|
||||
pub(crate) fn references(snap: StateSnapshot, params: ReferenceParams) -> Option<Vec<Location>> {
|
||||
pub(crate) fn references(
|
||||
snap: StateSnapshot,
|
||||
params: ReferenceParams,
|
||||
) -> Result<Option<Vec<Location>>> {
|
||||
let fpos = convert::from_file_pos(&snap, ¶ms.text_document_position)?;
|
||||
let refs = snap.analysis.references(fpos).ok()??;
|
||||
let refs = match snap.analysis.references(fpos)? {
|
||||
None => return Ok(None),
|
||||
Some(refs) => refs,
|
||||
};
|
||||
let vfs = snap.vfs.read().unwrap();
|
||||
let locs = refs
|
||||
.iter()
|
||||
.filter_map(|&frange| convert::to_location(&vfs, frange))
|
||||
.into_iter()
|
||||
.map(|frange| convert::to_location(&vfs, frange))
|
||||
.collect::<Vec<_>>();
|
||||
Some(locs)
|
||||
Ok(Some(locs))
|
||||
}
|
||||
|
||||
pub(crate) fn completion(
|
||||
snap: StateSnapshot,
|
||||
params: CompletionParams,
|
||||
) -> Option<CompletionResponse> {
|
||||
) -> Result<Option<CompletionResponse>> {
|
||||
let fpos = convert::from_file_pos(&snap, ¶ms.text_document_position)?;
|
||||
let items = snap.analysis.completions(fpos).ok()??;
|
||||
let items = match snap.analysis.completions(fpos)? {
|
||||
None => return Ok(None),
|
||||
Some(items) => items,
|
||||
};
|
||||
let vfs = snap.vfs.read().unwrap();
|
||||
let line_map = vfs.get_line_map(fpos.file_id)?;
|
||||
let line_map = vfs.file_line_map(fpos.file_id);
|
||||
let items = items
|
||||
.into_iter()
|
||||
.filter_map(|item| convert::to_completion_item(line_map, item))
|
||||
.map(|item| convert::to_completion_item(line_map, item))
|
||||
.collect::<Vec<_>>();
|
||||
Some(CompletionResponse::Array(items))
|
||||
Ok(Some(CompletionResponse::Array(items)))
|
||||
}
|
||||
|
||||
pub(crate) fn selection_range(
|
||||
snap: StateSnapshot,
|
||||
params: SelectionRangeParams,
|
||||
) -> Option<Vec<SelectionRange>> {
|
||||
) -> Result<Option<Vec<SelectionRange>>> {
|
||||
let file = convert::from_file(&snap, ¶ms.text_document)?;
|
||||
params
|
||||
let ret = params
|
||||
.positions
|
||||
.iter()
|
||||
.map(|&pos| {
|
||||
let pos = convert::from_pos(&snap, file, pos)?;
|
||||
let frange = FileRange::new(file, TextRange::empty(pos));
|
||||
|
||||
let mut ranges = snap.analysis.expand_selection(frange).ok()??;
|
||||
let mut ranges = snap.analysis.expand_selection(frange)?.unwrap_or_default();
|
||||
if ranges.is_empty() {
|
||||
ranges.push(TextRange::empty(pos));
|
||||
}
|
||||
|
||||
// FIXME: Use Arc for LineMap.
|
||||
let vfs = snap.vfs.read().unwrap();
|
||||
let line_map = vfs.get_line_map(file)?;
|
||||
let line_map = vfs.file_line_map(file);
|
||||
let mut ret = SelectionRange {
|
||||
range: convert::to_range(line_map, *ranges.last().unwrap()),
|
||||
parent: None,
|
||||
|
|
@ -101,7 +113,8 @@ pub(crate) fn selection_range(
|
|||
};
|
||||
}
|
||||
|
||||
Some(ret)
|
||||
Ok(ret)
|
||||
})
|
||||
.collect()
|
||||
.collect::<Result<Vec<_>>>();
|
||||
ret.map(Some)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ mod handler;
|
|||
mod state;
|
||||
mod vfs;
|
||||
|
||||
use anyhow::Result;
|
||||
use lsp_server::Connection;
|
||||
use lsp_types::InitializeParams;
|
||||
use std::env;
|
||||
|
|
@ -12,10 +11,13 @@ use std::path::PathBuf;
|
|||
pub(crate) use state::{State, StateSnapshot};
|
||||
pub(crate) use vfs::{LineMap, Vfs};
|
||||
|
||||
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
pub fn main_loop(conn: Connection) -> Result<()> {
|
||||
let init_params =
|
||||
conn.initialize(serde_json::to_value(&handler::server_capabilities()).unwrap())?;
|
||||
log::info!("Init params: {}", init_params);
|
||||
tracing::info!("Init params: {}", init_params);
|
||||
|
||||
let init_params = serde_json::from_value::<InitializeParams>(init_params)?;
|
||||
let workspace_path = (|| -> Option<PathBuf> {
|
||||
|
|
@ -25,12 +27,12 @@ pub fn main_loop(conn: Connection) -> Result<()> {
|
|||
if let Some(uri) = &init_params.root_uri {
|
||||
return uri.to_file_path().ok();
|
||||
}
|
||||
Some(env::current_dir().expect("Cannot get current directory"))
|
||||
env::current_dir().ok()
|
||||
})();
|
||||
|
||||
let mut state = State::new(conn.sender.clone(), workspace_path);
|
||||
state.run(conn.receiver)?;
|
||||
|
||||
log::info!("Leaving main loop");
|
||||
tracing::info!("Leaving main loop");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use lsp_server::Connection;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::{env, fs, io};
|
||||
use std::{env, fs, io, process};
|
||||
use tracing_subscriber::fmt::writer::BoxMakeWriter;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
|
|
@ -10,7 +9,7 @@ const LOG_FILTER_ENV: &str = "NIL_LOG";
|
|||
const LOG_PATH_ENV: &str = "NIL_LOG_PATH";
|
||||
const BACKTRACE_ENV: &str = "RUST_BACKTRACE";
|
||||
|
||||
fn main() -> Result<()> {
|
||||
fn main() {
|
||||
if env::var(BACKTRACE_ENV).is_err() {
|
||||
env::set_var(BACKTRACE_ENV, "short");
|
||||
}
|
||||
|
|
@ -21,13 +20,18 @@ fn main() -> Result<()> {
|
|||
let date = option_env!("CFG_DATE").unwrap_or("unknown");
|
||||
let rev = option_env!("CFG_REV").unwrap_or("unknown");
|
||||
println!("nil {} {}", date, rev);
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
let (conn, io_threads) = Connection::stdio();
|
||||
nil::main_loop(conn)?;
|
||||
io_threads.join()?;
|
||||
Ok(())
|
||||
match nil::main_loop(conn).and_then(|()| io_threads.join().map_err(Into::into)) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
tracing::error!("Unexpected error: {}", err);
|
||||
eprintln!("{}", err);
|
||||
process::exit(101);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_logger() {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use crate::{convert, handler, Vfs};
|
||||
use anyhow::{bail, Result};
|
||||
use crate::{convert, handler, Result, Vfs};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use ide::{Analysis, AnalysisHost, VfsPath};
|
||||
use lsp_server::{ErrorCode, Message, Notification, Request, Response};
|
||||
use ide::{Analysis, AnalysisHost, Cancelled, VfsPath};
|
||||
use lsp_server::{ErrorCode, Message, Notification, Request, RequestId, Response};
|
||||
use lsp_types::notification::Notification as _;
|
||||
use lsp_types::{notification as notif, request as req, PublishDiagnosticsParams, Url};
|
||||
use serde::Serialize;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -23,6 +23,8 @@ pub struct State {
|
|||
|
||||
impl State {
|
||||
pub fn new(responder: Sender<Message>, workspace_root: Option<PathBuf>) -> Self {
|
||||
// Vfs root must be absolute.
|
||||
let workspace_root = workspace_root.and_then(|root| root.canonicalize().ok());
|
||||
let vfs = Vfs::new(workspace_root.clone().unwrap_or_else(|| PathBuf::from("/")));
|
||||
Self {
|
||||
host: Default::default(),
|
||||
|
|
@ -58,12 +60,13 @@ impl State {
|
|||
if notif.method == notif::Exit::METHOD {
|
||||
return Ok(());
|
||||
}
|
||||
self.dispatch_notification(notif)
|
||||
self.dispatch_notification(notif)?;
|
||||
}
|
||||
Message::Response(_) => {}
|
||||
}
|
||||
}
|
||||
bail!("Channel closed")
|
||||
|
||||
Err("Channel closed".into())
|
||||
}
|
||||
|
||||
fn dispatch_request(&mut self, req: Request) {
|
||||
|
|
@ -80,6 +83,7 @@ impl State {
|
|||
RequestDispatcher(self, Some(req))
|
||||
.on_sync_mut::<req::Shutdown>(|st, ()| {
|
||||
st.is_shutdown = true;
|
||||
Ok(())
|
||||
})
|
||||
.on::<req::GotoDefinition>(handler::goto_definition)
|
||||
.on::<req::References>(handler::references)
|
||||
|
|
@ -88,26 +92,29 @@ impl State {
|
|||
.finish();
|
||||
}
|
||||
|
||||
fn dispatch_notification(&mut self, notif: Notification) {
|
||||
fn dispatch_notification(&mut self, notif: Notification) -> Result<()> {
|
||||
NotificationDispatcher(self, Some(notif))
|
||||
.on_sync_mut::<notif::DidOpenTextDocument>(|st, params| {
|
||||
let uri = ¶ms.text_document.uri;
|
||||
st.opened_files.write().unwrap().insert(uri.clone());
|
||||
st.set_vfs_file_content(uri, Some(params.text_document.text));
|
||||
})
|
||||
st.set_vfs_file_content(uri, Some(params.text_document.text))?;
|
||||
Ok(())
|
||||
})?
|
||||
.on_sync_mut::<notif::DidCloseTextDocument>(|st, params| {
|
||||
// N.B. Don't clear text here.
|
||||
st.opened_files
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove(¶ms.text_document.uri);
|
||||
})
|
||||
Ok(())
|
||||
})?
|
||||
.on_sync_mut::<notif::DidChangeTextDocument>(|st, params| {
|
||||
if let Some(chg) = params.content_changes.into_iter().next() {
|
||||
st.set_vfs_file_content(¶ms.text_document.uri, Some(chg.text));
|
||||
st.set_vfs_file_content(¶ms.text_document.uri, Some(chg.text))?;
|
||||
}
|
||||
})
|
||||
.finish();
|
||||
Ok(())
|
||||
})?
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn send_notification<N: notif::Notification>(&self, params: N::Params) {
|
||||
|
|
@ -123,9 +130,10 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_vfs_file_content(&mut self, uri: &Url, text: Option<String>) {
|
||||
self.vfs.write().unwrap().set_uri_content(uri, text);
|
||||
fn set_vfs_file_content(&mut self, uri: &Url, text: Option<String>) -> Result<()> {
|
||||
self.vfs.write().unwrap().set_uri_content(uri, text)?;
|
||||
self.apply_vfs_change();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_vfs_change(&mut self) {
|
||||
|
|
@ -136,22 +144,23 @@ impl State {
|
|||
.iter()
|
||||
.map(|(file, text)| (*file, text.is_some()))
|
||||
.collect::<Vec<_>>();
|
||||
log::debug!("Change: {:?}", change);
|
||||
tracing::debug!("Change: {:?}", change);
|
||||
self.host.apply_change(change);
|
||||
|
||||
let snap = self.host.snapshot();
|
||||
let opened_files = self.opened_files.read().unwrap();
|
||||
for (file, has_text) in file_changes {
|
||||
let uri = vfs.get_uri_for_file(file).unwrap();
|
||||
let uri = vfs.uri_for_file(file);
|
||||
if !opened_files.contains(&uri) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Error is ignored.
|
||||
let diagnostics = has_text
|
||||
.then(|| {
|
||||
let mut diags = snap.diagnostics(file).ok()?;
|
||||
diags.truncate(MAX_DIAGNOSTICS_CNT);
|
||||
convert::to_diagnostics(&vfs, file, &diags)
|
||||
Some(convert::to_diagnostics(&vfs, file, &diags))
|
||||
})
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
|
@ -168,25 +177,41 @@ impl State {
|
|||
struct RequestDispatcher<'s>(&'s mut State, Option<Request>);
|
||||
|
||||
impl<'s> RequestDispatcher<'s> {
|
||||
fn on_sync_mut<R: req::Request>(mut self, f: fn(&mut State, R::Params) -> R::Result) -> Self {
|
||||
fn on_sync_mut<R: req::Request>(
|
||||
mut self,
|
||||
f: fn(&mut State, R::Params) -> Result<R::Result>,
|
||||
) -> Self {
|
||||
if matches!(&self.1, Some(notif) if notif.method == R::METHOD) {
|
||||
let req = self.1.take().unwrap();
|
||||
let params = serde_json::from_value::<R::Params>(req.params).unwrap();
|
||||
let resp = f(self.0, params);
|
||||
let resp = Response::new_ok(req.id, serde_json::to_value(resp).unwrap());
|
||||
self.0.sender.send(resp.into()).unwrap();
|
||||
let ret = match serde_json::from_value::<R::Params>(req.params) {
|
||||
Ok(params) => result_to_response(req.id, f(self.0, params)),
|
||||
Err(err) => Ok(Response::new_err(
|
||||
req.id,
|
||||
ErrorCode::InvalidParams as i32,
|
||||
err.to_string(),
|
||||
)),
|
||||
};
|
||||
if let Ok(resp) = ret {
|
||||
self.0.sender.send(resp.into()).unwrap();
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
// TODO: Error handling?
|
||||
fn on<R: req::Request>(mut self, f: fn(StateSnapshot, R::Params) -> R::Result) -> Self {
|
||||
fn on<R: req::Request>(mut self, f: fn(StateSnapshot, R::Params) -> Result<R::Result>) -> Self {
|
||||
if matches!(&self.1, Some(notif) if notif.method == R::METHOD) {
|
||||
let req = self.1.take().unwrap();
|
||||
let params = serde_json::from_value::<R::Params>(req.params).unwrap();
|
||||
let resp = f(self.0.snapshot(), params);
|
||||
let resp = Response::new_ok(req.id, serde_json::to_value(resp).unwrap());
|
||||
self.0.sender.send(resp.into()).unwrap();
|
||||
let ret = match serde_json::from_value::<R::Params>(req.params) {
|
||||
Ok(params) => result_to_response(req.id, f(self.0.snapshot(), params)),
|
||||
Err(err) => Ok(Response::new_err(
|
||||
req.id,
|
||||
ErrorCode::InvalidParams as i32,
|
||||
err.to_string(),
|
||||
)),
|
||||
};
|
||||
if let Ok(resp) = ret {
|
||||
self.0.sender.send(resp.into()).unwrap();
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
|
@ -203,17 +228,35 @@ impl<'s> RequestDispatcher<'s> {
|
|||
struct NotificationDispatcher<'s>(&'s mut State, Option<Notification>);
|
||||
|
||||
impl<'s> NotificationDispatcher<'s> {
|
||||
fn on_sync_mut<N: notif::Notification>(mut self, f: fn(&mut State, N::Params)) -> Self {
|
||||
fn on_sync_mut<N: notif::Notification>(
|
||||
mut self,
|
||||
f: fn(&mut State, N::Params) -> Result<()>,
|
||||
) -> Result<Self> {
|
||||
if matches!(&self.1, Some(notif) if notif.method == N::METHOD) {
|
||||
let params =
|
||||
serde_json::from_value::<N::Params>(self.1.take().unwrap().params).unwrap();
|
||||
f(self.0, params);
|
||||
f(self.0, params)?;
|
||||
}
|
||||
self
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn finish(self) {
|
||||
let _ = self;
|
||||
fn finish(self) -> Result<()> {
|
||||
// TODO: We are not done yet.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn result_to_response(id: RequestId, ret: Result<impl Serialize>) -> Result<Response, Cancelled> {
|
||||
match ret {
|
||||
Ok(ret) => Ok(Response::new_ok(id, ret)),
|
||||
Err(err) => match err.downcast::<Cancelled>() {
|
||||
Ok(cancelled) => Err(*cancelled),
|
||||
Err(err) => Ok(Response::new_err(
|
||||
id,
|
||||
ErrorCode::InternalError as i32,
|
||||
err.to_string(),
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::Result;
|
||||
use ide::{Change, FileId, FileSet, SourceRoot, VfsPath};
|
||||
use lsp_types::Url;
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -9,6 +10,7 @@ use text_size::TextSize;
|
|||
pub struct Vfs {
|
||||
// FIXME: Currently this list is append-only.
|
||||
files: Vec<Option<(Arc<str>, LineMap)>>,
|
||||
/// The root directory, which must be absolute.
|
||||
local_root: PathBuf,
|
||||
local_file_set: FileSet,
|
||||
root_changed: bool,
|
||||
|
|
@ -26,6 +28,7 @@ impl fmt::Debug for Vfs {
|
|||
|
||||
impl Vfs {
|
||||
pub fn new(local_root: PathBuf) -> Self {
|
||||
assert!(local_root.is_absolute());
|
||||
Self {
|
||||
files: Vec::new(),
|
||||
local_root,
|
||||
|
|
@ -41,15 +44,20 @@ impl Vfs {
|
|||
FileId(id)
|
||||
}
|
||||
|
||||
fn uri_to_vpath(&self, uri: &Url) -> Option<VfsPath> {
|
||||
let path = uri.to_file_path().ok()?;
|
||||
let relative_path = path.strip_prefix(&self.local_root).ok()?;
|
||||
VfsPath::from_path(relative_path)
|
||||
fn uri_to_vpath(&self, uri: &Url) -> Result<VfsPath> {
|
||||
let path = uri
|
||||
.to_file_path()
|
||||
.map_err(|_| format!("Non-file URI: {}", uri))?;
|
||||
let relative_path = path
|
||||
.strip_prefix(&self.local_root)
|
||||
.map_err(|_| format!("URI outside workspace: {}", uri))?;
|
||||
Ok(VfsPath::from_path(relative_path).expect("URI is UTF-8"))
|
||||
}
|
||||
|
||||
pub fn set_uri_content(&mut self, uri: &Url, text: Option<String>) -> Option<FileId> {
|
||||
pub fn set_uri_content(&mut self, uri: &Url, text: Option<String>) -> Result<()> {
|
||||
let vpath = self.uri_to_vpath(uri)?;
|
||||
self.set_path_content(vpath, text)
|
||||
self.set_path_content(vpath, text);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_path_content(&mut self, path: VfsPath, text: Option<String>) -> Option<FileId> {
|
||||
|
|
@ -77,16 +85,18 @@ impl Vfs {
|
|||
Some(file)
|
||||
}
|
||||
|
||||
pub fn get_file_for_uri(&self, uri: &Url) -> Option<FileId> {
|
||||
pub fn get_file_for_uri(&self, uri: &Url) -> Result<FileId> {
|
||||
let vpath = self.uri_to_vpath(uri)?;
|
||||
self.local_file_set.get_file_for_path(&vpath)
|
||||
self.local_file_set
|
||||
.get_file_for_path(&vpath)
|
||||
.ok_or_else(|| format!("URI not found: {}", uri).into())
|
||||
}
|
||||
|
||||
pub fn get_uri_for_file(&self, file: FileId) -> Option<Url> {
|
||||
let vpath = self.local_file_set.get_path_for_file(file)?.as_str();
|
||||
pub fn uri_for_file(&self, file: FileId) -> Url {
|
||||
let vpath = self.local_file_set.path_for_file(file).as_str();
|
||||
assert!(!vpath.is_empty(), "Root is a directory");
|
||||
let path = self.local_root.join(vpath.strip_prefix('/')?);
|
||||
Url::from_file_path(path).ok()
|
||||
let path = self.local_root.join(vpath.strip_prefix('/').unwrap());
|
||||
Url::from_file_path(path).expect("Root is absolute")
|
||||
}
|
||||
|
||||
pub fn take_change(&mut self) -> Change {
|
||||
|
|
@ -106,8 +116,11 @@ impl Vfs {
|
|||
change
|
||||
}
|
||||
|
||||
pub fn get_line_map(&self, file_id: FileId) -> Option<&LineMap> {
|
||||
Some(&self.files.get(file_id.0 as usize)?.as_ref()?.1)
|
||||
pub fn file_line_map(&self, file_id: FileId) -> &LineMap {
|
||||
&self.files[file_id.0 as usize]
|
||||
.as_ref()
|
||||
.expect("File must be valid")
|
||||
.1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue