mirror of
https://github.com/denoland/deno.git
synced 2024-10-29 08:58:01 -04:00
fix(lsp): diagnostics use own thread and debounce (#9572)
This commit is contained in:
parent
8d3baa7777
commit
a020ebde2d
4 changed files with 395 additions and 198 deletions
|
@ -15,7 +15,7 @@ pub struct ClientCapabilities {
|
|||
pub workspace_did_change_watched_files: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeLensSettings {
|
||||
/// Flag for providing implementation code lenses.
|
||||
|
@ -30,7 +30,7 @@ pub struct CodeLensSettings {
|
|||
pub references_all_functions: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceSettings {
|
||||
pub enable: bool,
|
||||
|
@ -81,7 +81,7 @@ impl WorkspaceSettings {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Config {
|
||||
pub client_capabilities: ClientCapabilities,
|
||||
pub root_uri: Option<Url>,
|
||||
|
|
|
@ -3,20 +3,33 @@
|
|||
use super::analysis::get_lint_references;
|
||||
use super::analysis::references_to_diagnostics;
|
||||
use super::analysis::ResolvedDependency;
|
||||
use super::language_server::StateSnapshot;
|
||||
use super::language_server;
|
||||
use super::tsc;
|
||||
|
||||
use crate::diagnostics;
|
||||
use crate::media_type::MediaType;
|
||||
use crate::tokio_util::create_basic_runtime;
|
||||
|
||||
use deno_core::error::anyhow;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::json;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use lspower::lsp;
|
||||
use lspower::Client;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::time;
|
||||
|
||||
// 150ms between keystrokes is about 45 WPM, so we want something that is longer
|
||||
// than that, but not too long to introduce detectable UI delay. 200ms is a
|
||||
// decent compromise.
|
||||
const DIAGNOSTIC_DEBOUNCE_MS: u64 = 200;
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub enum DiagnosticSource {
|
||||
|
@ -25,8 +38,311 @@ pub enum DiagnosticSource {
|
|||
TypeScript,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum DiagnosticRequest {
|
||||
Get(
|
||||
ModuleSpecifier,
|
||||
DiagnosticSource,
|
||||
oneshot::Sender<Vec<lsp::Diagnostic>>,
|
||||
),
|
||||
Invalidate(ModuleSpecifier),
|
||||
Update,
|
||||
}
|
||||
|
||||
/// Given a client and a diagnostics collection, publish the appropriate changes
|
||||
/// to the client.
|
||||
async fn publish_diagnostics(
|
||||
client: &Client,
|
||||
collection: &mut DiagnosticCollection,
|
||||
snapshot: &language_server::StateSnapshot,
|
||||
) {
|
||||
let mark = snapshot.performance.mark("publish_diagnostics");
|
||||
let maybe_changes = collection.take_changes();
|
||||
if let Some(diagnostic_changes) = maybe_changes {
|
||||
for specifier in diagnostic_changes {
|
||||
// TODO(@kitsonk) not totally happy with the way we collect and store
|
||||
// different types of diagnostics and offer them up to the client, we
|
||||
// do need to send "empty" vectors though when a particular feature is
|
||||
// disabled, otherwise the client will not clear down previous
|
||||
// diagnostics
|
||||
let mut diagnostics: Vec<lsp::Diagnostic> =
|
||||
if snapshot.config.settings.lint {
|
||||
collection
|
||||
.diagnostics_for(&specifier, &DiagnosticSource::Lint)
|
||||
.cloned()
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
if snapshot.config.settings.enable {
|
||||
diagnostics.extend(
|
||||
collection
|
||||
.diagnostics_for(&specifier, &DiagnosticSource::TypeScript)
|
||||
.cloned(),
|
||||
);
|
||||
diagnostics.extend(
|
||||
collection
|
||||
.diagnostics_for(&specifier, &DiagnosticSource::Deno)
|
||||
.cloned(),
|
||||
);
|
||||
}
|
||||
let uri = specifier.clone();
|
||||
let version = snapshot.documents.version(&specifier);
|
||||
client.publish_diagnostics(uri, diagnostics, version).await;
|
||||
}
|
||||
}
|
||||
|
||||
snapshot.performance.measure(mark);
|
||||
}
|
||||
|
||||
async fn update_diagnostics(
|
||||
client: &Client,
|
||||
collection: &mut DiagnosticCollection,
|
||||
snapshot: &language_server::StateSnapshot,
|
||||
ts_server: &tsc::TsServer,
|
||||
) {
|
||||
let (enabled, lint_enabled) = {
|
||||
let config = &snapshot.config;
|
||||
(config.settings.enable, config.settings.lint)
|
||||
};
|
||||
|
||||
let mark = snapshot.performance.mark("update_diagnostics");
|
||||
let lint = async {
|
||||
let mut diagnostics = None;
|
||||
if lint_enabled {
|
||||
let mark = snapshot.performance.mark("prepare_diagnostics_lint");
|
||||
diagnostics = Some(
|
||||
generate_lint_diagnostics(snapshot.clone(), collection.clone()).await,
|
||||
);
|
||||
snapshot.performance.measure(mark);
|
||||
};
|
||||
Ok::<_, AnyError>(diagnostics)
|
||||
};
|
||||
|
||||
let ts = async {
|
||||
let mut diagnostics = None;
|
||||
if enabled {
|
||||
let mark = snapshot.performance.mark("prepare_diagnostics_ts");
|
||||
diagnostics = Some(
|
||||
generate_ts_diagnostics(
|
||||
snapshot.clone(),
|
||||
collection.clone(),
|
||||
ts_server,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
snapshot.performance.measure(mark);
|
||||
};
|
||||
Ok::<_, AnyError>(diagnostics)
|
||||
};
|
||||
|
||||
let deps = async {
|
||||
let mut diagnostics = None;
|
||||
if enabled {
|
||||
let mark = snapshot.performance.mark("prepare_diagnostics_deps");
|
||||
diagnostics = Some(
|
||||
generate_dependency_diagnostics(snapshot.clone(), collection.clone())
|
||||
.await?,
|
||||
);
|
||||
snapshot.performance.measure(mark);
|
||||
};
|
||||
Ok::<_, AnyError>(diagnostics)
|
||||
};
|
||||
|
||||
let (lint_res, ts_res, deps_res) = tokio::join!(lint, ts, deps);
|
||||
let mut disturbed = false;
|
||||
|
||||
match lint_res {
|
||||
Ok(Some(diagnostics)) => {
|
||||
for (specifier, version, diagnostics) in diagnostics {
|
||||
collection.set(specifier, DiagnosticSource::Lint, version, diagnostics);
|
||||
disturbed = true;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Internal error: {}", err);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match ts_res {
|
||||
Ok(Some(diagnostics)) => {
|
||||
for (specifier, version, diagnostics) in diagnostics {
|
||||
collection.set(
|
||||
specifier,
|
||||
DiagnosticSource::TypeScript,
|
||||
version,
|
||||
diagnostics,
|
||||
);
|
||||
disturbed = true;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Internal error: {}", err);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match deps_res {
|
||||
Ok(Some(diagnostics)) => {
|
||||
for (specifier, version, diagnostics) in diagnostics {
|
||||
collection.set(specifier, DiagnosticSource::Deno, version, diagnostics);
|
||||
disturbed = true;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Internal error: {}", err);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
snapshot.performance.measure(mark);
|
||||
|
||||
if disturbed {
|
||||
publish_diagnostics(client, collection, snapshot).await
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
maybe_request: Option<DiagnosticRequest>,
|
||||
collection: &mut DiagnosticCollection,
|
||||
dirty: &mut bool,
|
||||
) -> bool {
|
||||
match maybe_request {
|
||||
Some(request) => {
|
||||
match request {
|
||||
DiagnosticRequest::Get(specifier, source, tx) => {
|
||||
let diagnostics = collection
|
||||
.diagnostics_for(&specifier, &source)
|
||||
.cloned()
|
||||
.collect();
|
||||
if tx.send(diagnostics).is_err() {
|
||||
error!("DiagnosticServer unable to send response on channel.");
|
||||
}
|
||||
}
|
||||
DiagnosticRequest::Invalidate(specifier) => {
|
||||
collection.invalidate(&specifier)
|
||||
}
|
||||
DiagnosticRequest::Update => *dirty = true,
|
||||
}
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// A server which calculates diagnostics in its own thread and publishes them
|
||||
/// to an LSP client.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DiagnosticsServer(
|
||||
Option<mpsc::UnboundedSender<DiagnosticRequest>>,
|
||||
);
|
||||
|
||||
impl DiagnosticsServer {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self(None)
|
||||
}
|
||||
|
||||
pub(crate) fn start(
|
||||
&mut self,
|
||||
language_server: Arc<tokio::sync::Mutex<language_server::Inner>>,
|
||||
client: Client,
|
||||
ts_server: Arc<tsc::TsServer>,
|
||||
) {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<DiagnosticRequest>();
|
||||
self.0 = Some(tx);
|
||||
|
||||
let _join_handle = thread::spawn(move || {
|
||||
let runtime = create_basic_runtime();
|
||||
let mut collection = DiagnosticCollection::default();
|
||||
|
||||
runtime.block_on(async {
|
||||
// Some(snapshot) is the flag we use to determine if something has
|
||||
// changed where we will wait for the timeout of changes or a request
|
||||
// that forces us to update diagnostics
|
||||
let mut dirty = false;
|
||||
|
||||
loop {
|
||||
let next = rx.recv();
|
||||
tokio::pin!(next);
|
||||
|
||||
let duration = if dirty {
|
||||
time::Duration::from_millis(DIAGNOSTIC_DEBOUNCE_MS)
|
||||
} else {
|
||||
// we need to await an arbitrary silly amount of time, so this is
|
||||
// 1 year in seconds
|
||||
time::Duration::new(31_622_400, 0)
|
||||
};
|
||||
|
||||
// "race" the next message off the rx queue or the debounce timer.
|
||||
// if the next message comes off the queue, the next iteration of the
|
||||
// loop will reset the debounce future. When the debounce future
|
||||
// occurs, the diagnostics will be updated based on the snapshot that
|
||||
// is retrieved, thereby "skipping" all the interim state changes.
|
||||
tokio::select! {
|
||||
_ = time::sleep(duration) => {
|
||||
if dirty {
|
||||
dirty = false;
|
||||
let snapshot = {
|
||||
// make sure the lock drops
|
||||
language_server.lock().await.snapshot()
|
||||
};
|
||||
update_diagnostics(
|
||||
&client,
|
||||
&mut collection,
|
||||
&snapshot,
|
||||
&ts_server
|
||||
).await;
|
||||
}
|
||||
let maybe_request = next.await;
|
||||
if !handle_request(maybe_request, &mut collection, &mut dirty) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
maybe_request = &mut next => {
|
||||
if !handle_request(maybe_request, &mut collection, &mut dirty) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
&self,
|
||||
specifier: ModuleSpecifier,
|
||||
source: DiagnosticSource,
|
||||
) -> Result<Vec<lsp::Diagnostic>, AnyError> {
|
||||
let (tx, rx) = oneshot::channel::<Vec<lsp::Diagnostic>>();
|
||||
if let Some(self_tx) = &self.0 {
|
||||
self_tx.send(DiagnosticRequest::Get(specifier, source, tx))?;
|
||||
rx.await.map_err(|err| err.into())
|
||||
} else {
|
||||
Err(anyhow!("diagnostic server not started"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalidate(&self, specifier: ModuleSpecifier) -> Result<(), AnyError> {
|
||||
if let Some(tx) = &self.0 {
|
||||
tx.send(DiagnosticRequest::Invalidate(specifier))
|
||||
.map_err(|err| err.into())
|
||||
} else {
|
||||
Err(anyhow!("diagnostic server not started"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&self) -> Result<(), AnyError> {
|
||||
if let Some(tx) = &self.0 {
|
||||
tx.send(DiagnosticRequest::Update).map_err(|err| err.into())
|
||||
} else {
|
||||
Err(anyhow!("diagnostic server not started"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DiagnosticCollection {
|
||||
struct DiagnosticCollection {
|
||||
map: HashMap<(ModuleSpecifier, DiagnosticSource), Vec<lsp::Diagnostic>>,
|
||||
versions: HashMap<ModuleSpecifier, i32>,
|
||||
changes: HashSet<ModuleSpecifier>,
|
||||
|
@ -78,16 +394,16 @@ impl DiagnosticCollection {
|
|||
pub type DiagnosticVec =
|
||||
Vec<(ModuleSpecifier, Option<i32>, Vec<lsp::Diagnostic>)>;
|
||||
|
||||
pub async fn generate_lint_diagnostics(
|
||||
state_snapshot: StateSnapshot,
|
||||
diagnostic_collection: DiagnosticCollection,
|
||||
async fn generate_lint_diagnostics(
|
||||
state_snapshot: language_server::StateSnapshot,
|
||||
collection: DiagnosticCollection,
|
||||
) -> DiagnosticVec {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut diagnostic_list = Vec::new();
|
||||
|
||||
for specifier in state_snapshot.documents.open_specifiers() {
|
||||
let version = state_snapshot.documents.version(specifier);
|
||||
let current_version = diagnostic_collection.get_version(specifier);
|
||||
let current_version = collection.get_version(specifier);
|
||||
if version != current_version {
|
||||
let media_type = MediaType::from(specifier);
|
||||
if let Ok(Some(source_code)) =
|
||||
|
@ -229,16 +545,16 @@ fn ts_json_to_diagnostics(
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub async fn generate_ts_diagnostics(
|
||||
state_snapshot: StateSnapshot,
|
||||
diagnostic_collection: DiagnosticCollection,
|
||||
async fn generate_ts_diagnostics(
|
||||
state_snapshot: language_server::StateSnapshot,
|
||||
collection: DiagnosticCollection,
|
||||
ts_server: &tsc::TsServer,
|
||||
) -> Result<DiagnosticVec, AnyError> {
|
||||
let mut diagnostics = Vec::new();
|
||||
let mut specifiers = Vec::new();
|
||||
for specifier in state_snapshot.documents.open_specifiers() {
|
||||
let version = state_snapshot.documents.version(specifier);
|
||||
let current_version = diagnostic_collection.get_version(specifier);
|
||||
let current_version = collection.get_version(specifier);
|
||||
if version != current_version {
|
||||
specifiers.push(specifier.clone());
|
||||
}
|
||||
|
@ -260,9 +576,9 @@ pub async fn generate_ts_diagnostics(
|
|||
Ok(diagnostics)
|
||||
}
|
||||
|
||||
pub async fn generate_dependency_diagnostics(
|
||||
mut state_snapshot: StateSnapshot,
|
||||
diagnostic_collection: DiagnosticCollection,
|
||||
async fn generate_dependency_diagnostics(
|
||||
mut state_snapshot: language_server::StateSnapshot,
|
||||
collection: DiagnosticCollection,
|
||||
) -> Result<DiagnosticVec, AnyError> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut diagnostics = Vec::new();
|
||||
|
@ -270,7 +586,7 @@ pub async fn generate_dependency_diagnostics(
|
|||
let sources = &mut state_snapshot.sources;
|
||||
for specifier in state_snapshot.documents.open_specifiers() {
|
||||
let version = state_snapshot.documents.version(specifier);
|
||||
let current_version = diagnostic_collection.get_version(specifier);
|
||||
let current_version = collection.get_version(specifier);
|
||||
if version != current_version {
|
||||
let mut diagnostic_list = Vec::new();
|
||||
if let Some(dependencies) = state_snapshot.documents.dependencies(specifier) {
|
||||
|
|
|
@ -41,7 +41,6 @@ use super::analysis::ResolvedDependency;
|
|||
use super::capabilities;
|
||||
use super::config::Config;
|
||||
use super::diagnostics;
|
||||
use super::diagnostics::DiagnosticCollection;
|
||||
use super::diagnostics::DiagnosticSource;
|
||||
use super::documents::DocumentCache;
|
||||
use super::performance::Performance;
|
||||
|
@ -66,6 +65,7 @@ pub struct LanguageServer(Arc<tokio::sync::Mutex<Inner>>);
|
|||
#[derive(Debug, Clone, Default)]
|
||||
pub struct StateSnapshot {
|
||||
pub assets: Assets,
|
||||
pub config: Config,
|
||||
pub documents: DocumentCache,
|
||||
pub performance: Performance,
|
||||
pub sources: Sources,
|
||||
|
@ -80,8 +80,7 @@ pub(crate) struct Inner {
|
|||
client: Client,
|
||||
/// Configuration information.
|
||||
config: Config,
|
||||
/// A collection of diagnostics from different sources.
|
||||
diagnostics: DiagnosticCollection,
|
||||
diagnostics_server: diagnostics::DiagnosticsServer,
|
||||
/// The "in-memory" documents in the editor which can be updated and changed.
|
||||
documents: DocumentCache,
|
||||
/// An optional URL which provides the location of a TypeScript configuration
|
||||
|
@ -100,7 +99,7 @@ pub(crate) struct Inner {
|
|||
/// A memoized version of fixable diagnostic codes retrieved from TypeScript.
|
||||
ts_fixable_diagnostics: Vec<String>,
|
||||
/// An abstraction that handles interactions with TypeScript.
|
||||
ts_server: TsServer,
|
||||
ts_server: Arc<TsServer>,
|
||||
/// A map of specifiers and URLs used to translate over the LSP.
|
||||
pub url_map: urls::LspUrlMap,
|
||||
}
|
||||
|
@ -118,21 +117,24 @@ impl Inner {
|
|||
.expect("could not access DENO_DIR");
|
||||
let location = dir.root.join("deps");
|
||||
let sources = Sources::new(&location);
|
||||
let ts_server = Arc::new(TsServer::new());
|
||||
let performance = Performance::default();
|
||||
let diagnostics_server = diagnostics::DiagnosticsServer::new();
|
||||
|
||||
Self {
|
||||
assets: Default::default(),
|
||||
client,
|
||||
config: Default::default(),
|
||||
diagnostics: Default::default(),
|
||||
diagnostics_server,
|
||||
documents: Default::default(),
|
||||
maybe_config_uri: Default::default(),
|
||||
maybe_import_map: Default::default(),
|
||||
maybe_import_map_uri: Default::default(),
|
||||
navigation_trees: Default::default(),
|
||||
performance: Default::default(),
|
||||
performance,
|
||||
sources,
|
||||
ts_fixable_diagnostics: Default::default(),
|
||||
ts_server: TsServer::new(),
|
||||
ts_server,
|
||||
url_map: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -242,157 +244,10 @@ impl Inner {
|
|||
}
|
||||
}
|
||||
|
||||
async fn prepare_diagnostics(&mut self) -> Result<(), AnyError> {
|
||||
let (enabled, lint_enabled) = {
|
||||
let config = &self.config;
|
||||
(config.settings.enable, config.settings.lint)
|
||||
};
|
||||
|
||||
let lint = async {
|
||||
let mut diagnostics = None;
|
||||
if lint_enabled {
|
||||
let mark = self.performance.mark("prepare_diagnostics_lint");
|
||||
diagnostics = Some(
|
||||
diagnostics::generate_lint_diagnostics(
|
||||
self.snapshot(),
|
||||
self.diagnostics.clone(),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
self.performance.measure(mark);
|
||||
};
|
||||
Ok::<_, AnyError>(diagnostics)
|
||||
};
|
||||
|
||||
let ts = async {
|
||||
let mut diagnostics = None;
|
||||
if enabled {
|
||||
let mark = self.performance.mark("prepare_diagnostics_ts");
|
||||
diagnostics = Some(
|
||||
diagnostics::generate_ts_diagnostics(
|
||||
self.snapshot(),
|
||||
self.diagnostics.clone(),
|
||||
&self.ts_server,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
self.performance.measure(mark);
|
||||
};
|
||||
Ok::<_, AnyError>(diagnostics)
|
||||
};
|
||||
|
||||
let deps = async {
|
||||
let mut diagnostics = None;
|
||||
if enabled {
|
||||
let mark = self.performance.mark("prepare_diagnostics_deps");
|
||||
diagnostics = Some(
|
||||
diagnostics::generate_dependency_diagnostics(
|
||||
self.snapshot(),
|
||||
self.diagnostics.clone(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
self.performance.measure(mark);
|
||||
};
|
||||
Ok::<_, AnyError>(diagnostics)
|
||||
};
|
||||
|
||||
let (lint_res, ts_res, deps_res) = tokio::join!(lint, ts, deps);
|
||||
let mut disturbed = false;
|
||||
|
||||
if let Some(diagnostics) = lint_res? {
|
||||
for (specifier, version, diagnostics) in diagnostics {
|
||||
self.diagnostics.set(
|
||||
specifier,
|
||||
DiagnosticSource::Lint,
|
||||
version,
|
||||
diagnostics,
|
||||
);
|
||||
disturbed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(diagnostics) = ts_res? {
|
||||
for (specifier, version, diagnostics) in diagnostics {
|
||||
self.diagnostics.set(
|
||||
specifier,
|
||||
DiagnosticSource::TypeScript,
|
||||
version,
|
||||
diagnostics,
|
||||
);
|
||||
disturbed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(diagnostics) = deps_res? {
|
||||
for (specifier, version, diagnostics) in diagnostics {
|
||||
self.diagnostics.set(
|
||||
specifier,
|
||||
DiagnosticSource::Deno,
|
||||
version,
|
||||
diagnostics,
|
||||
);
|
||||
disturbed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if disturbed {
|
||||
self.publish_diagnostics().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn publish_diagnostics(&mut self) -> Result<(), AnyError> {
|
||||
let mark = self.performance.mark("publish_diagnostics");
|
||||
let (maybe_changes, diagnostics_collection) = {
|
||||
let diagnostics_collection = &mut self.diagnostics;
|
||||
let maybe_changes = diagnostics_collection.take_changes();
|
||||
(maybe_changes, diagnostics_collection.clone())
|
||||
};
|
||||
if let Some(diagnostic_changes) = maybe_changes {
|
||||
for specifier in diagnostic_changes {
|
||||
// TODO(@kitsonk) not totally happy with the way we collect and store
|
||||
// different types of diagnostics and offer them up to the client, we
|
||||
// do need to send "empty" vectors though when a particular feature is
|
||||
// disabled, otherwise the client will not clear down previous
|
||||
// diagnostics
|
||||
let mut diagnostics: Vec<Diagnostic> = if self.config.settings.lint {
|
||||
diagnostics_collection
|
||||
.diagnostics_for(&specifier, &DiagnosticSource::Lint)
|
||||
.cloned()
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
if self.enabled() {
|
||||
diagnostics.extend(
|
||||
diagnostics_collection
|
||||
.diagnostics_for(&specifier, &DiagnosticSource::TypeScript)
|
||||
.cloned(),
|
||||
);
|
||||
diagnostics.extend(
|
||||
diagnostics_collection
|
||||
.diagnostics_for(&specifier, &DiagnosticSource::Deno)
|
||||
.cloned(),
|
||||
);
|
||||
}
|
||||
let uri = specifier.clone();
|
||||
let version = self.documents.version(&specifier);
|
||||
self
|
||||
.client
|
||||
.publish_diagnostics(uri, diagnostics, version)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
self.performance.measure(mark);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> StateSnapshot {
|
||||
pub(crate) fn snapshot(&self) -> StateSnapshot {
|
||||
StateSnapshot {
|
||||
assets: self.assets.clone(),
|
||||
config: self.config.clone(),
|
||||
documents: self.documents.clone(),
|
||||
performance: self.performance.clone(),
|
||||
sources: self.sources.clone(),
|
||||
|
@ -667,8 +522,7 @@ impl Inner {
|
|||
self.analyze_dependencies(&specifier, ¶ms.text_document.text);
|
||||
self.performance.measure(mark);
|
||||
|
||||
// TODO(@kitsonk): how to better lazily do this?
|
||||
if let Err(err) = self.prepare_diagnostics().await {
|
||||
if let Err(err) = self.diagnostics_server.update() {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
|
@ -687,8 +541,7 @@ impl Inner {
|
|||
}
|
||||
self.performance.measure(mark);
|
||||
|
||||
// TODO(@kitsonk): how to better lazily do this?
|
||||
if let Err(err) = self.prepare_diagnostics().await {
|
||||
if let Err(err) = self.diagnostics_server.update() {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
|
@ -706,8 +559,7 @@ impl Inner {
|
|||
self.navigation_trees.remove(&specifier);
|
||||
|
||||
self.performance.measure(mark);
|
||||
// TODO(@kitsonk): how to better lazily do this?
|
||||
if let Err(err) = self.prepare_diagnostics().await {
|
||||
if let Err(err) = self.diagnostics_server.update() {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
|
@ -755,7 +607,7 @@ impl Inner {
|
|||
.show_message(MessageType::Warning, err.to_string())
|
||||
.await;
|
||||
}
|
||||
if let Err(err) = self.prepare_diagnostics().await {
|
||||
if let Err(err) = self.diagnostics_server.update() {
|
||||
error!("{}", err);
|
||||
}
|
||||
} else {
|
||||
|
@ -931,11 +783,14 @@ impl Inner {
|
|||
}
|
||||
let line_index = self.get_line_index_sync(&specifier).unwrap();
|
||||
let mut code_actions = CodeActionCollection::default();
|
||||
let file_diagnostics: Vec<Diagnostic> = self
|
||||
.diagnostics
|
||||
.diagnostics_for(&specifier, &DiagnosticSource::TypeScript)
|
||||
.cloned()
|
||||
.collect();
|
||||
let file_diagnostics = self
|
||||
.diagnostics_server
|
||||
.get(specifier.clone(), DiagnosticSource::TypeScript)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Unable to get diagnostics: {}", err);
|
||||
LspError::internal_error()
|
||||
})?;
|
||||
for diagnostic in &fixable_diagnostics {
|
||||
match diagnostic.source.as_deref() {
|
||||
Some("deno-ts") => {
|
||||
|
@ -1748,7 +1603,13 @@ impl lspower::LanguageServer for LanguageServer {
|
|||
&self,
|
||||
params: InitializeParams,
|
||||
) -> LspResult<InitializeResult> {
|
||||
self.0.lock().await.initialize(params).await
|
||||
let mut language_server = self.0.lock().await;
|
||||
let client = language_server.client.clone();
|
||||
let ts_server = language_server.ts_server.clone();
|
||||
language_server
|
||||
.diagnostics_server
|
||||
.start(self.0.clone(), client, ts_server);
|
||||
language_server.initialize(params).await
|
||||
}
|
||||
|
||||
async fn initialized(&self, params: InitializedParams) {
|
||||
|
@ -1932,10 +1793,16 @@ impl Inner {
|
|||
if let Some(source) = self.documents.content(&referrer).unwrap() {
|
||||
self.analyze_dependencies(&referrer, &source);
|
||||
}
|
||||
self.diagnostics.invalidate(&referrer);
|
||||
self
|
||||
.diagnostics_server
|
||||
.invalidate(referrer)
|
||||
.map_err(|err| {
|
||||
error!("{}", err);
|
||||
LspError::internal_error()
|
||||
})?;
|
||||
}
|
||||
|
||||
self.prepare_diagnostics().await.map_err(|err| {
|
||||
self.diagnostics_server.update().map_err(|err| {
|
||||
error!("{}", err);
|
||||
LspError::internal_error()
|
||||
})?;
|
||||
|
@ -2018,6 +1885,7 @@ mod tests {
|
|||
V: FnOnce(Value),
|
||||
{
|
||||
None,
|
||||
Delay(u64),
|
||||
RequestAny,
|
||||
Request(u64, Value),
|
||||
RequestAssert(V),
|
||||
|
@ -2043,14 +1911,23 @@ mod tests {
|
|||
assert_eq!(self.service.poll_ready(), Poll::Ready(Ok(())));
|
||||
let fixtures_path = test_util::root_path().join("cli/tests/lsp");
|
||||
assert!(fixtures_path.is_dir());
|
||||
let req_path = fixtures_path.join(req_path_str);
|
||||
let req_str = fs::read_to_string(req_path).unwrap();
|
||||
let req: jsonrpc::Incoming = serde_json::from_str(&req_str).unwrap();
|
||||
let response: Result<Option<jsonrpc::Outgoing>, ExitedError> =
|
||||
self.service.call(req).await;
|
||||
if req_path_str.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
let req_path = fixtures_path.join(req_path_str);
|
||||
let req_str = fs::read_to_string(req_path).unwrap();
|
||||
let req: jsonrpc::Incoming =
|
||||
serde_json::from_str(&req_str).unwrap();
|
||||
self.service.call(req).await
|
||||
};
|
||||
match response {
|
||||
Ok(result) => match expected {
|
||||
LspResponse::None => assert_eq!(result, None),
|
||||
LspResponse::Delay(millis) => {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(*millis))
|
||||
.await
|
||||
}
|
||||
LspResponse::RequestAny => match result {
|
||||
Some(jsonrpc::Outgoing::Response(_)) => (),
|
||||
_ => panic!("unexpected result: {:?}", result),
|
||||
|
@ -2296,18 +2173,18 @@ mod tests {
|
|||
"contents": [
|
||||
{
|
||||
"language": "typescript",
|
||||
"value": "const b: \"😃\"",
|
||||
"value": "const b: \"🦕😃\"",
|
||||
},
|
||||
"",
|
||||
],
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 2,
|
||||
"character": 13,
|
||||
"character": 15,
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"character": 14,
|
||||
"character": 16,
|
||||
},
|
||||
}
|
||||
}),
|
||||
|
@ -2418,7 +2295,7 @@ mod tests {
|
|||
let time = Instant::now();
|
||||
harness.run().await;
|
||||
assert!(
|
||||
time.elapsed().as_millis() <= 15000,
|
||||
time.elapsed().as_millis() <= 10000,
|
||||
"the execution time exceeded 10000ms"
|
||||
);
|
||||
}
|
||||
|
@ -2820,6 +2697,7 @@ mod tests {
|
|||
("initialize_request.json", LspResponse::RequestAny),
|
||||
("initialized_notification.json", LspResponse::None),
|
||||
("did_open_notification_code_action.json", LspResponse::None),
|
||||
("", LspResponse::Delay(500)),
|
||||
(
|
||||
"code_action_request.json",
|
||||
LspResponse::RequestFixture(2, "code_action_response.json".to_string()),
|
||||
|
@ -2907,7 +2785,10 @@ mod tests {
|
|||
LspResponse::RequestAssert(|value| {
|
||||
let resp: PerformanceResponse =
|
||||
serde_json::from_value(value).unwrap();
|
||||
assert_eq!(resp.result.averages.len(), 12);
|
||||
// the len can be variable since some of the parts of the language
|
||||
// server run in separate threads and may not add to performance by
|
||||
// the time the results are checked.
|
||||
assert!(resp.result.averages.len() >= 6);
|
||||
}),
|
||||
),
|
||||
(
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"position": {
|
||||
"line": 2,
|
||||
"character": 14
|
||||
"character": 15
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue