mirror of
https://github.com/denoland/deno.git
synced 2025-02-08 15:21:26 -05:00
![Kitson Kelly](/assets/img/avatar_default.png)
Closes #11826 **BREAKING CHANGE** this behaviour was disable when introduced in Deno 1.14/TypeScript 4.4. It will highlight code that unsafely handles variables that are caught, and will cause type errors in unsafe code.
2830 lines
87 KiB
Rust
2830 lines
87 KiB
Rust
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use deno_core::error::anyhow;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::resolve_url;
|
|
use deno_core::serde_json;
|
|
use deno_core::serde_json::json;
|
|
use deno_core::serde_json::Value;
|
|
use deno_core::ModuleSpecifier;
|
|
use import_map::ImportMap;
|
|
use log::error;
|
|
use log::info;
|
|
use log::warn;
|
|
use lspower::jsonrpc::Error as LspError;
|
|
use lspower::jsonrpc::Result as LspResult;
|
|
use lspower::lsp::request::*;
|
|
use lspower::lsp::*;
|
|
use lspower::Client;
|
|
use serde_json::from_value;
|
|
use std::env;
|
|
use std::path::PathBuf;
|
|
use std::sync::atomic::Ordering;
|
|
use std::sync::Arc;
|
|
use tokio::fs;
|
|
|
|
use super::analysis;
|
|
use super::analysis::fix_ts_import_changes;
|
|
use super::analysis::ts_changes_to_edit;
|
|
use super::analysis::CodeActionCollection;
|
|
use super::analysis::CodeActionData;
|
|
use super::analysis::ResolvedDependency;
|
|
use super::cache::CacheServer;
|
|
use super::capabilities;
|
|
use super::code_lens;
|
|
use super::completions;
|
|
use super::config::Config;
|
|
use super::config::ConfigSnapshot;
|
|
use super::config::SETTINGS_SECTION;
|
|
use super::diagnostics;
|
|
use super::diagnostics::DiagnosticSource;
|
|
use super::documents::DocumentCache;
|
|
use super::documents::LanguageId;
|
|
use super::lsp_custom;
|
|
use super::parent_process_checker;
|
|
use super::performance::Performance;
|
|
use super::refactor;
|
|
use super::registries;
|
|
use super::sources::Sources;
|
|
use super::text;
|
|
use super::text::LineIndex;
|
|
use super::tsc;
|
|
use super::tsc::AssetDocument;
|
|
use super::tsc::Assets;
|
|
use super::tsc::TsServer;
|
|
use super::urls;
|
|
use crate::config_file::ConfigFile;
|
|
use crate::config_file::FmtConfig;
|
|
use crate::config_file::LintConfig;
|
|
use crate::config_file::TsConfig;
|
|
use crate::deno_dir;
|
|
use crate::file_fetcher::get_source_from_data_url;
|
|
use crate::fs_util;
|
|
use crate::logger;
|
|
use crate::tools::fmt::format_file;
|
|
use crate::tools::fmt::format_parsed_source;
|
|
|
|
pub const REGISTRIES_PATH: &str = "registries";
|
|
const SOURCES_PATH: &str = "deps";
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct LanguageServer(Arc<tokio::sync::Mutex<Inner>>);
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct StateSnapshot {
|
|
pub assets: Assets,
|
|
pub config: ConfigSnapshot,
|
|
pub documents: DocumentCache,
|
|
pub maybe_lint_config: Option<LintConfig>,
|
|
pub maybe_fmt_config: Option<FmtConfig>,
|
|
pub maybe_config_uri: Option<ModuleSpecifier>,
|
|
pub module_registries: registries::ModuleRegistry,
|
|
pub performance: Performance,
|
|
pub sources: Sources,
|
|
pub url_map: urls::LspUrlMap,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct Inner {
|
|
/// Cached versions of "fixed" assets that can either be inlined in Rust or
|
|
/// are part of the TypeScript snapshot and have to be fetched out.
|
|
assets: Assets,
|
|
/// The LSP client that this LSP server is connected to.
|
|
pub(crate) client: Client,
|
|
/// Configuration information.
|
|
pub(crate) config: Config,
|
|
diagnostics_server: diagnostics::DiagnosticsServer,
|
|
/// The "in-memory" documents in the editor which can be updated and changed.
|
|
documents: DocumentCache,
|
|
/// Handles module registries, which allow discovery of modules
|
|
module_registries: registries::ModuleRegistry,
|
|
/// The path to the module registries cache
|
|
module_registries_location: PathBuf,
|
|
/// An optional path to the DENO_DIR which has been specified in the client
|
|
/// options.
|
|
maybe_cache_path: Option<PathBuf>,
|
|
/// A lazily created "server" for handling cache requests.
|
|
maybe_cache_server: Option<CacheServer>,
|
|
/// An optional configuration file which has been specified in the client
|
|
/// options.
|
|
maybe_config_file: Option<ConfigFile>,
|
|
/// An optional configuration for linter which has been taken from specified config file.
|
|
maybe_lint_config: Option<LintConfig>,
|
|
/// An optional configuration for formatter which has been taken from specified config file.
|
|
maybe_fmt_config: Option<FmtConfig>,
|
|
/// An optional URL which provides the location of a TypeScript configuration
|
|
/// file which will be used by the Deno LSP.
|
|
maybe_config_uri: Option<Url>,
|
|
/// An optional import map which is used to resolve modules.
|
|
pub(crate) maybe_import_map: Option<ImportMap>,
|
|
/// The URL for the import map which is used to determine relative imports.
|
|
maybe_import_map_uri: Option<Url>,
|
|
/// A collection of measurements which instrument that performance of the LSP.
|
|
performance: Performance,
|
|
/// Cached sources that are read-only.
|
|
sources: Sources,
|
|
/// A memoized version of fixable diagnostic codes retrieved from TypeScript.
|
|
ts_fixable_diagnostics: Vec<String>,
|
|
/// An abstraction that handles interactions with TypeScript.
|
|
pub(crate) ts_server: Arc<TsServer>,
|
|
/// A map of specifiers and URLs used to translate over the LSP.
|
|
pub(crate) url_map: urls::LspUrlMap,
|
|
}
|
|
|
|
impl LanguageServer {
|
|
pub fn new(client: Client) -> Self {
|
|
Self(Arc::new(tokio::sync::Mutex::new(Inner::new(client))))
|
|
}
|
|
}
|
|
|
|
impl Inner {
|
|
fn new(client: Client) -> Self {
|
|
let maybe_custom_root = env::var("DENO_DIR").map(String::into).ok();
|
|
let dir = deno_dir::DenoDir::new(maybe_custom_root)
|
|
.expect("could not access DENO_DIR");
|
|
let module_registries_location = dir.root.join(REGISTRIES_PATH);
|
|
let module_registries =
|
|
registries::ModuleRegistry::new(&module_registries_location);
|
|
let sources_location = dir.root.join(SOURCES_PATH);
|
|
let sources = Sources::new(&sources_location);
|
|
let ts_server = Arc::new(TsServer::new());
|
|
let performance = Performance::default();
|
|
let diagnostics_server = diagnostics::DiagnosticsServer::new();
|
|
let config = Config::new(client.clone());
|
|
|
|
Self {
|
|
assets: Default::default(),
|
|
client,
|
|
config,
|
|
diagnostics_server,
|
|
documents: Default::default(),
|
|
maybe_cache_path: None,
|
|
maybe_lint_config: None,
|
|
maybe_fmt_config: None,
|
|
maybe_cache_server: None,
|
|
maybe_config_file: None,
|
|
maybe_config_uri: None,
|
|
maybe_import_map: None,
|
|
maybe_import_map_uri: None,
|
|
module_registries,
|
|
module_registries_location,
|
|
performance,
|
|
sources,
|
|
ts_fixable_diagnostics: Default::default(),
|
|
ts_server,
|
|
url_map: Default::default(),
|
|
}
|
|
}
|
|
|
|
/// Analyzes dependencies of a document that has been opened in the editor and
|
|
/// sets the dependencies property on the document.
|
|
fn analyze_dependencies(&mut self, specifier: &ModuleSpecifier) {
|
|
if let Some(Ok(parsed_module)) = self
|
|
.documents
|
|
.get(specifier)
|
|
.map(|d| d.source().module())
|
|
.flatten()
|
|
{
|
|
let (mut deps, _) = analysis::analyze_dependencies(
|
|
specifier,
|
|
parsed_module.media_type(),
|
|
parsed_module,
|
|
&self.maybe_import_map,
|
|
);
|
|
for (_, dep) in deps.iter_mut() {
|
|
if dep.maybe_type.is_none() {
|
|
if let Some(ResolvedDependency::Resolved(resolved)) = &dep.maybe_code
|
|
{
|
|
dep.maybe_type = self.sources.get_maybe_types(resolved);
|
|
}
|
|
}
|
|
}
|
|
let dep_ranges = analysis::analyze_dependency_ranges(parsed_module).ok();
|
|
if let Err(err) =
|
|
self
|
|
.documents
|
|
.set_dependencies(specifier, Some(deps), dep_ranges)
|
|
{
|
|
error!("{}", err);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Analyzes all dependencies for all documents that have been opened in the
|
|
/// editor and sets the dependencies property on the documents.
|
|
fn analyze_dependencies_all(&mut self) {
|
|
let specifiers = self
|
|
.documents
|
|
.docs
|
|
.keys()
|
|
.map(ToOwned::to_owned)
|
|
.collect::<Vec<_>>();
|
|
for specifier in specifiers {
|
|
self.analyze_dependencies(&specifier);
|
|
}
|
|
}
|
|
|
|
/// Searches assets, open documents and external sources for a line_index,
|
|
/// which might be performed asynchronously, hydrating in memory caches for
|
|
/// subsequent requests.
|
|
pub(crate) async fn get_line_index(
|
|
&mut self,
|
|
specifier: ModuleSpecifier,
|
|
) -> Result<LineIndex, AnyError> {
|
|
let mark = self
|
|
.performance
|
|
.mark("get_line_index", Some(json!({ "specifier": specifier })));
|
|
let result = if specifier.scheme() == "asset" {
|
|
if let Some(asset) = self.get_asset(&specifier).await? {
|
|
Ok(asset.line_index)
|
|
} else {
|
|
Err(anyhow!("asset is missing: {}", specifier))
|
|
}
|
|
} else if let Some(line_index) = self.documents.line_index(&specifier) {
|
|
Ok(line_index)
|
|
} else if let Some(line_index) = self.sources.get_line_index(&specifier) {
|
|
Ok(line_index)
|
|
} else {
|
|
Err(anyhow!("Unable to find line index for: {}", specifier))
|
|
};
|
|
self.performance.measure(mark);
|
|
result
|
|
}
|
|
|
|
/// Only searches already cached assets and documents for a line index. If
|
|
/// the line index cannot be found, `None` is returned.
|
|
pub fn get_line_index_sync(
|
|
&self,
|
|
specifier: &ModuleSpecifier,
|
|
) -> Option<LineIndex> {
|
|
let mark = self.performance.mark(
|
|
"get_line_index_sync",
|
|
Some(json!({ "specifier": specifier })),
|
|
);
|
|
let maybe_line_index = if specifier.scheme() == "asset" {
|
|
if let Some(Some(asset)) = self.assets.get(specifier) {
|
|
Some(asset.line_index.clone())
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
let documents = &self.documents;
|
|
if documents.contains_key(specifier) {
|
|
documents.line_index(specifier)
|
|
} else {
|
|
self.sources.get_line_index(specifier)
|
|
}
|
|
};
|
|
self.performance.measure(mark);
|
|
maybe_line_index
|
|
}
|
|
|
|
// TODO(@kitsonk) we really should find a better way to just return the
|
|
// content as a `&str`, or be able to get the byte at a particular offset
|
|
// which is all that this API that is consuming it is trying to do at the
|
|
// moment
|
|
/// Searches already cached assets and documents and returns its text
|
|
/// content. If not found, `None` is returned.
|
|
pub(crate) fn get_text_content(
|
|
&self,
|
|
specifier: &ModuleSpecifier,
|
|
) -> Option<Arc<String>> {
|
|
if specifier.scheme() == "asset" {
|
|
self
|
|
.assets
|
|
.get(specifier)
|
|
.map(|o| o.clone().map(|a| a.text))?
|
|
} else if self.documents.contains_key(specifier) {
|
|
self.documents.content(specifier)
|
|
} else {
|
|
self.sources.get_source(specifier)
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn get_navigation_tree(
|
|
&mut self,
|
|
specifier: &ModuleSpecifier,
|
|
) -> Result<tsc::NavigationTree, AnyError> {
|
|
let mark = self.performance.mark(
|
|
"get_navigation_tree",
|
|
Some(json!({ "specifier": specifier })),
|
|
);
|
|
let maybe_navigation_tree = if specifier.scheme() == "asset" {
|
|
self
|
|
.assets
|
|
.get(specifier)
|
|
.map(|o| o.clone().map(|a| a.maybe_navigation_tree).flatten())
|
|
.flatten()
|
|
} else if self.documents.contains_key(specifier) {
|
|
self.documents.get_navigation_tree(specifier)
|
|
} else {
|
|
self.sources.get_navigation_tree(specifier)
|
|
};
|
|
let navigation_tree = if let Some(navigation_tree) = maybe_navigation_tree {
|
|
navigation_tree
|
|
} else {
|
|
let navigation_tree: tsc::NavigationTree = self
|
|
.ts_server
|
|
.request(
|
|
self.snapshot()?,
|
|
tsc::RequestMethod::GetNavigationTree(specifier.clone()),
|
|
)
|
|
.await?;
|
|
if specifier.scheme() == "asset" {
|
|
self
|
|
.assets
|
|
.set_navigation_tree(specifier, navigation_tree.clone())?;
|
|
} else if self.documents.contains_key(specifier) {
|
|
self
|
|
.documents
|
|
.set_navigation_tree(specifier, navigation_tree.clone())?;
|
|
} else {
|
|
self
|
|
.sources
|
|
.set_navigation_tree(specifier, navigation_tree.clone())?;
|
|
}
|
|
navigation_tree
|
|
};
|
|
self.performance.measure(mark);
|
|
Ok(navigation_tree)
|
|
}
|
|
|
|
/// Returns a tuple with parsed `ConfigFile` and `Url` pointing to that file.
|
|
/// If there's no config file specified in settings returns `None`.
|
|
fn get_config_file_and_url(
|
|
&self,
|
|
) -> Result<Option<(ConfigFile, Url)>, AnyError> {
|
|
let workspace_settings = self.config.get_workspace_settings();
|
|
let maybe_root_uri = self.config.root_uri.clone();
|
|
let maybe_config = workspace_settings.config;
|
|
if let Some(config_str) = &maybe_config {
|
|
if !config_str.is_empty() {
|
|
info!("Setting TypeScript configuration from: \"{}\"", config_str);
|
|
let config_url = if let Ok(url) = Url::from_file_path(config_str) {
|
|
Ok(url)
|
|
} else if let Some(root_uri) = maybe_root_uri {
|
|
let root_path = root_uri
|
|
.to_file_path()
|
|
.map_err(|_| anyhow!("Bad root_uri: {}", root_uri))?;
|
|
let config_path = root_path.join(config_str);
|
|
Url::from_file_path(config_path).map_err(|_| {
|
|
anyhow!("Bad file path for configuration file: \"{}\"", config_str)
|
|
})
|
|
} else {
|
|
Err(anyhow!(
|
|
"The path to the configuration file (\"{}\") is not resolvable.",
|
|
config_str
|
|
))
|
|
}?;
|
|
info!(" Resolved configuration file: \"{}\"", config_url);
|
|
|
|
let config_file = {
|
|
let buffer = config_url
|
|
.to_file_path()
|
|
.map_err(|_| anyhow!("Bad uri: \"{}\"", config_url))?;
|
|
let path = buffer
|
|
.to_str()
|
|
.ok_or_else(|| anyhow!("Bad uri: \"{}\"", config_url))?;
|
|
ConfigFile::read(path)?
|
|
};
|
|
return Ok(Some((config_file, config_url)));
|
|
}
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
|
|
fn merge_user_tsconfig(
|
|
&mut self,
|
|
tsconfig: &mut TsConfig,
|
|
) -> Result<(), AnyError> {
|
|
if let Some(config_file) = self.maybe_config_file.as_ref() {
|
|
let (value, maybe_ignored_options) = config_file.to_compiler_options()?;
|
|
tsconfig.merge(&value);
|
|
if let Some(ignored_options) = maybe_ignored_options {
|
|
// TODO(@kitsonk) turn these into diagnostics that can be sent to the
|
|
// client
|
|
warn!("{}", ignored_options);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn snapshot(&self) -> LspResult<StateSnapshot> {
|
|
Ok(StateSnapshot {
|
|
assets: self.assets.clone(),
|
|
config: self.config.snapshot().map_err(|err| {
|
|
error!("{}", err);
|
|
LspError::internal_error()
|
|
})?,
|
|
documents: self.documents.clone(),
|
|
maybe_lint_config: self.maybe_lint_config.clone(),
|
|
maybe_fmt_config: self.maybe_fmt_config.clone(),
|
|
maybe_config_uri: self.maybe_config_uri.clone(),
|
|
module_registries: self.module_registries.clone(),
|
|
performance: self.performance.clone(),
|
|
sources: self.sources.clone(),
|
|
url_map: self.url_map.clone(),
|
|
})
|
|
}
|
|
|
|
pub fn update_cache(&mut self) -> Result<(), AnyError> {
|
|
let mark = self.performance.mark("update_cache", None::<()>);
|
|
self.performance.measure(mark);
|
|
self.maybe_cache_server = None;
|
|
let (maybe_cache, maybe_root_uri) = {
|
|
let config = &self.config;
|
|
(
|
|
config.get_workspace_settings().cache,
|
|
config.root_uri.clone(),
|
|
)
|
|
};
|
|
let maybe_cache_path = if let Some(cache_str) = &maybe_cache {
|
|
info!("Setting cache path from: \"{}\"", cache_str);
|
|
let cache_url = if let Ok(url) = Url::from_file_path(cache_str) {
|
|
Ok(url)
|
|
} else if let Some(root_uri) = &maybe_root_uri {
|
|
let root_path = root_uri
|
|
.to_file_path()
|
|
.map_err(|_| anyhow!("Bad root_uri: {}", root_uri))?;
|
|
let cache_path = root_path.join(cache_str);
|
|
Url::from_file_path(cache_path).map_err(|_| {
|
|
anyhow!("Bad file path for import path: {:?}", cache_str)
|
|
})
|
|
} else {
|
|
Err(anyhow!(
|
|
"The path to the cache path (\"{}\") is not resolvable.",
|
|
cache_str
|
|
))
|
|
}?;
|
|
let cache_path = cache_url.to_file_path().map_err(|_| {
|
|
anyhow!("Cannot convert \"{}\" into a file path.", cache_url)
|
|
})?;
|
|
info!(
|
|
" Resolved cache path: \"{}\"",
|
|
cache_path.to_string_lossy()
|
|
);
|
|
Some(cache_path)
|
|
} else {
|
|
None
|
|
};
|
|
if self.maybe_cache_path != maybe_cache_path {
|
|
let maybe_custom_root = maybe_cache_path
|
|
.clone()
|
|
.or_else(|| env::var("DENO_DIR").map(String::into).ok());
|
|
let dir = deno_dir::DenoDir::new(maybe_custom_root)
|
|
.expect("could not access DENO_DIR");
|
|
let module_registries_location = dir.root.join(REGISTRIES_PATH);
|
|
self.module_registries =
|
|
registries::ModuleRegistry::new(&module_registries_location);
|
|
self.module_registries_location = module_registries_location;
|
|
let sources_location = dir.root.join(SOURCES_PATH);
|
|
self.sources = Sources::new(&sources_location);
|
|
self.maybe_cache_path = maybe_cache_path;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn update_import_map(&mut self) -> Result<(), AnyError> {
|
|
let mark = self.performance.mark("update_import_map", None::<()>);
|
|
self.maybe_cache_server = None;
|
|
let (maybe_import_map, maybe_root_uri) = {
|
|
let config = &self.config;
|
|
(
|
|
config.get_workspace_settings().import_map,
|
|
config.root_uri.clone(),
|
|
)
|
|
};
|
|
if let Some(import_map_str) = &maybe_import_map {
|
|
info!("Setting import map from: \"{}\"", import_map_str);
|
|
let import_map_url = if let Ok(url) = Url::from_file_path(import_map_str)
|
|
{
|
|
Ok(url)
|
|
} else if import_map_str.starts_with("data:") {
|
|
Url::parse(import_map_str).map_err(|_| {
|
|
anyhow!("Bad data url for import map: {:?}", import_map_str)
|
|
})
|
|
} else if let Some(root_uri) = &maybe_root_uri {
|
|
let root_path = root_uri
|
|
.to_file_path()
|
|
.map_err(|_| anyhow!("Bad root_uri: {}", root_uri))?;
|
|
let import_map_path = root_path.join(import_map_str);
|
|
Url::from_file_path(import_map_path).map_err(|_| {
|
|
anyhow!("Bad file path for import map: {:?}", import_map_str)
|
|
})
|
|
} else {
|
|
Err(anyhow!(
|
|
"The path to the import map (\"{}\") is not resolvable.",
|
|
import_map_str
|
|
))
|
|
}?;
|
|
|
|
let import_map_json = if import_map_url.scheme() == "data" {
|
|
get_source_from_data_url(&import_map_url)?.0
|
|
} else {
|
|
let import_map_path = import_map_url.to_file_path().map_err(|_| {
|
|
anyhow!("Cannot convert \"{}\" into a file path.", import_map_url)
|
|
})?;
|
|
info!(
|
|
" Resolved import map: \"{}\"",
|
|
import_map_path.to_string_lossy()
|
|
);
|
|
fs::read_to_string(import_map_path).await.map_err(|err| {
|
|
anyhow!(
|
|
"Failed to load the import map at: {}. [{}]",
|
|
import_map_url,
|
|
err
|
|
)
|
|
})?
|
|
};
|
|
let import_map =
|
|
ImportMap::from_json(&import_map_url.to_string(), &import_map_json)?;
|
|
self.maybe_import_map_uri = Some(import_map_url);
|
|
self.maybe_import_map = Some(import_map.clone());
|
|
self.sources.set_import_map(Some(import_map));
|
|
} else {
|
|
self.sources.set_import_map(None);
|
|
self.maybe_import_map = None;
|
|
}
|
|
self.performance.measure(mark);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn update_debug_flag(&self) -> bool {
|
|
let internal_debug = self.config.get_workspace_settings().internal_debug;
|
|
logger::LSP_DEBUG_FLAG
|
|
.compare_exchange(
|
|
!internal_debug,
|
|
internal_debug,
|
|
Ordering::Acquire,
|
|
Ordering::Relaxed,
|
|
)
|
|
.is_ok()
|
|
}
|
|
|
|
async fn update_registries(&mut self) -> Result<(), AnyError> {
|
|
let mark = self.performance.mark("update_registries", None::<()>);
|
|
for (registry, enabled) in self
|
|
.config
|
|
.get_workspace_settings()
|
|
.suggest
|
|
.imports
|
|
.hosts
|
|
.iter()
|
|
{
|
|
if *enabled {
|
|
info!("Enabling import suggestions for: {}", registry);
|
|
self.module_registries.enable(registry).await?;
|
|
} else {
|
|
self.module_registries.disable(registry).await?;
|
|
}
|
|
}
|
|
self.performance.measure(mark);
|
|
Ok(())
|
|
}
|
|
|
|
fn update_config_file(&mut self) -> Result<(), AnyError> {
|
|
self.maybe_config_file = None;
|
|
self.maybe_config_uri = None;
|
|
self.maybe_fmt_config = None;
|
|
self.maybe_lint_config = None;
|
|
|
|
let maybe_file_and_url = self.get_config_file_and_url()?;
|
|
|
|
if let Some((config_file, config_url)) = maybe_file_and_url {
|
|
let lint_config = config_file
|
|
.to_lint_config()
|
|
.map_err(|err| {
|
|
anyhow!("Unable to update lint configuration: {:?}", err)
|
|
})?
|
|
.unwrap_or_default();
|
|
let fmt_config = config_file
|
|
.to_fmt_config()
|
|
.map_err(|err| {
|
|
anyhow!("Unable to update formatter configuration: {:?}", err)
|
|
})?
|
|
.unwrap_or_default();
|
|
|
|
self.maybe_config_file = Some(config_file);
|
|
self.maybe_config_uri = Some(config_url);
|
|
self.maybe_lint_config = Some(lint_config);
|
|
self.maybe_fmt_config = Some(fmt_config);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_tsconfig(&mut self) -> Result<(), AnyError> {
|
|
let mark = self.performance.mark("update_tsconfig", None::<()>);
|
|
let mut tsconfig = TsConfig::new(json!({
|
|
"allowJs": true,
|
|
"esModuleInterop": true,
|
|
"experimentalDecorators": true,
|
|
"isolatedModules": true,
|
|
"jsx": "react",
|
|
"lib": ["deno.ns", "deno.window"],
|
|
"module": "esnext",
|
|
"noEmit": true,
|
|
"strict": true,
|
|
"target": "esnext",
|
|
"useDefineForClassFields": true,
|
|
}));
|
|
let config = &self.config;
|
|
let workspace_settings = config.get_workspace_settings();
|
|
if workspace_settings.unstable {
|
|
let unstable_libs = json!({
|
|
"lib": ["deno.ns", "deno.window", "deno.unstable"]
|
|
});
|
|
tsconfig.merge(&unstable_libs);
|
|
}
|
|
if let Err(err) = self.merge_user_tsconfig(&mut tsconfig) {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
let _ok: bool = self
|
|
.ts_server
|
|
.request(self.snapshot()?, tsc::RequestMethod::Configure(tsconfig))
|
|
.await?;
|
|
self.performance.measure(mark);
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn document_version(
|
|
&self,
|
|
specifier: ModuleSpecifier,
|
|
) -> Option<i32> {
|
|
self.documents.version(&specifier)
|
|
}
|
|
|
|
async fn get_asset(
|
|
&mut self,
|
|
specifier: &ModuleSpecifier,
|
|
) -> Result<Option<AssetDocument>, AnyError> {
|
|
if let Some(maybe_asset) = self.assets.get(specifier) {
|
|
Ok(maybe_asset.clone())
|
|
} else {
|
|
let maybe_asset =
|
|
tsc::get_asset(specifier, &self.ts_server, self.snapshot()?).await?;
|
|
self.assets.insert(specifier.clone(), maybe_asset.clone());
|
|
Ok(maybe_asset)
|
|
}
|
|
}
|
|
}
|
|
|
|
// lspower::LanguageServer methods. This file's LanguageServer delegates to us.
|
|
impl Inner {
|
|
async fn initialize(
|
|
&mut self,
|
|
params: InitializeParams,
|
|
) -> LspResult<InitializeResult> {
|
|
info!("Starting Deno language server...");
|
|
let mark = self.performance.mark("initialize", Some(¶ms));
|
|
|
|
// exit this process when the parent is lost
|
|
if let Some(parent_pid) = params.process_id {
|
|
parent_process_checker::start(parent_pid)
|
|
}
|
|
|
|
let capabilities = capabilities::server_capabilities(¶ms.capabilities);
|
|
|
|
let version = format!(
|
|
"{} ({}, {})",
|
|
crate::version::deno(),
|
|
env!("PROFILE"),
|
|
env!("TARGET")
|
|
);
|
|
info!(" version: {}", version);
|
|
if let Ok(path) = std::env::current_exe() {
|
|
info!(" executable: {}", path.to_string_lossy());
|
|
}
|
|
|
|
let server_info = ServerInfo {
|
|
name: "deno-language-server".to_string(),
|
|
version: Some(version),
|
|
};
|
|
|
|
if let Some(client_info) = params.client_info {
|
|
info!(
|
|
"Connected to \"{}\" {}",
|
|
client_info.name,
|
|
client_info.version.unwrap_or_default(),
|
|
);
|
|
}
|
|
|
|
{
|
|
let config = &mut self.config;
|
|
config.root_uri = params.root_uri;
|
|
if let Some(value) = params.initialization_options {
|
|
config.set_workspace_settings(value).map_err(|err| {
|
|
error!("Cannot set workspace settings: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
}
|
|
config.update_capabilities(¶ms.capabilities);
|
|
}
|
|
|
|
self.update_debug_flag();
|
|
// Check to see if we need to change the cache path
|
|
if let Err(err) = self.update_cache() {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
if let Err(err) = self.update_config_file() {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
if let Err(err) = self.update_tsconfig().await {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
|
|
if capabilities.code_action_provider.is_some() {
|
|
let fixable_diagnostics: Vec<String> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, tsc::RequestMethod::GetSupportedCodeFixes)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Unable to get fixable diagnostics: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
self.ts_fixable_diagnostics = fixable_diagnostics;
|
|
}
|
|
|
|
// Check to see if we need to setup the import map
|
|
if let Err(err) = self.update_import_map().await {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
// Check to see if we need to setup any module registries
|
|
if let Err(err) = self.update_registries().await {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
|
|
self.performance.measure(mark);
|
|
Ok(InitializeResult {
|
|
capabilities,
|
|
server_info: Some(server_info),
|
|
})
|
|
}
|
|
|
|
async fn initialized(&mut self, _: InitializedParams) {
|
|
if self
|
|
.config
|
|
.client_capabilities
|
|
.workspace_did_change_watched_files
|
|
{
|
|
// we are going to watch all the JSON files in the workspace, and the
|
|
// notification handler will pick up any of the changes of those files we
|
|
// are interested in.
|
|
let watch_registration_options =
|
|
DidChangeWatchedFilesRegistrationOptions {
|
|
watchers: vec![FileSystemWatcher {
|
|
glob_pattern: "**/*.json".to_string(),
|
|
kind: Some(WatchKind::Change),
|
|
}],
|
|
};
|
|
let registration = Registration {
|
|
id: "workspace/didChangeWatchedFiles".to_string(),
|
|
method: "workspace/didChangeWatchedFiles".to_string(),
|
|
register_options: Some(
|
|
serde_json::to_value(watch_registration_options).unwrap(),
|
|
),
|
|
};
|
|
if let Err(err) =
|
|
self.client.register_capability(vec![registration]).await
|
|
{
|
|
warn!("Client errored on capabilities.\n{}", err);
|
|
}
|
|
}
|
|
|
|
info!("Server ready.");
|
|
}
|
|
|
|
async fn shutdown(&self) -> LspResult<()> {
|
|
Ok(())
|
|
}
|
|
|
|
async fn did_open(&mut self, params: DidOpenTextDocumentParams) {
|
|
let mark = self.performance.mark("did_open", Some(¶ms));
|
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
|
|
|
if let Err(err) = self
|
|
.config
|
|
.update_specifier_settings(&specifier, ¶ms.text_document.uri)
|
|
.await
|
|
{
|
|
error!("Error updating specifier settings: {}", err);
|
|
}
|
|
|
|
if params.text_document.uri.scheme() == "deno" {
|
|
// we can ignore virtual text documents opening, as they don't need to
|
|
// be tracked in memory, as they are static assets that won't change
|
|
// already managed by the language service
|
|
return;
|
|
}
|
|
let language_id =
|
|
params
|
|
.text_document
|
|
.language_id
|
|
.parse()
|
|
.unwrap_or_else(|err| {
|
|
error!("{}", err);
|
|
LanguageId::Unknown
|
|
});
|
|
if language_id == LanguageId::Unknown {
|
|
warn!(
|
|
"Unsupported language id \"{}\" received for document \"{}\".",
|
|
params.text_document.language_id, params.text_document.uri
|
|
);
|
|
}
|
|
self.documents.open(
|
|
specifier.clone(),
|
|
params.text_document.version,
|
|
language_id,
|
|
Arc::new(params.text_document.text),
|
|
);
|
|
|
|
if self.documents.is_diagnosable(&specifier) {
|
|
self.analyze_dependencies(&specifier);
|
|
self
|
|
.diagnostics_server
|
|
.invalidate(self.documents.dependents(&specifier))
|
|
.await;
|
|
if let Err(err) = self.diagnostics_server.update() {
|
|
error!("{}", err);
|
|
}
|
|
}
|
|
self.performance.measure(mark);
|
|
}
|
|
|
|
async fn did_change(&mut self, params: DidChangeTextDocumentParams) {
|
|
let mark = self.performance.mark("did_change", Some(¶ms));
|
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
|
match self.documents.change(
|
|
&specifier,
|
|
params.text_document.version,
|
|
params.content_changes,
|
|
) {
|
|
Ok(()) => {
|
|
if self.documents.is_diagnosable(&specifier) {
|
|
self.analyze_dependencies(&specifier);
|
|
self
|
|
.diagnostics_server
|
|
.invalidate(self.documents.dependents(&specifier))
|
|
.await;
|
|
if let Err(err) = self.diagnostics_server.update() {
|
|
error!("{}", err);
|
|
}
|
|
}
|
|
}
|
|
Err(err) => error!("{}", err),
|
|
}
|
|
self.performance.measure(mark);
|
|
}
|
|
|
|
async fn did_close(&mut self, params: DidCloseTextDocumentParams) {
|
|
let mark = self.performance.mark("did_close", Some(¶ms));
|
|
if params.text_document.uri.scheme() == "deno" {
|
|
// we can ignore virtual text documents closing, as they don't need to
|
|
// be tracked in memory, as they are static assets that won't change
|
|
// already managed by the language service
|
|
return;
|
|
}
|
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
|
let is_diagnosable = self.documents.is_diagnosable(&specifier);
|
|
|
|
if is_diagnosable {
|
|
let mut specifiers = self.documents.dependents(&specifier);
|
|
specifiers.push(specifier.clone());
|
|
self.diagnostics_server.invalidate(specifiers).await;
|
|
}
|
|
self.documents.close(&specifier);
|
|
if is_diagnosable {
|
|
if let Err(err) = self.diagnostics_server.update() {
|
|
error!("{}", err);
|
|
}
|
|
}
|
|
self.performance.measure(mark);
|
|
}
|
|
|
|
async fn did_change_configuration(
|
|
&mut self,
|
|
params: DidChangeConfigurationParams,
|
|
) {
|
|
let mark = self
|
|
.performance
|
|
.mark("did_change_configuration", Some(¶ms));
|
|
|
|
let maybe_config =
|
|
if self.config.client_capabilities.workspace_configuration {
|
|
let config_response = self
|
|
.client
|
|
.configuration(vec![ConfigurationItem {
|
|
scope_uri: None,
|
|
section: Some(SETTINGS_SECTION.to_string()),
|
|
}])
|
|
.await;
|
|
if let Err(err) = self.config.update_all_settings().await {
|
|
error!("Cannot request updating all settings: {}", err);
|
|
}
|
|
match config_response {
|
|
Ok(value_vec) => value_vec.get(0).cloned(),
|
|
Err(err) => {
|
|
error!("Error getting workspace configuration: {}", err);
|
|
None
|
|
}
|
|
}
|
|
} else {
|
|
params
|
|
.settings
|
|
.as_object()
|
|
.map(|settings| settings.get(SETTINGS_SECTION))
|
|
.flatten()
|
|
.cloned()
|
|
};
|
|
|
|
if let Some(value) = maybe_config {
|
|
if let Err(err) = self.config.set_workspace_settings(value) {
|
|
error!("failed to update settings: {}", err);
|
|
}
|
|
}
|
|
|
|
self.update_debug_flag();
|
|
if let Err(err) = self.update_cache() {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
if let Err(err) = self.update_import_map().await {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
if let Err(err) = self.update_registries().await {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
if let Err(err) = self.update_config_file() {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
if let Err(err) = self.update_tsconfig().await {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
if let Err(err) = self.diagnostics_server.update() {
|
|
error!("{}", err);
|
|
}
|
|
|
|
self.performance.measure(mark);
|
|
}
|
|
|
|
async fn did_change_watched_files(
|
|
&mut self,
|
|
params: DidChangeWatchedFilesParams,
|
|
) {
|
|
let mark = self
|
|
.performance
|
|
.mark("did_change_watched_files", Some(¶ms));
|
|
let mut touched = false;
|
|
// if the current import map has changed, we need to reload it
|
|
if let Some(import_map_uri) = &self.maybe_import_map_uri {
|
|
if params.changes.iter().any(|fe| *import_map_uri == fe.uri) {
|
|
if let Err(err) = self.update_import_map().await {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
touched = true;
|
|
}
|
|
}
|
|
// if the current tsconfig has changed, we need to reload it
|
|
if let Some(config_uri) = &self.maybe_config_uri {
|
|
if params.changes.iter().any(|fe| *config_uri == fe.uri) {
|
|
if let Err(err) = self.update_config_file() {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
if let Err(err) = self.update_tsconfig().await {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
touched = true;
|
|
}
|
|
}
|
|
if touched {
|
|
self.analyze_dependencies_all();
|
|
self.diagnostics_server.invalidate_all().await;
|
|
if let Err(err) = self.diagnostics_server.update() {
|
|
error!("Cannot update diagnostics: {}", err);
|
|
}
|
|
}
|
|
self.performance.measure(mark);
|
|
}
|
|
|
|
async fn document_symbol(
|
|
&mut self,
|
|
params: DocumentSymbolParams,
|
|
) -> LspResult<Option<DocumentSymbolResponse>> {
|
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("document_symbol", Some(¶ms));
|
|
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
|
|
let req = tsc::RequestMethod::GetNavigationTree(specifier);
|
|
let navigation_tree: tsc::NavigationTree = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
|
|
let response = if let Some(child_items) = navigation_tree.child_items {
|
|
let mut document_symbols = Vec::<DocumentSymbol>::new();
|
|
for item in child_items {
|
|
item.collect_document_symbols(&line_index, &mut document_symbols);
|
|
}
|
|
Some(DocumentSymbolResponse::Nested(document_symbols))
|
|
} else {
|
|
None
|
|
};
|
|
self.performance.measure(mark);
|
|
Ok(response)
|
|
}
|
|
|
|
async fn formatting(
|
|
&self,
|
|
params: DocumentFormattingParams,
|
|
) -> LspResult<Option<Vec<TextEdit>>> {
|
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
|
if !self.documents.is_formattable(&specifier) {
|
|
return Ok(None);
|
|
}
|
|
let mark = self.performance.mark("formatting", Some(¶ms));
|
|
let document_data = self.documents.get(&specifier).ok_or_else(|| {
|
|
LspError::invalid_params(
|
|
"The specified file could not be found in memory.",
|
|
)
|
|
})?;
|
|
let file_path =
|
|
if let Ok(file_path) = params.text_document.uri.to_file_path() {
|
|
file_path
|
|
} else {
|
|
PathBuf::from(params.text_document.uri.path())
|
|
};
|
|
|
|
let fmt_options = if let Some(fmt_config) = self.maybe_fmt_config.as_ref() {
|
|
fmt_config.options.clone()
|
|
} else {
|
|
Default::default()
|
|
};
|
|
|
|
let source = document_data.source().clone();
|
|
let text_edits = tokio::task::spawn_blocking(move || {
|
|
let format_result = match source.module() {
|
|
Some(Ok(parsed_module)) => {
|
|
format_parsed_source(parsed_module, fmt_options)
|
|
}
|
|
Some(Err(err)) => Err(err.to_string()),
|
|
None => {
|
|
// it's not a js/ts file, so attempt to format its contents
|
|
format_file(&file_path, source.text_info().text_str(), fmt_options)
|
|
}
|
|
};
|
|
|
|
match format_result {
|
|
Ok(new_text) => {
|
|
let line_index = source.line_index();
|
|
Some(text::get_edits(
|
|
source.text_info().text_str(),
|
|
&new_text,
|
|
line_index,
|
|
))
|
|
}
|
|
Err(err) => {
|
|
// TODO(lucacasonato): handle error properly
|
|
warn!("Format error: {}", err);
|
|
None
|
|
}
|
|
}
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
self.performance.measure(mark);
|
|
if let Some(text_edits) = text_edits {
|
|
if text_edits.is_empty() {
|
|
Ok(None)
|
|
} else {
|
|
Ok(Some(text_edits))
|
|
}
|
|
} else {
|
|
self.client.show_message(MessageType::Warning, format!("Unable to format \"{}\". Likely due to unrecoverable syntax errors in the file.", specifier)).await;
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
async fn hover(&mut self, params: HoverParams) -> LspResult<Option<Hover>> {
|
|
let specifier = self
|
|
.url_map
|
|
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("hover", Some(¶ms));
|
|
let hover = if let Some(dependency_range) =
|
|
self.documents.is_specifier_position(
|
|
&specifier,
|
|
¶ms.text_document_position_params.position,
|
|
) {
|
|
if let Some(dependencies) = &self.documents.dependencies(&specifier) {
|
|
if let Some(dep) = dependencies.get(&dependency_range.specifier) {
|
|
let value = match (&dep.maybe_code, &dep.maybe_type) {
|
|
(Some(code_dep), Some(type_dep)) => {
|
|
format!(
|
|
"**Resolved Dependency**\n\n**Code**: {}\n\n**Types**: {}\n",
|
|
code_dep.as_hover_text(),
|
|
type_dep.as_hover_text()
|
|
)
|
|
}
|
|
(Some(code_dep), None) => {
|
|
format!(
|
|
"**Resolved Dependency**\n\n**Code**: {}\n",
|
|
code_dep.as_hover_text()
|
|
)
|
|
}
|
|
(None, Some(type_dep)) => {
|
|
format!(
|
|
"**Resolved Dependency**\n\n**Types**: {}\n",
|
|
type_dep.as_hover_text()
|
|
)
|
|
}
|
|
(None, None) => {
|
|
error!(
|
|
"Unexpected state hovering on dependency. Dependency \"{}\" in \"{}\" not found.",
|
|
dependency_range.specifier,
|
|
specifier
|
|
);
|
|
"".to_string()
|
|
}
|
|
};
|
|
Some(Hover {
|
|
contents: HoverContents::Markup(MarkupContent {
|
|
kind: MarkupKind::Markdown,
|
|
value,
|
|
}),
|
|
range: Some(dependency_range.range),
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
let req = tsc::RequestMethod::GetQuickInfo((
|
|
specifier,
|
|
line_index.offset_tsc(params.text_document_position_params.position)?,
|
|
));
|
|
let maybe_quick_info: Option<tsc::QuickInfo> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Unable to get quick info: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
maybe_quick_info.map(|qi| qi.to_hover(&line_index))
|
|
};
|
|
self.performance.measure(mark);
|
|
Ok(hover)
|
|
}
|
|
|
|
async fn code_action(
|
|
&mut self,
|
|
params: CodeActionParams,
|
|
) -> LspResult<Option<CodeActionResponse>> {
|
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("code_action", Some(¶ms));
|
|
let mut all_actions = CodeActionResponse::new();
|
|
let line_index = self.get_line_index_sync(&specifier).unwrap();
|
|
|
|
// QuickFix
|
|
let fixable_diagnostics: Vec<&Diagnostic> = params
|
|
.context
|
|
.diagnostics
|
|
.iter()
|
|
.filter(|d| match &d.source {
|
|
Some(source) => match source.as_str() {
|
|
"deno-ts" => match &d.code {
|
|
Some(NumberOrString::String(code)) => {
|
|
self.ts_fixable_diagnostics.contains(code)
|
|
}
|
|
Some(NumberOrString::Number(code)) => {
|
|
self.ts_fixable_diagnostics.contains(&code.to_string())
|
|
}
|
|
_ => false,
|
|
},
|
|
"deno-lint" => matches!(&d.code, Some(_)),
|
|
"deno" => match &d.code {
|
|
Some(NumberOrString::String(code)) => {
|
|
code == "no-cache" || code == "no-cache-data"
|
|
}
|
|
_ => false,
|
|
},
|
|
_ => false,
|
|
},
|
|
None => false,
|
|
})
|
|
.collect();
|
|
if !fixable_diagnostics.is_empty() {
|
|
let mut code_actions = CodeActionCollection::default();
|
|
let file_diagnostics = self
|
|
.diagnostics_server
|
|
.get(&specifier, DiagnosticSource::TypeScript)
|
|
.await;
|
|
for diagnostic in &fixable_diagnostics {
|
|
match diagnostic.source.as_deref() {
|
|
Some("deno-ts") => {
|
|
let code = match diagnostic.code.as_ref().unwrap() {
|
|
NumberOrString::String(code) => code.to_string(),
|
|
NumberOrString::Number(code) => code.to_string(),
|
|
};
|
|
let codes = vec![code];
|
|
let req = tsc::RequestMethod::GetCodeFixes((
|
|
specifier.clone(),
|
|
line_index.offset_tsc(diagnostic.range.start)?,
|
|
line_index.offset_tsc(diagnostic.range.end)?,
|
|
codes,
|
|
));
|
|
let actions: Vec<tsc::CodeFixAction> =
|
|
match self.ts_server.request(self.snapshot()?, req).await {
|
|
Ok(items) => items,
|
|
Err(err) => {
|
|
// sometimes tsc reports errors when retrieving code actions
|
|
// because they don't reflect the current state of the document
|
|
// so we will log them to the output, but we won't send an error
|
|
// message back to the client.
|
|
error!("Error getting actions from TypeScript: {}", err);
|
|
Vec::new()
|
|
}
|
|
};
|
|
for action in actions {
|
|
code_actions
|
|
.add_ts_fix_action(&specifier, &action, diagnostic, self)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Unable to convert fix: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
if code_actions.is_fix_all_action(
|
|
&action,
|
|
diagnostic,
|
|
&file_diagnostics,
|
|
) {
|
|
code_actions
|
|
.add_ts_fix_all_action(&action, &specifier, diagnostic);
|
|
}
|
|
}
|
|
}
|
|
Some("deno") => code_actions
|
|
.add_deno_fix_action(diagnostic)
|
|
.map_err(|err| {
|
|
error!("{}", err);
|
|
LspError::internal_error()
|
|
})?,
|
|
Some("deno-lint") => code_actions
|
|
.add_deno_lint_ignore_action(
|
|
&specifier,
|
|
self.documents.get(&specifier),
|
|
diagnostic,
|
|
)
|
|
.map_err(|err| {
|
|
error!("Unable to fix lint error: {}", err);
|
|
LspError::internal_error()
|
|
})?,
|
|
_ => (),
|
|
}
|
|
}
|
|
code_actions.set_preferred_fixes();
|
|
all_actions.extend(code_actions.get_response());
|
|
}
|
|
|
|
// Refactor
|
|
let start = line_index.offset_tsc(params.range.start)?;
|
|
let length = line_index.offset_tsc(params.range.end)? - start;
|
|
let only =
|
|
params
|
|
.context
|
|
.only
|
|
.as_ref()
|
|
.map_or(String::default(), |values| {
|
|
values
|
|
.first()
|
|
.map_or(String::default(), |v| v.as_str().to_owned())
|
|
});
|
|
let req = tsc::RequestMethod::GetApplicableRefactors((
|
|
specifier.clone(),
|
|
tsc::TextSpan { start, length },
|
|
only,
|
|
));
|
|
let refactor_infos: Vec<tsc::ApplicableRefactorInfo> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
let mut refactor_actions = Vec::<CodeAction>::new();
|
|
for refactor_info in refactor_infos.iter() {
|
|
refactor_actions
|
|
.extend(refactor_info.to_code_actions(&specifier, ¶ms.range));
|
|
}
|
|
all_actions.extend(
|
|
refactor::prune_invalid_actions(&refactor_actions, 5)
|
|
.into_iter()
|
|
.map(CodeActionOrCommand::CodeAction),
|
|
);
|
|
|
|
let code_action_disabled_support =
|
|
self.config.client_capabilities.code_action_disabled_support;
|
|
let actions: Vec<CodeActionOrCommand> = all_actions.into_iter().filter(|ca| {
|
|
code_action_disabled_support
|
|
|| matches!(ca, CodeActionOrCommand::CodeAction(ca) if ca.disabled.is_none())
|
|
}).collect();
|
|
let response = if actions.is_empty() {
|
|
None
|
|
} else {
|
|
Some(actions)
|
|
};
|
|
|
|
self.performance.measure(mark);
|
|
Ok(response)
|
|
}
|
|
|
|
async fn code_action_resolve(
|
|
&mut self,
|
|
params: CodeAction,
|
|
) -> LspResult<CodeAction> {
|
|
if params.kind.is_none() || params.data.is_none() {
|
|
return Ok(params);
|
|
}
|
|
|
|
let mark = self.performance.mark("code_action_resolve", Some(¶ms));
|
|
let kind = params.kind.clone().unwrap();
|
|
let data = params.data.clone().unwrap();
|
|
|
|
let result = if kind.as_str().starts_with(CodeActionKind::QUICKFIX.as_str())
|
|
{
|
|
let code_action_data: CodeActionData =
|
|
from_value(data).map_err(|err| {
|
|
error!("Unable to decode code action data: {}", err);
|
|
LspError::invalid_params("The CodeAction's data is invalid.")
|
|
})?;
|
|
let req = tsc::RequestMethod::GetCombinedCodeFix((
|
|
code_action_data.specifier.clone(),
|
|
json!(code_action_data.fix_id.clone()),
|
|
));
|
|
let combined_code_actions: tsc::CombinedCodeActions = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Unable to get combined fix from TypeScript: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
if combined_code_actions.commands.is_some() {
|
|
error!("Deno does not support code actions with commands.");
|
|
return Err(LspError::invalid_request());
|
|
}
|
|
|
|
let changes = if code_action_data.fix_id == "fixMissingImport" {
|
|
fix_ts_import_changes(
|
|
&code_action_data.specifier,
|
|
&combined_code_actions.changes,
|
|
self,
|
|
)
|
|
.map_err(|err| {
|
|
error!("Unable to remap changes: {}", err);
|
|
LspError::internal_error()
|
|
})?
|
|
} else {
|
|
combined_code_actions.changes.clone()
|
|
};
|
|
let mut code_action = params.clone();
|
|
code_action.edit =
|
|
ts_changes_to_edit(&changes, self).await.map_err(|err| {
|
|
error!("Unable to convert changes to edits: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
code_action
|
|
} else if kind.as_str().starts_with(CodeActionKind::REFACTOR.as_str()) {
|
|
let mut code_action = params.clone();
|
|
let action_data: refactor::RefactorCodeActionData = from_value(data)
|
|
.map_err(|err| {
|
|
error!("Unable to decode code action data: {}", err);
|
|
LspError::invalid_params("The CodeAction's data is invalid.")
|
|
})?;
|
|
let line_index =
|
|
self.get_line_index_sync(&action_data.specifier).unwrap();
|
|
let start = line_index.offset_tsc(action_data.range.start)?;
|
|
let length = line_index.offset_tsc(action_data.range.end)? - start;
|
|
let req = tsc::RequestMethod::GetEditsForRefactor((
|
|
action_data.specifier.clone(),
|
|
tsc::TextSpan { start, length },
|
|
action_data.refactor_name.clone(),
|
|
action_data.action_name.clone(),
|
|
));
|
|
let refactor_edit_info: tsc::RefactorEditInfo = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
code_action.edit = refactor_edit_info
|
|
.to_workspace_edit(self)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Unable to convert changes to edits: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
code_action
|
|
} else {
|
|
// The code action doesn't need to be resolved
|
|
params
|
|
};
|
|
|
|
self.performance.measure(mark);
|
|
Ok(result)
|
|
}
|
|
|
|
async fn code_lens(
|
|
&mut self,
|
|
params: CodeLensParams,
|
|
) -> LspResult<Option<Vec<CodeLens>>> {
|
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
|| !(self.config.get_workspace_settings().enabled_code_lens()
|
|
|| self.config.specifier_code_lens_test(&specifier))
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("code_lens", Some(¶ms));
|
|
let navigation_tree =
|
|
self.get_navigation_tree(&specifier).await.map_err(|err| {
|
|
error!("Error getting code lenses for \"{}\": {}", specifier, err);
|
|
LspError::internal_error()
|
|
})?;
|
|
let parsed_module = self
|
|
.documents
|
|
.get(&specifier)
|
|
.map(|d| d.source().module())
|
|
.flatten()
|
|
.map(|m| m.as_ref().ok())
|
|
.flatten();
|
|
let line_index = self.get_line_index_sync(&specifier).ok_or_else(|| {
|
|
error!(
|
|
"Error getting code lenses for \"{}\": Missing line index",
|
|
specifier
|
|
);
|
|
LspError::internal_error()
|
|
})?;
|
|
let code_lenses = code_lens::collect(
|
|
&specifier,
|
|
parsed_module,
|
|
&self.config,
|
|
&line_index,
|
|
&navigation_tree,
|
|
)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Error getting code lenses for \"{}\": {}", specifier, err);
|
|
LspError::internal_error()
|
|
})?;
|
|
self.performance.measure(mark);
|
|
|
|
Ok(Some(code_lenses))
|
|
}
|
|
|
|
async fn code_lens_resolve(
|
|
&mut self,
|
|
code_lens: CodeLens,
|
|
) -> LspResult<CodeLens> {
|
|
let mark = self.performance.mark("code_lens_resolve", Some(&code_lens));
|
|
let result = if code_lens.data.is_some() {
|
|
code_lens::resolve_code_lens(code_lens, self)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Error resolving code lens: {}", err);
|
|
LspError::internal_error()
|
|
})
|
|
} else {
|
|
Err(LspError::invalid_params(
|
|
"Code lens is missing the \"data\" property.",
|
|
))
|
|
};
|
|
self.performance.measure(mark);
|
|
result
|
|
}
|
|
|
|
async fn document_highlight(
|
|
&mut self,
|
|
params: DocumentHighlightParams,
|
|
) -> LspResult<Option<Vec<DocumentHighlight>>> {
|
|
let specifier = self
|
|
.url_map
|
|
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("document_highlight", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
let files_to_search = vec![specifier.clone()];
|
|
let req = tsc::RequestMethod::GetDocumentHighlights((
|
|
specifier,
|
|
line_index.offset_tsc(params.text_document_position_params.position)?,
|
|
files_to_search,
|
|
));
|
|
let maybe_document_highlights: Option<Vec<tsc::DocumentHighlights>> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Unable to get document highlights from TypeScript: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
|
|
if let Some(document_highlights) = maybe_document_highlights {
|
|
let result = document_highlights
|
|
.into_iter()
|
|
.map(|dh| dh.to_highlight(&line_index))
|
|
.flatten()
|
|
.collect();
|
|
self.performance.measure(mark);
|
|
Ok(Some(result))
|
|
} else {
|
|
self.performance.measure(mark);
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
async fn references(
|
|
&mut self,
|
|
params: ReferenceParams,
|
|
) -> LspResult<Option<Vec<Location>>> {
|
|
let specifier = self
|
|
.url_map
|
|
.normalize_url(¶ms.text_document_position.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("references", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
let req = tsc::RequestMethod::GetReferences((
|
|
specifier,
|
|
line_index.offset_tsc(params.text_document_position.position)?,
|
|
));
|
|
let maybe_references: Option<Vec<tsc::ReferenceEntry>> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Unable to get references from TypeScript: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
|
|
if let Some(references) = maybe_references {
|
|
let mut results = Vec::new();
|
|
for reference in references {
|
|
if !params.context.include_declaration && reference.is_definition {
|
|
continue;
|
|
}
|
|
let reference_specifier =
|
|
resolve_url(&reference.document_span.file_name).unwrap();
|
|
// TODO(lucacasonato): handle error correctly
|
|
let line_index =
|
|
self.get_line_index(reference_specifier).await.unwrap();
|
|
results.push(reference.to_location(&line_index, self));
|
|
}
|
|
|
|
self.performance.measure(mark);
|
|
Ok(Some(results))
|
|
} else {
|
|
self.performance.measure(mark);
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
async fn goto_definition(
|
|
&mut self,
|
|
params: GotoDefinitionParams,
|
|
) -> LspResult<Option<GotoDefinitionResponse>> {
|
|
let specifier = self
|
|
.url_map
|
|
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("goto_definition", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
let req = tsc::RequestMethod::GetDefinition((
|
|
specifier,
|
|
line_index.offset_tsc(params.text_document_position_params.position)?,
|
|
));
|
|
let maybe_definition: Option<tsc::DefinitionInfoAndBoundSpan> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Unable to get definition from TypeScript: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
|
|
if let Some(definition) = maybe_definition {
|
|
let results = definition.to_definition(&line_index, self).await;
|
|
self.performance.measure(mark);
|
|
Ok(results)
|
|
} else {
|
|
self.performance.measure(mark);
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
async fn completion(
|
|
&mut self,
|
|
params: CompletionParams,
|
|
) -> LspResult<Option<CompletionResponse>> {
|
|
let specifier = self
|
|
.url_map
|
|
.normalize_url(¶ms.text_document_position.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("completion", Some(¶ms));
|
|
// Import specifiers are something wholly internal to Deno, so for
|
|
// completions, we will use internal logic and if there are completions
|
|
// for imports, we will return those and not send a message into tsc, where
|
|
// other completions come from.
|
|
let response = if let Some(response) = completions::get_import_completions(
|
|
&specifier,
|
|
¶ms.text_document_position.position,
|
|
&self.snapshot()?,
|
|
self.client.clone(),
|
|
)
|
|
.await
|
|
{
|
|
Some(response)
|
|
} else {
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
let trigger_character = if let Some(context) = ¶ms.context {
|
|
context.trigger_character.clone()
|
|
} else {
|
|
None
|
|
};
|
|
let position =
|
|
line_index.offset_tsc(params.text_document_position.position)?;
|
|
let req = tsc::RequestMethod::GetCompletions((
|
|
specifier.clone(),
|
|
position,
|
|
tsc::GetCompletionsAtPositionOptions {
|
|
user_preferences: tsc::UserPreferences {
|
|
allow_text_changes_in_new_files: Some(specifier.scheme() == "file"),
|
|
include_automatic_optional_chain_completions: Some(true),
|
|
provide_refactor_not_applicable_reason: Some(true),
|
|
include_completions_with_insert_text: Some(true),
|
|
allow_incomplete_completions: Some(true),
|
|
..Default::default()
|
|
},
|
|
trigger_character,
|
|
},
|
|
));
|
|
let maybe_completion_info: Option<tsc::CompletionInfo> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Unable to get completion info from TypeScript: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
|
|
if let Some(completions) = maybe_completion_info {
|
|
let results = completions.as_completion_response(
|
|
&line_index,
|
|
&self.config.get_workspace_settings().suggest,
|
|
&specifier,
|
|
position,
|
|
);
|
|
Some(results)
|
|
} else {
|
|
None
|
|
}
|
|
};
|
|
self.performance.measure(mark);
|
|
Ok(response)
|
|
}
|
|
|
|
async fn completion_resolve(
|
|
&mut self,
|
|
params: CompletionItem,
|
|
) -> LspResult<CompletionItem> {
|
|
let mark = self.performance.mark("completion_resolve", Some(¶ms));
|
|
let completion_item = if let Some(data) = ¶ms.data {
|
|
let data: completions::CompletionItemData =
|
|
serde_json::from_value(data.clone()).map_err(|err| {
|
|
error!("{}", err);
|
|
LspError::invalid_params(
|
|
"Could not decode data field of completion item.",
|
|
)
|
|
})?;
|
|
if let Some(data) = data.tsc {
|
|
let req = tsc::RequestMethod::GetCompletionDetails(data.into());
|
|
let maybe_completion_info: Option<tsc::CompletionEntryDetails> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Unable to get completion info from TypeScript: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
if let Some(completion_info) = maybe_completion_info {
|
|
completion_info.as_completion_item(¶ms)
|
|
} else {
|
|
error!(
|
|
"Received an undefined response from tsc for completion details."
|
|
);
|
|
params
|
|
}
|
|
} else {
|
|
params
|
|
}
|
|
} else {
|
|
params
|
|
};
|
|
self.performance.measure(mark);
|
|
Ok(completion_item)
|
|
}
|
|
|
|
async fn goto_implementation(
|
|
&mut self,
|
|
params: GotoImplementationParams,
|
|
) -> LspResult<Option<GotoImplementationResponse>> {
|
|
let specifier = self
|
|
.url_map
|
|
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("goto_implementation", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
|
|
let req = tsc::RequestMethod::GetImplementation((
|
|
specifier,
|
|
line_index.offset_tsc(params.text_document_position_params.position)?,
|
|
));
|
|
let maybe_implementations: Option<Vec<tsc::ImplementationLocation>> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
|
|
let result = if let Some(implementations) = maybe_implementations {
|
|
let mut links = Vec::new();
|
|
for implementation in implementations {
|
|
if let Some(link) = implementation.to_link(&line_index, self).await {
|
|
links.push(link)
|
|
}
|
|
}
|
|
Some(GotoDefinitionResponse::Link(links))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
self.performance.measure(mark);
|
|
Ok(result)
|
|
}
|
|
|
|
async fn folding_range(
|
|
&mut self,
|
|
params: FoldingRangeParams,
|
|
) -> LspResult<Option<Vec<FoldingRange>>> {
|
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("folding_range", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
|
|
let req = tsc::RequestMethod::GetOutliningSpans(specifier.clone());
|
|
let outlining_spans: Vec<tsc::OutliningSpan> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
|
|
let response = if !outlining_spans.is_empty() {
|
|
let text_content =
|
|
self.get_text_content(&specifier).ok_or_else(|| {
|
|
LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
))
|
|
})?;
|
|
Some(
|
|
outlining_spans
|
|
.iter()
|
|
.map(|span| {
|
|
span.to_folding_range(
|
|
&line_index,
|
|
text_content.as_str().as_bytes(),
|
|
self.config.client_capabilities.line_folding_only,
|
|
)
|
|
})
|
|
.collect::<Vec<FoldingRange>>(),
|
|
)
|
|
} else {
|
|
None
|
|
};
|
|
self.performance.measure(mark);
|
|
Ok(response)
|
|
}
|
|
|
|
async fn incoming_calls(
|
|
&mut self,
|
|
params: CallHierarchyIncomingCallsParams,
|
|
) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> {
|
|
let specifier = self.url_map.normalize_url(¶ms.item.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("incoming_calls", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
|
|
let req = tsc::RequestMethod::ProvideCallHierarchyIncomingCalls((
|
|
specifier.clone(),
|
|
line_index.offset_tsc(params.item.selection_range.start)?,
|
|
));
|
|
let incoming_calls: Vec<tsc::CallHierarchyIncomingCall> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
|
|
let maybe_root_path_owned = self
|
|
.config
|
|
.root_uri
|
|
.as_ref()
|
|
.and_then(|uri| uri.to_file_path().ok());
|
|
let mut resolved_items = Vec::<CallHierarchyIncomingCall>::new();
|
|
for item in incoming_calls.iter() {
|
|
if let Some(resolved) = item
|
|
.try_resolve_call_hierarchy_incoming_call(
|
|
self,
|
|
maybe_root_path_owned.as_deref(),
|
|
)
|
|
.await
|
|
{
|
|
resolved_items.push(resolved);
|
|
}
|
|
}
|
|
self.performance.measure(mark);
|
|
Ok(Some(resolved_items))
|
|
}
|
|
|
|
async fn outgoing_calls(
|
|
&mut self,
|
|
params: CallHierarchyOutgoingCallsParams,
|
|
) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> {
|
|
let specifier = self.url_map.normalize_url(¶ms.item.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("outgoing_calls", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
|
|
let req = tsc::RequestMethod::ProvideCallHierarchyOutgoingCalls((
|
|
specifier.clone(),
|
|
line_index.offset_tsc(params.item.selection_range.start)?,
|
|
));
|
|
let outgoing_calls: Vec<tsc::CallHierarchyOutgoingCall> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
|
|
let maybe_root_path_owned = self
|
|
.config
|
|
.root_uri
|
|
.as_ref()
|
|
.and_then(|uri| uri.to_file_path().ok());
|
|
let mut resolved_items = Vec::<CallHierarchyOutgoingCall>::new();
|
|
for item in outgoing_calls.iter() {
|
|
if let Some(resolved) = item
|
|
.try_resolve_call_hierarchy_outgoing_call(
|
|
&line_index,
|
|
self,
|
|
maybe_root_path_owned.as_deref(),
|
|
)
|
|
.await
|
|
{
|
|
resolved_items.push(resolved);
|
|
}
|
|
}
|
|
self.performance.measure(mark);
|
|
Ok(Some(resolved_items))
|
|
}
|
|
|
|
async fn prepare_call_hierarchy(
|
|
&mut self,
|
|
params: CallHierarchyPrepareParams,
|
|
) -> LspResult<Option<Vec<CallHierarchyItem>>> {
|
|
let specifier = self
|
|
.url_map
|
|
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self
|
|
.performance
|
|
.mark("prepare_call_hierarchy", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
|
|
let req = tsc::RequestMethod::PrepareCallHierarchy((
|
|
specifier.clone(),
|
|
line_index.offset_tsc(params.text_document_position_params.position)?,
|
|
));
|
|
let maybe_one_or_many: Option<tsc::OneOrMany<tsc::CallHierarchyItem>> =
|
|
self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
|
|
let response = if let Some(one_or_many) = maybe_one_or_many {
|
|
let maybe_root_path_owned = self
|
|
.config
|
|
.root_uri
|
|
.as_ref()
|
|
.and_then(|uri| uri.to_file_path().ok());
|
|
let mut resolved_items = Vec::<CallHierarchyItem>::new();
|
|
match one_or_many {
|
|
tsc::OneOrMany::One(item) => {
|
|
if let Some(resolved) = item
|
|
.try_resolve_call_hierarchy_item(
|
|
self,
|
|
maybe_root_path_owned.as_deref(),
|
|
)
|
|
.await
|
|
{
|
|
resolved_items.push(resolved)
|
|
}
|
|
}
|
|
tsc::OneOrMany::Many(items) => {
|
|
for item in items.iter() {
|
|
if let Some(resolved) = item
|
|
.try_resolve_call_hierarchy_item(
|
|
self,
|
|
maybe_root_path_owned.as_deref(),
|
|
)
|
|
.await
|
|
{
|
|
resolved_items.push(resolved);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Some(resolved_items)
|
|
} else {
|
|
None
|
|
};
|
|
self.performance.measure(mark);
|
|
Ok(response)
|
|
}
|
|
|
|
async fn rename(
|
|
&mut self,
|
|
params: RenameParams,
|
|
) -> LspResult<Option<WorkspaceEdit>> {
|
|
let specifier = self
|
|
.url_map
|
|
.normalize_url(¶ms.text_document_position.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("rename", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
|
|
let req = tsc::RequestMethod::FindRenameLocations {
|
|
specifier,
|
|
position: line_index
|
|
.offset_tsc(params.text_document_position.position)?,
|
|
find_in_strings: false,
|
|
find_in_comments: false,
|
|
provide_prefix_and_suffix_text_for_rename: false,
|
|
};
|
|
|
|
let maybe_locations: Option<Vec<tsc::RenameLocation>> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
|
|
if let Some(locations) = maybe_locations {
|
|
let rename_locations = tsc::RenameLocations { locations };
|
|
let workspace_edits = rename_locations
|
|
.into_workspace_edit(¶ms.new_name, self)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to get workspace edits: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
self.performance.measure(mark);
|
|
Ok(Some(workspace_edits))
|
|
} else {
|
|
self.performance.measure(mark);
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
async fn request_else(
|
|
&mut self,
|
|
method: &str,
|
|
params: Option<Value>,
|
|
) -> LspResult<Option<Value>> {
|
|
match method {
|
|
lsp_custom::CACHE_REQUEST => match params.map(serde_json::from_value) {
|
|
Some(Ok(params)) => self.cache(params).await,
|
|
Some(Err(err)) => Err(LspError::invalid_params(err.to_string())),
|
|
None => Err(LspError::invalid_params("Missing parameters")),
|
|
},
|
|
lsp_custom::PERFORMANCE_REQUEST => Ok(Some(self.get_performance())),
|
|
lsp_custom::RELOAD_IMPORT_REGISTRIES_REQUEST => {
|
|
self.reload_import_registries().await
|
|
}
|
|
lsp_custom::VIRTUAL_TEXT_DOCUMENT => {
|
|
match params.map(serde_json::from_value) {
|
|
Some(Ok(params)) => Ok(Some(
|
|
serde_json::to_value(self.virtual_text_document(params).await?)
|
|
.map_err(|err| {
|
|
error!(
|
|
"Failed to serialize virtual_text_document response: {}",
|
|
err
|
|
);
|
|
LspError::internal_error()
|
|
})?,
|
|
)),
|
|
Some(Err(err)) => Err(LspError::invalid_params(err.to_string())),
|
|
None => Err(LspError::invalid_params("Missing parameters")),
|
|
}
|
|
}
|
|
_ => {
|
|
error!("Got a {} request, but no handler is defined", method);
|
|
Err(LspError::method_not_found())
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn selection_range(
|
|
&mut self,
|
|
params: SelectionRangeParams,
|
|
) -> LspResult<Option<Vec<SelectionRange>>> {
|
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("selection_range", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
|
|
let mut selection_ranges = Vec::<SelectionRange>::new();
|
|
for position in params.positions {
|
|
let req = tsc::RequestMethod::GetSmartSelectionRange((
|
|
specifier.clone(),
|
|
line_index.offset_tsc(position)?,
|
|
));
|
|
|
|
let selection_range: tsc::SelectionRange = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
|
|
selection_ranges.push(selection_range.to_selection_range(&line_index));
|
|
}
|
|
self.performance.measure(mark);
|
|
Ok(Some(selection_ranges))
|
|
}
|
|
|
|
async fn semantic_tokens_full(
|
|
&mut self,
|
|
params: SemanticTokensParams,
|
|
) -> LspResult<Option<SemanticTokensResult>> {
|
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("semantic_tokens_full", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
|
|
let req = tsc::RequestMethod::GetEncodedSemanticClassifications((
|
|
specifier.clone(),
|
|
tsc::TextSpan {
|
|
start: 0,
|
|
length: line_index.text_content_length_utf16().into(),
|
|
},
|
|
));
|
|
let semantic_classification: tsc::Classifications = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
|
|
let semantic_tokens: SemanticTokens =
|
|
semantic_classification.to_semantic_tokens(&line_index);
|
|
let response = if !semantic_tokens.data.is_empty() {
|
|
Some(SemanticTokensResult::Tokens(semantic_tokens))
|
|
} else {
|
|
None
|
|
};
|
|
self.performance.measure(mark);
|
|
Ok(response)
|
|
}
|
|
|
|
async fn semantic_tokens_range(
|
|
&mut self,
|
|
params: SemanticTokensRangeParams,
|
|
) -> LspResult<Option<SemanticTokensRangeResult>> {
|
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self
|
|
.performance
|
|
.mark("semantic_tokens_range", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
|
|
let start = line_index.offset_tsc(params.range.start)?;
|
|
let length = line_index.offset_tsc(params.range.end)? - start;
|
|
let req = tsc::RequestMethod::GetEncodedSemanticClassifications((
|
|
specifier.clone(),
|
|
tsc::TextSpan { start, length },
|
|
));
|
|
let semantic_classification: tsc::Classifications = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
|
|
let semantic_tokens: SemanticTokens =
|
|
semantic_classification.to_semantic_tokens(&line_index);
|
|
let response = if !semantic_tokens.data.is_empty() {
|
|
Some(SemanticTokensRangeResult::Tokens(semantic_tokens))
|
|
} else {
|
|
None
|
|
};
|
|
self.performance.measure(mark);
|
|
Ok(response)
|
|
}
|
|
|
|
async fn signature_help(
|
|
&mut self,
|
|
params: SignatureHelpParams,
|
|
) -> LspResult<Option<SignatureHelp>> {
|
|
let specifier = self
|
|
.url_map
|
|
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
|
if !self.documents.is_diagnosable(&specifier)
|
|
|| !self.config.specifier_enabled(&specifier)
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("signature_help", Some(¶ms));
|
|
let line_index =
|
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
|
line_index
|
|
} else {
|
|
return Err(LspError::invalid_params(format!(
|
|
"An unexpected specifier ({}) was provided.",
|
|
specifier
|
|
)));
|
|
};
|
|
let options = if let Some(context) = params.context {
|
|
tsc::SignatureHelpItemsOptions {
|
|
trigger_reason: Some(tsc::SignatureHelpTriggerReason {
|
|
kind: context.trigger_kind.into(),
|
|
trigger_character: context.trigger_character,
|
|
}),
|
|
}
|
|
} else {
|
|
tsc::SignatureHelpItemsOptions {
|
|
trigger_reason: None,
|
|
}
|
|
};
|
|
let req = tsc::RequestMethod::GetSignatureHelpItems((
|
|
specifier,
|
|
line_index.offset_tsc(params.text_document_position_params.position)?,
|
|
options,
|
|
));
|
|
let maybe_signature_help_items: Option<tsc::SignatureHelpItems> = self
|
|
.ts_server
|
|
.request(self.snapshot()?, req)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Failed to request to tsserver: {}", err);
|
|
LspError::invalid_request()
|
|
})?;
|
|
|
|
if let Some(signature_help_items) = maybe_signature_help_items {
|
|
let signature_help = signature_help_items.into_signature_help();
|
|
self.performance.measure(mark);
|
|
Ok(Some(signature_help))
|
|
} else {
|
|
self.performance.measure(mark);
|
|
Ok(None)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[lspower::async_trait]
|
|
impl lspower::LanguageServer for LanguageServer {
|
|
async fn initialize(
|
|
&self,
|
|
params: InitializeParams,
|
|
) -> LspResult<InitializeResult> {
|
|
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) {
|
|
self.0.lock().await.initialized(params).await
|
|
}
|
|
|
|
async fn shutdown(&self) -> LspResult<()> {
|
|
self.0.lock().await.shutdown().await
|
|
}
|
|
|
|
async fn did_open(&self, params: DidOpenTextDocumentParams) {
|
|
self.0.lock().await.did_open(params).await
|
|
}
|
|
|
|
async fn did_change(&self, params: DidChangeTextDocumentParams) {
|
|
self.0.lock().await.did_change(params).await
|
|
}
|
|
|
|
async fn did_save(&self, _params: DidSaveTextDocumentParams) {
|
|
// We don't need to do anything on save at the moment, but if this isn't
|
|
// implemented, lspower complains about it not being implemented.
|
|
}
|
|
|
|
async fn did_close(&self, params: DidCloseTextDocumentParams) {
|
|
self.0.lock().await.did_close(params).await
|
|
}
|
|
|
|
async fn did_change_configuration(
|
|
&self,
|
|
params: DidChangeConfigurationParams,
|
|
) {
|
|
self.0.lock().await.did_change_configuration(params).await
|
|
}
|
|
|
|
async fn did_change_watched_files(
|
|
&self,
|
|
params: DidChangeWatchedFilesParams,
|
|
) {
|
|
self.0.lock().await.did_change_watched_files(params).await
|
|
}
|
|
|
|
async fn document_symbol(
|
|
&self,
|
|
params: DocumentSymbolParams,
|
|
) -> LspResult<Option<DocumentSymbolResponse>> {
|
|
self.0.lock().await.document_symbol(params).await
|
|
}
|
|
|
|
async fn formatting(
|
|
&self,
|
|
params: DocumentFormattingParams,
|
|
) -> LspResult<Option<Vec<TextEdit>>> {
|
|
self.0.lock().await.formatting(params).await
|
|
}
|
|
|
|
async fn hover(&self, params: HoverParams) -> LspResult<Option<Hover>> {
|
|
self.0.lock().await.hover(params).await
|
|
}
|
|
|
|
async fn code_action(
|
|
&self,
|
|
params: CodeActionParams,
|
|
) -> LspResult<Option<CodeActionResponse>> {
|
|
self.0.lock().await.code_action(params).await
|
|
}
|
|
|
|
async fn code_action_resolve(
|
|
&self,
|
|
params: CodeAction,
|
|
) -> LspResult<CodeAction> {
|
|
self.0.lock().await.code_action_resolve(params).await
|
|
}
|
|
|
|
async fn code_lens(
|
|
&self,
|
|
params: CodeLensParams,
|
|
) -> LspResult<Option<Vec<CodeLens>>> {
|
|
self.0.lock().await.code_lens(params).await
|
|
}
|
|
|
|
async fn code_lens_resolve(&self, params: CodeLens) -> LspResult<CodeLens> {
|
|
self.0.lock().await.code_lens_resolve(params).await
|
|
}
|
|
|
|
async fn document_highlight(
|
|
&self,
|
|
params: DocumentHighlightParams,
|
|
) -> LspResult<Option<Vec<DocumentHighlight>>> {
|
|
self.0.lock().await.document_highlight(params).await
|
|
}
|
|
|
|
async fn references(
|
|
&self,
|
|
params: ReferenceParams,
|
|
) -> LspResult<Option<Vec<Location>>> {
|
|
self.0.lock().await.references(params).await
|
|
}
|
|
|
|
async fn goto_definition(
|
|
&self,
|
|
params: GotoDefinitionParams,
|
|
) -> LspResult<Option<GotoDefinitionResponse>> {
|
|
self.0.lock().await.goto_definition(params).await
|
|
}
|
|
|
|
async fn completion(
|
|
&self,
|
|
params: CompletionParams,
|
|
) -> LspResult<Option<CompletionResponse>> {
|
|
self.0.lock().await.completion(params).await
|
|
}
|
|
|
|
async fn completion_resolve(
|
|
&self,
|
|
params: CompletionItem,
|
|
) -> LspResult<CompletionItem> {
|
|
self.0.lock().await.completion_resolve(params).await
|
|
}
|
|
|
|
async fn goto_implementation(
|
|
&self,
|
|
params: GotoImplementationParams,
|
|
) -> LspResult<Option<GotoImplementationResponse>> {
|
|
self.0.lock().await.goto_implementation(params).await
|
|
}
|
|
|
|
async fn folding_range(
|
|
&self,
|
|
params: FoldingRangeParams,
|
|
) -> LspResult<Option<Vec<FoldingRange>>> {
|
|
self.0.lock().await.folding_range(params).await
|
|
}
|
|
|
|
async fn incoming_calls(
|
|
&self,
|
|
params: CallHierarchyIncomingCallsParams,
|
|
) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> {
|
|
self.0.lock().await.incoming_calls(params).await
|
|
}
|
|
|
|
async fn outgoing_calls(
|
|
&self,
|
|
params: CallHierarchyOutgoingCallsParams,
|
|
) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> {
|
|
self.0.lock().await.outgoing_calls(params).await
|
|
}
|
|
|
|
async fn prepare_call_hierarchy(
|
|
&self,
|
|
params: CallHierarchyPrepareParams,
|
|
) -> LspResult<Option<Vec<CallHierarchyItem>>> {
|
|
self.0.lock().await.prepare_call_hierarchy(params).await
|
|
}
|
|
|
|
async fn rename(
|
|
&self,
|
|
params: RenameParams,
|
|
) -> LspResult<Option<WorkspaceEdit>> {
|
|
self.0.lock().await.rename(params).await
|
|
}
|
|
|
|
async fn request_else(
|
|
&self,
|
|
method: &str,
|
|
params: Option<Value>,
|
|
) -> LspResult<Option<Value>> {
|
|
self.0.lock().await.request_else(method, params).await
|
|
}
|
|
|
|
async fn selection_range(
|
|
&self,
|
|
params: SelectionRangeParams,
|
|
) -> LspResult<Option<Vec<SelectionRange>>> {
|
|
self.0.lock().await.selection_range(params).await
|
|
}
|
|
|
|
async fn semantic_tokens_full(
|
|
&self,
|
|
params: SemanticTokensParams,
|
|
) -> LspResult<Option<SemanticTokensResult>> {
|
|
self.0.lock().await.semantic_tokens_full(params).await
|
|
}
|
|
|
|
async fn semantic_tokens_range(
|
|
&self,
|
|
params: SemanticTokensRangeParams,
|
|
) -> LspResult<Option<SemanticTokensRangeResult>> {
|
|
self.0.lock().await.semantic_tokens_range(params).await
|
|
}
|
|
|
|
async fn signature_help(
|
|
&self,
|
|
params: SignatureHelpParams,
|
|
) -> LspResult<Option<SignatureHelp>> {
|
|
self.0.lock().await.signature_help(params).await
|
|
}
|
|
}
|
|
|
|
// These are implementations of custom commands supported by the LSP
|
|
impl Inner {
|
|
/// Similar to `deno cache` on the command line, where modules will be cached
|
|
/// in the Deno cache, including any of their dependencies.
|
|
async fn cache(
|
|
&mut self,
|
|
params: lsp_custom::CacheParams,
|
|
) -> LspResult<Option<Value>> {
|
|
let referrer = self.url_map.normalize_url(¶ms.referrer.uri);
|
|
if !self.documents.is_diagnosable(&referrer) {
|
|
return Ok(None);
|
|
}
|
|
|
|
let mark = self.performance.mark("cache", Some(¶ms));
|
|
let roots = if !params.uris.is_empty() {
|
|
params
|
|
.uris
|
|
.iter()
|
|
.map(|t| self.url_map.normalize_url(&t.uri))
|
|
.collect()
|
|
} else {
|
|
vec![referrer.clone()]
|
|
};
|
|
|
|
if self.maybe_cache_server.is_none() {
|
|
self.maybe_cache_server = Some(
|
|
CacheServer::new(
|
|
self.maybe_cache_path.clone(),
|
|
self.maybe_import_map.clone(),
|
|
)
|
|
.await,
|
|
);
|
|
}
|
|
let cache_server = self.maybe_cache_server.as_ref().unwrap();
|
|
if let Err(err) = cache_server.cache(roots).await {
|
|
self.client.show_message(MessageType::Warning, err).await;
|
|
}
|
|
|
|
// now that we have dependencies loaded, we need to re-analyze them and
|
|
// invalidate some diagnostics
|
|
if self.documents.contains_key(&referrer) {
|
|
self.analyze_dependencies(&referrer);
|
|
self.diagnostics_server.invalidate(vec![referrer]).await;
|
|
}
|
|
|
|
self.diagnostics_server.update().map_err(|err| {
|
|
error!("{}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
self.performance.measure(mark);
|
|
Ok(Some(json!(true)))
|
|
}
|
|
|
|
fn get_performance(&self) -> Value {
|
|
let averages = self.performance.averages();
|
|
json!({ "averages": averages })
|
|
}
|
|
|
|
async fn reload_import_registries(&mut self) -> LspResult<Option<Value>> {
|
|
fs_util::remove_dir_all_if_exists(&self.module_registries_location)
|
|
.await
|
|
.map_err(|err| {
|
|
error!("Unable to remove registries cache: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
self.module_registries =
|
|
registries::ModuleRegistry::new(&self.module_registries_location);
|
|
self.update_registries().await.map_err(|err| {
|
|
error!("Unable to update registries: {}", err);
|
|
LspError::internal_error()
|
|
})?;
|
|
Ok(Some(json!(true)))
|
|
}
|
|
|
|
async fn virtual_text_document(
|
|
&mut self,
|
|
params: lsp_custom::VirtualTextDocumentParams,
|
|
) -> LspResult<Option<String>> {
|
|
let mark = self
|
|
.performance
|
|
.mark("virtual_text_document", Some(¶ms));
|
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
|
let contents = if specifier.as_str() == "deno:/status.md" {
|
|
let mut contents = String::new();
|
|
let mut documents_specifiers = self.documents.specifiers();
|
|
documents_specifiers.sort();
|
|
let mut sources_specifiers = self.sources.specifiers();
|
|
sources_specifiers.sort();
|
|
let measures = self.performance.to_vec();
|
|
let workspace_settings = self.config.get_workspace_settings();
|
|
|
|
contents.push_str(&format!(
|
|
r#"# Deno Language Server Status
|
|
|
|
## Workspace Settings
|
|
|
|
```json
|
|
{}
|
|
```
|
|
|
|
## Workspace Details
|
|
|
|
- <details><summary>Documents in memory: {}</summary>
|
|
|
|
- {}
|
|
|
|
</details>
|
|
|
|
- <details><summary>Sources in memory: {}</summary>
|
|
|
|
- {}
|
|
|
|
</details>
|
|
|
|
- <details><summary>Performance measures: {}</summary>
|
|
|
|
- {}
|
|
|
|
</details>
|
|
"#,
|
|
serde_json::to_string_pretty(&workspace_settings).unwrap(),
|
|
self.documents.len(),
|
|
documents_specifiers
|
|
.into_iter()
|
|
.map(|s| s.to_string())
|
|
.collect::<Vec<String>>()
|
|
.join("\n - "),
|
|
self.sources.len(),
|
|
sources_specifiers
|
|
.into_iter()
|
|
.map(|s| s.to_string())
|
|
.collect::<Vec<String>>()
|
|
.join("\n - "),
|
|
measures.len(),
|
|
measures
|
|
.iter()
|
|
.map(|m| m.to_string())
|
|
.collect::<Vec<String>>()
|
|
.join("\n - ")
|
|
));
|
|
contents
|
|
.push_str("\n## Performance\n\n|Name|Duration|Count|\n|---|---|---|\n");
|
|
let mut averages = self.performance.averages();
|
|
averages.sort();
|
|
for average in averages {
|
|
contents.push_str(&format!(
|
|
"|{}|{}ms|{}|\n",
|
|
average.name, average.average_duration, average.count
|
|
));
|
|
}
|
|
Some(contents)
|
|
} else {
|
|
match specifier.scheme() {
|
|
"asset" => {
|
|
if let Some(asset) = self
|
|
.get_asset(&specifier)
|
|
.await
|
|
.map_err(|_| LspError::internal_error())?
|
|
{
|
|
Some(asset.text.to_string())
|
|
} else {
|
|
error!("Missing asset: {}", specifier);
|
|
None
|
|
}
|
|
}
|
|
_ => {
|
|
if let Some(source) = self.sources.get_source(&specifier) {
|
|
Some(source.to_string())
|
|
} else {
|
|
error!("The cached source was not found: {}", specifier);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
};
|
|
self.performance.measure(mark);
|
|
Ok(contents)
|
|
}
|
|
}
|