mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 00:29:09 -05:00
feat(lsp): jupyter notebook analysis (#20719)
This commit is contained in:
parent
61b91e10ad
commit
2d1af0cf51
7 changed files with 623 additions and 135 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -980,6 +980,7 @@ dependencies = [
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"clap_complete_fig",
|
"clap_complete_fig",
|
||||||
"console_static_text",
|
"console_static_text",
|
||||||
|
"dashmap 5.5.3",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"data-url",
|
"data-url",
|
||||||
"deno_ast",
|
"deno_ast",
|
||||||
|
|
|
@ -72,6 +72,7 @@ clap = { version = "=4.3.3", features = ["string"] }
|
||||||
clap_complete = "=4.3.1"
|
clap_complete = "=4.3.1"
|
||||||
clap_complete_fig = "=4.3.1"
|
clap_complete_fig = "=4.3.1"
|
||||||
console_static_text.workspace = true
|
console_static_text.workspace = true
|
||||||
|
dashmap = "5.5.3"
|
||||||
data-encoding.workspace = true
|
data-encoding.workspace = true
|
||||||
data-url.workspace = true
|
data-url.workspace = true
|
||||||
dissimilar = "=1.0.4"
|
dissimilar = "=1.0.4"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use super::client::Client;
|
use super::client::Client;
|
||||||
use super::config::ConfigSnapshot;
|
use super::config::ConfigSnapshot;
|
||||||
|
use super::documents::cell_to_file_specifier;
|
||||||
use super::documents::Documents;
|
use super::documents::Documents;
|
||||||
use super::documents::DocumentsFilter;
|
use super::documents::DocumentsFilter;
|
||||||
use super::lsp_custom;
|
use super::lsp_custom;
|
||||||
|
@ -364,11 +365,16 @@ fn get_local_completions(
|
||||||
current: &str,
|
current: &str,
|
||||||
range: &lsp::Range,
|
range: &lsp::Range,
|
||||||
) -> Option<Vec<lsp::CompletionItem>> {
|
) -> Option<Vec<lsp::CompletionItem>> {
|
||||||
|
let base = match cell_to_file_specifier(base) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => base.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
if base.scheme() != "file" {
|
if base.scheme() != "file" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut base_path = specifier_to_file_path(base).ok()?;
|
let mut base_path = specifier_to_file_path(&base).ok()?;
|
||||||
base_path.pop();
|
base_path.pop();
|
||||||
let mut current_path = normalize_path(base_path.join(current));
|
let mut current_path = normalize_path(base_path.join(current));
|
||||||
// if the current text does not end in a `/` then we are still selecting on
|
// if the current text does not end in a `/` then we are still selecting on
|
||||||
|
@ -388,10 +394,10 @@ fn get_local_completions(
|
||||||
let de = de.ok()?;
|
let de = de.ok()?;
|
||||||
let label = de.path().file_name()?.to_string_lossy().to_string();
|
let label = de.path().file_name()?.to_string_lossy().to_string();
|
||||||
let entry_specifier = resolve_path(de.path().to_str()?, &cwd).ok()?;
|
let entry_specifier = resolve_path(de.path().to_str()?, &cwd).ok()?;
|
||||||
if &entry_specifier == base {
|
if entry_specifier == base {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let full_text = relative_specifier(base, &entry_specifier)?;
|
let full_text = relative_specifier(&base, &entry_specifier)?;
|
||||||
// this weeds out situations where we are browsing in the parent, but
|
// this weeds out situations where we are browsing in the parent, but
|
||||||
// we want to filter out non-matches when the completion is manually
|
// we want to filter out non-matches when the completion is manually
|
||||||
// invoked by the user, but still allows for things like `../src/../`
|
// invoked by the user, but still allows for things like `../src/../`
|
||||||
|
|
|
@ -692,10 +692,14 @@ impl WorkspaceSettings {
|
||||||
self.code_lens.implementations || self.code_lens.references
|
self.code_lens.implementations || self.code_lens.references
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(nayeemrmn): Factor in out-of-band media type here.
|
||||||
pub fn language_settings_for_specifier(
|
pub fn language_settings_for_specifier(
|
||||||
&self,
|
&self,
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
) -> Option<&LanguageWorkspaceSettings> {
|
) -> Option<&LanguageWorkspaceSettings> {
|
||||||
|
if specifier.scheme() == "deno-notebook-cell" {
|
||||||
|
return Some(&self.typescript);
|
||||||
|
}
|
||||||
match MediaType::from_specifier(specifier) {
|
match MediaType::from_specifier(specifier) {
|
||||||
MediaType::JavaScript
|
MediaType::JavaScript
|
||||||
| MediaType::Jsx
|
| MediaType::Jsx
|
||||||
|
|
|
@ -16,7 +16,6 @@ use crate::cache::HttpCache;
|
||||||
use crate::file_fetcher::get_source_from_bytes;
|
use crate::file_fetcher::get_source_from_bytes;
|
||||||
use crate::file_fetcher::get_source_from_data_url;
|
use crate::file_fetcher::get_source_from_data_url;
|
||||||
use crate::file_fetcher::map_content_type;
|
use crate::file_fetcher::map_content_type;
|
||||||
use crate::file_fetcher::SUPPORTED_SCHEMES;
|
|
||||||
use crate::lsp::logging::lsp_warn;
|
use crate::lsp::logging::lsp_warn;
|
||||||
use crate::npm::CliNpmRegistryApi;
|
use crate::npm::CliNpmRegistryApi;
|
||||||
use crate::npm::NpmResolution;
|
use crate::npm::NpmResolution;
|
||||||
|
@ -93,6 +92,15 @@ static TSX_HEADERS: Lazy<HashMap<String, String>> = Lazy::new(|| {
|
||||||
.collect()
|
.collect()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub const DOCUMENT_SCHEMES: [&str; 6] = [
|
||||||
|
"data",
|
||||||
|
"blob",
|
||||||
|
"file",
|
||||||
|
"http",
|
||||||
|
"https",
|
||||||
|
"deno-notebook-cell",
|
||||||
|
];
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum LanguageId {
|
pub enum LanguageId {
|
||||||
JavaScript,
|
JavaScript,
|
||||||
|
@ -254,6 +262,27 @@ impl AssetOrDocument {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a `deno-notebook-cell:` specifier to a `file:` specifier.
|
||||||
|
/// ```rust
|
||||||
|
/// assert_eq!(
|
||||||
|
/// cell_to_file_specifier(
|
||||||
|
/// &Url::parse("deno-notebook-cell:/path/to/file.ipynb#abc").unwrap(),
|
||||||
|
/// ),
|
||||||
|
/// Some(Url::parse("file:///path/to/file.ipynb#abc").unwrap()),
|
||||||
|
/// );
|
||||||
|
pub fn cell_to_file_specifier(specifier: &Url) -> Option<Url> {
|
||||||
|
if specifier.scheme() == "deno-notebook-cell" {
|
||||||
|
if let Ok(specifier) = ModuleSpecifier::parse(&format!(
|
||||||
|
"file://{}",
|
||||||
|
&specifier.as_str()
|
||||||
|
[url::quirks::internal_components(specifier).host_end as usize..],
|
||||||
|
)) {
|
||||||
|
return Some(specifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct DocumentDependencies {
|
struct DocumentDependencies {
|
||||||
deps: IndexMap<String, deno_graph::Dependency>,
|
deps: IndexMap<String, deno_graph::Dependency>,
|
||||||
|
@ -270,11 +299,33 @@ impl DocumentDependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_module(module: &deno_graph::EsmModule) -> Self {
|
pub fn from_module(module: &deno_graph::EsmModule) -> Self {
|
||||||
Self {
|
let mut deps = Self {
|
||||||
deps: module.dependencies.clone(),
|
deps: module.dependencies.clone(),
|
||||||
maybe_types_dependency: module.maybe_types_dependency.clone(),
|
maybe_types_dependency: module.maybe_types_dependency.clone(),
|
||||||
|
};
|
||||||
|
if module.specifier.scheme() == "deno-notebook-cell" {
|
||||||
|
for (_, dep) in &mut deps.deps {
|
||||||
|
if let Resolution::Ok(resolved) = &mut dep.maybe_code {
|
||||||
|
if let Some(specifier) = cell_to_file_specifier(&resolved.specifier) {
|
||||||
|
resolved.specifier = specifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Resolution::Ok(resolved) = &mut dep.maybe_type {
|
||||||
|
if let Some(specifier) = cell_to_file_specifier(&resolved.specifier) {
|
||||||
|
resolved.specifier = specifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(dep) = &mut deps.maybe_types_dependency {
|
||||||
|
if let Resolution::Ok(resolved) = &mut dep.dependency {
|
||||||
|
if let Some(specifier) = cell_to_file_specifier(&resolved.specifier) {
|
||||||
|
resolved.specifier = specifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deps
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModuleResult = Result<deno_graph::EsmModule, deno_graph::ModuleGraphError>;
|
type ModuleResult = Result<deno_graph::EsmModule, deno_graph::ModuleGraphError>;
|
||||||
|
@ -677,13 +728,11 @@ impl SpecifierResolver {
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
) -> Option<ModuleSpecifier> {
|
) -> Option<ModuleSpecifier> {
|
||||||
let scheme = specifier.scheme();
|
let scheme = specifier.scheme();
|
||||||
if !SUPPORTED_SCHEMES.contains(&scheme) {
|
if !DOCUMENT_SCHEMES.contains(&scheme) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if scheme == "data" || scheme == "blob" || scheme == "file" {
|
if scheme == "http" || scheme == "https" {
|
||||||
Some(specifier.clone())
|
|
||||||
} else {
|
|
||||||
let mut redirects = self.redirects.lock();
|
let mut redirects = self.redirects.lock();
|
||||||
if let Some(specifier) = redirects.get(specifier) {
|
if let Some(specifier) = redirects.get(specifier) {
|
||||||
Some(specifier.clone())
|
Some(specifier.clone())
|
||||||
|
@ -692,6 +741,8 @@ impl SpecifierResolver {
|
||||||
redirects.insert(specifier.clone(), redirect.clone());
|
redirects.insert(specifier.clone(), redirect.clone());
|
||||||
Some(redirect)
|
Some(redirect)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Some(specifier.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
554
cli/lsp/tsc.rs
554
cli/lsp/tsc.rs
File diff suppressed because it is too large
Load diff
|
@ -7734,6 +7734,49 @@ fn lsp_diagnostics_refresh_dependents() {
|
||||||
assert_eq!(client.queue_len(), 0);
|
assert_eq!(client.queue_len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsp_jupyter_diagnostics() {
|
||||||
|
let context = TestContextBuilder::new().use_temp_cwd().build();
|
||||||
|
let mut client = context.new_lsp_command().build();
|
||||||
|
client.initialize_default();
|
||||||
|
let diagnostics = client.did_open(json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "deno-notebook-cell:/a/file.ts#abc",
|
||||||
|
"languageId": "typescript",
|
||||||
|
"version": 1,
|
||||||
|
"text": "Deno.readTextFileSync(1234);",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
assert_eq!(
|
||||||
|
json!(diagnostics.all_messages()),
|
||||||
|
json!([
|
||||||
|
{
|
||||||
|
"uri": "deno-notebook-cell:/a/file.ts#abc",
|
||||||
|
"diagnostics": [
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 22,
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 26,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"severity": 1,
|
||||||
|
"code": 2345,
|
||||||
|
"source": "deno-ts",
|
||||||
|
"message": "Argument of type 'number' is not assignable to parameter of type 'string | URL'.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"version": 1,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
|
client.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PerformanceAverage {
|
pub struct PerformanceAverage {
|
||||||
|
@ -9504,7 +9547,7 @@ fn lsp_data_urls_with_jsx_compiler_option() {
|
||||||
"end": { "line": 1, "character": 1 }
|
"end": { "line": 1, "character": 1 }
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"uri": "deno:/5c42b5916c4a3fb55be33fdb0c3b1f438639420592d150fca1b6dc043c1df3d9/data_url.ts",
|
"uri": "deno:/ed0224c51f7e2a845dfc0941ed6959675e5e3e3d2a39b127f0ff569c1ffda8d8/data_url.ts",
|
||||||
"range": {
|
"range": {
|
||||||
"start": { "line": 0, "character": 7 },
|
"start": { "line": 0, "character": 7 },
|
||||||
"end": {"line": 0, "character": 14 },
|
"end": {"line": 0, "character": 14 },
|
||||||
|
|
Loading…
Reference in a new issue