use crate::{
config::{Config, FilesWatcher},
diagnostics::{CheckFixes, DiagnosticCollection},
- main_loop::pending_requests::{CompletedRequest, LatestRequests},
+ from_proto,
+ line_endings::LineEndings,
+ main_loop::ReqQueue,
+ request_metrics::{LatestRequests, RequestMetrics},
to_proto::url_from_abs_path,
- vfs_glob::{Glob, RustPackageFilterBuilder},
- LspError, Result,
+ Result,
};
-use ra_db::{CrateId, ExternSourceId};
-use ra_progress::{ProgressSource, ProgressStatus};
use rustc_hash::{FxHashMap, FxHashSet};
- fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> {
+ fn create_flycheck(
+ workspaces: &[ProjectWorkspace],
+ config: &FlycheckConfig,
+ progress_src: &ProgressSource<(), String>,
+ ) -> Option<Flycheck> {
// FIXME: Figure out the multi-workspace situation
- workspaces.iter().find_map(|w| match w {
+ workspaces.iter().find_map(move |w| match w {
ProjectWorkspace::Cargo { cargo, .. } => {
let cargo_project_root = cargo.workspace_root().to_path_buf();
- Some(Flycheck::new(config.clone(), cargo_project_root, progress_src.clone()))
+ Some(Flycheck::new(config.clone(), cargo_project_root.into()))
}
ProjectWorkspace::Json { .. } => {
log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
let mut analysis_host = AnalysisHost::new(lru_capacity);
analysis_host.apply_change(change);
- GlobalState {
+ let mut res = GlobalState {
config,
- local_roots,
- workspaces: Arc::new(workspaces),
analysis_host,
- vfs: Arc::new(RwLock::new(vfs)),
+ loader,
task_receiver,
- latest_requests: Default::default(),
flycheck,
+ flycheck_progress_src,
+ flycheck_progress_receiver,
diagnostics: Default::default(),
- proc_macro_client,
- }
+ mem_docs: FxHashSet::default(),
+ vfs: Arc::new(RwLock::new((vfs, FxHashMap::default()))),
+ status: Status::default(),
+ req_queue,
+ latest_requests: Default::default(),
+ source_root_config: project_folders.source_root_config,
+ _proc_macro_client: proc_macro_client,
+ workspaces: Arc::new(workspaces),
+ };
+ res.process_changes();
+ res
}
- pub fn update_configuration(&mut self, config: Config) {
+ pub(crate) fn update_configuration(&mut self, config: Config) {
self.analysis_host.update_lru_capacity(config.lru_capacity);
if config.check != self.config.check {
- self.flycheck =
- config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it));
+ self.flycheck = config
+ .check
+ .as_ref()
+ .and_then(|it| create_flycheck(&self.workspaces, it, &self.flycheck_progress_src));
}
self.config = config;
--- /dev/null
--- /dev/null
++//! Utilities for LSP-related boilerplate code.
++
++use crossbeam_channel::Sender;
++use lsp_server::{Message, Notification, Request, RequestId};
++use ra_db::Canceled;
++use serde::{de::DeserializeOwned, Serialize};
++use std::error::Error;
++
++pub fn show_message(
++ typ: lsp_types::MessageType,
++ message: impl Into<String>,
++ sender: &Sender<Message>,
++) {
++ let message = message.into();
++ let params = lsp_types::ShowMessageParams { typ, message };
++ let not = notification_new::<lsp_types::notification::ShowMessage>(params);
++ sender.send(not.into()).unwrap();
++}
++
++pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool {
++ e.downcast_ref::<Canceled>().is_some()
++}
++
++pub(crate) fn notification_is<N: lsp_types::notification::Notification>(
++ notification: &Notification,
++) -> bool {
++ notification.method == N::METHOD
++}
++
++pub(crate) fn notification_cast<N>(notification: Notification) -> Result<N::Params, Notification>
++where
++ N: lsp_types::notification::Notification,
++ N::Params: DeserializeOwned,
++{
++ notification.extract(N::METHOD)
++}
++
++pub(crate) fn notification_new<N>(params: N::Params) -> Notification
++where
++ N: lsp_types::notification::Notification,
++ N::Params: Serialize,
++{
++ Notification::new(N::METHOD.to_string(), params)
++}
++
++pub(crate) fn request_new<R>(id: RequestId, params: R::Params) -> Request
++where
++ R: lsp_types::request::Request,
++ R::Params: Serialize,
++{
++ Request::new(id, R::METHOD.to_string(), params)
++}
config::{Config, FilesWatcher, LinkedProject},
diagnostics::DiagnosticTask,
from_proto,
- global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot},
- lsp_ext,
- main_loop::{
- pending_requests::{PendingRequest, PendingRequests},
- subscriptions::Subscriptions,
- },
- Result,
+ global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot, Status},
+ handlers, lsp_ext,
+ request_metrics::RequestMetrics,
+ LspError, Result,
};
+ pub use lsp_utils::show_message;
+ use lsp_utils::{is_canceled, notification_cast, notification_is, notification_new, request_new};
+ use ra_progress::{
+ IsDone, ProgressStatus, U32Progress, U32ProgressReport, U32ProgressSource, U32ProgressStatus,
+ };
+
+ const FLYCHECK_PROGRESS_TOKEN: &str = "rustAnalyzer/flycheck";
+ const ROOTS_SCANNED_PROGRESS_TOKEN: &str = "rustAnalyzer/rootsScanned";
-#[derive(Debug)]
-pub struct LspError {
- pub code: i32,
- pub message: String,
-}
-
-impl LspError {
- pub const UNKNOWN_FILE: i32 = -32900;
-
- pub fn new(code: i32, message: String) -> LspError {
- LspError { code, message }
- }
-}
-
-impl fmt::Display for LspError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "Language Server request failed with {}. ({})", self.code, self.message)
- }
-}
-
-impl Error for LspError {}
-
pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
log::info!("initial config: {:#?}", config);
enum Event {
Msg(Message),
Task(Task),
- Vfs(VfsTask),
+ Vfs(vfs::loader::Message),
CheckWatcher(CheckTask),
+ ProgressReport(ProgressReport),
+ }
+
+ #[derive(Debug)]
+ enum ProgressReport {
+ Flycheck(ProgressStatus<(), String>),
+ RootsScanned(U32ProgressStatus),
}
impl fmt::Debug for Event {
log::info!("queued count = {}", queue_count);
}
+ let mut became_ready = false;
match event {
Event::Task(task) => {
- on_task(task, &connection.sender, &mut loop_state.pending_requests, global_state);
+ on_task(task, &connection.sender, global_state);
global_state.maybe_collect_garbage();
}
- Event::Vfs(task) => {
- global_state.vfs.write().handle_task(task);
- }
+ Event::Vfs(task) => match task {
+ vfs::loader::Message::Loaded { files } => {
+ let vfs = &mut global_state.vfs.write().0;
+ for (path, contents) in files {
+ let path = VfsPath::from(path);
+ if !global_state.mem_docs.contains(&path) {
+ vfs.set_file_contents(path, contents)
+ }
+ }
+ }
+ vfs::loader::Message::Progress { n_total, n_done } => {
+ if n_done == n_total {
+ global_state.status = Status::Ready;
+ became_ready = true;
+ }
+ report_progress(global_state, &connection.sender, n_done, n_total, "roots scanned")
+ }
+ },
Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?,
+ Event::ProgressReport(report) => {
+ on_progress_report(report, task_sender, loop_state, global_state)
+ }
Event::Msg(msg) => match msg {
- Message::Request(req) => on_request(
- global_state,
- &mut loop_state.pending_requests,
- pool,
- task_sender,
- &connection.sender,
- loop_start,
- req,
- )?,
+ Message::Request(req) => {
+ on_request(global_state, pool, task_sender, &connection.sender, loop_start, req)?
+ }
Message::Notification(not) => {
- on_notification(&connection.sender, global_state, loop_state, not)?;
+ on_notification(&connection.sender, global_state, not)?;
}
Message::Response(resp) => {
- let removed = loop_state.pending_responses.remove(&resp.id);
- if !removed {
- log::error!("unexpected response: {:?}", resp)
- }
-
- if Some(&resp.id) == loop_state.configuration_request_id.as_ref() {
- loop_state.configuration_request_id = None;
- log::debug!("config update response: '{:?}", resp);
- let Response { error, result, .. } = resp;
-
- match (error, result) {
- (Some(err), _) => {
- log::error!("failed to fetch the server settings: {:?}", err)
- }
- (None, Some(configs)) => {
- if let Some(new_config) = configs.get(0) {
- let mut config = global_state.config.clone();
- config.update(&new_config);
- global_state.update_configuration(config);
- }
- }
- (None, None) => {
- log::error!("received empty server settings response from the client")
- }
- }
- }
+ let handler = global_state.req_queue.outgoing.complete(resp.id.clone());
+ handler(global_state, resp)
}
},
};
let response = match result {
Ok(resp) => Response::new_ok(id, &resp),
Err(e) => match e.downcast::<LspError>() {
- Ok(lsp_error) => {
- if lsp_error.code == LspError::UNKNOWN_FILE {
- // Work-around for https://github.com/rust-analyzer/rust-analyzer/issues/1521
- Response::new_ok(id, ())
- } else {
- Response::new_err(id, lsp_error.code, lsp_error.message)
- }
- }
+ Ok(lsp_error) => Response::new_err(id, lsp_error.code, lsp_error.message),
Err(e) => {
- if is_canceled(&e) {
+ if is_canceled(&*e) {
Response::new_err(
id,
ErrorCode::ContentModified as i32,