mirror of
https://github.com/denoland/deno.git
synced 2025-02-08 15:21:26 -05:00
feat(lsp): jupyter cell continuity
This commit is contained in:
parent
b72d1a7256
commit
d9bb791c20
5 changed files with 532 additions and 34 deletions
|
@ -151,10 +151,28 @@ pub fn server_capabilities(
|
|||
})),
|
||||
inlay_hint_provider: Some(OneOf::Left(true)),
|
||||
position_encoding: None,
|
||||
// TODO(nayeemrmn): Support pull-based diagnostics.
|
||||
diagnostic_provider: None,
|
||||
inline_value_provider: None,
|
||||
inline_completion_provider: None,
|
||||
notebook_document_sync: None,
|
||||
notebook_document_sync: Some(OneOf::Left(NotebookDocumentSyncOptions {
|
||||
notebook_selector: vec![NotebookSelector::ByCells {
|
||||
notebook: None,
|
||||
cells: vec![
|
||||
NotebookCellSelector {
|
||||
language: "javascript".to_string(),
|
||||
},
|
||||
NotebookCellSelector {
|
||||
language: "javascriptreact".to_string(),
|
||||
},
|
||||
NotebookCellSelector {
|
||||
language: "typescript".to_string(),
|
||||
},
|
||||
NotebookCellSelector {
|
||||
language: "typescriptreact".to_string(),
|
||||
},
|
||||
],
|
||||
}],
|
||||
save: Some(true),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use super::config::Config;
|
|||
use super::resolver::LspResolver;
|
||||
use super::testing::TestCollector;
|
||||
use super::testing::TestModule;
|
||||
use super::text::IndexValid;
|
||||
use super::text::LineIndex;
|
||||
use super::tsc;
|
||||
use super::tsc::AssetDocument;
|
||||
|
@ -126,21 +127,6 @@ impl FromStr for LanguageId {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum IndexValid {
|
||||
All,
|
||||
UpTo(u32),
|
||||
}
|
||||
|
||||
impl IndexValid {
|
||||
fn covers(&self, line: u32) -> bool {
|
||||
match *self {
|
||||
IndexValid::UpTo(to) => to > line,
|
||||
IndexValid::All => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AssetOrDocument {
|
||||
Document(Arc<Document>),
|
||||
|
@ -1038,15 +1024,20 @@ impl Documents {
|
|||
/// Close an open document, this essentially clears any editor state that is
|
||||
/// being held, and the document store will revert to the file system if
|
||||
/// information about the document is required.
|
||||
pub fn close(&mut self, specifier: &ModuleSpecifier) {
|
||||
pub fn close(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<Arc<Document>> {
|
||||
if let Some(document) = self.open_docs.remove(specifier) {
|
||||
let document = document.closed(&self.cache);
|
||||
self
|
||||
.file_system_docs
|
||||
.docs
|
||||
.insert(specifier.clone(), document);
|
||||
|
||||
.insert(specifier.clone(), document.clone());
|
||||
self.dirty = true;
|
||||
Some(document)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -105,6 +105,7 @@ use crate::lsp::config::ConfigWatchedFileType;
|
|||
use crate::lsp::logging::init_log_file;
|
||||
use crate::lsp::tsc::file_text_changes_to_workspace_edit;
|
||||
use crate::lsp::urls::LspUrlKind;
|
||||
use crate::lsp::urls::NotebookCellInfo;
|
||||
use crate::tools::fmt::format_file;
|
||||
use crate::tools::fmt::format_parsed_source;
|
||||
use crate::tools::upgrade::check_for_upgrades_for_lsp;
|
||||
|
@ -726,7 +727,8 @@ impl Inner {
|
|||
(
|
||||
self
|
||||
.url_map
|
||||
.uri_to_specifier(&folder.uri, LspUrlKind::Folder),
|
||||
.uri_to_specifier2(&folder.uri, LspUrlKind::Folder)
|
||||
.into_specifier(),
|
||||
folder,
|
||||
)
|
||||
})
|
||||
|
@ -737,8 +739,10 @@ impl Inner {
|
|||
#[allow(deprecated)]
|
||||
if let Some(root_uri) = params.root_uri {
|
||||
if !workspace_folders.iter().any(|(_, f)| f.uri == root_uri) {
|
||||
let root_url =
|
||||
self.url_map.uri_to_specifier(&root_uri, LspUrlKind::Folder);
|
||||
let root_url = self
|
||||
.url_map
|
||||
.uri_to_specifier2(&root_uri, LspUrlKind::Folder)
|
||||
.into_specifier();
|
||||
let name = root_url.path_segments().and_then(|s| s.last());
|
||||
let name = name.unwrap_or_default().to_string();
|
||||
workspace_folders.insert(
|
||||
|
@ -1046,7 +1050,8 @@ impl Inner {
|
|||
.filter(|s| self.documents.is_valid_file_referrer(s));
|
||||
let specifier = self
|
||||
.url_map
|
||||
.uri_to_specifier(¶ms.text_document.uri, LspUrlKind::File);
|
||||
.uri_to_specifier2(¶ms.text_document.uri, LspUrlKind::File)
|
||||
.into_specifier();
|
||||
let document = self.documents.open(
|
||||
specifier.clone(),
|
||||
params.text_document.version,
|
||||
|
@ -1068,7 +1073,8 @@ impl Inner {
|
|||
let mark = self.performance.mark_with_args("lsp.did_change", ¶ms);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.uri_to_specifier(¶ms.text_document.uri, LspUrlKind::File);
|
||||
.uri_to_specifier2(¶ms.text_document.uri, LspUrlKind::File)
|
||||
.into_specifier();
|
||||
match self.documents.change(
|
||||
&specifier,
|
||||
params.text_document.version,
|
||||
|
@ -1105,7 +1111,8 @@ impl Inner {
|
|||
let _mark = self.performance.measure_scope("lsp.did_save");
|
||||
let specifier = self
|
||||
.url_map
|
||||
.uri_to_specifier(¶ms.text_document.uri, LspUrlKind::File);
|
||||
.uri_to_specifier2(¶ms.text_document.uri, LspUrlKind::File)
|
||||
.into_specifier();
|
||||
self.documents.save(&specifier);
|
||||
if !self
|
||||
.config
|
||||
|
@ -1151,7 +1158,8 @@ impl Inner {
|
|||
}
|
||||
let specifier = self
|
||||
.url_map
|
||||
.uri_to_specifier(¶ms.text_document.uri, LspUrlKind::File);
|
||||
.uri_to_specifier2(¶ms.text_document.uri, LspUrlKind::File)
|
||||
.into_specifier();
|
||||
self.diagnostics_state.clear(&specifier);
|
||||
if self.is_diagnosable(&specifier) {
|
||||
self.refresh_npm_specifiers().await;
|
||||
|
@ -1164,6 +1172,122 @@ impl Inner {
|
|||
self.performance.measure(mark);
|
||||
}
|
||||
|
||||
async fn notebook_did_open(&mut self, params: DidOpenNotebookDocumentParams) {
|
||||
dbg!(
|
||||
"notebook_did_open",
|
||||
params.notebook_document.uri.to_string()
|
||||
);
|
||||
let _mark = self.performance.measure_scope("lsp.notebook_did_open");
|
||||
let params = lsp_types::DidOpenTextDocumentParams {
|
||||
text_document: self.url_map.notebook_did_open(params),
|
||||
};
|
||||
self.did_open(params).await;
|
||||
}
|
||||
|
||||
async fn notebook_did_change(
|
||||
&mut self,
|
||||
params: DidChangeNotebookDocumentParams,
|
||||
) {
|
||||
dbg!(
|
||||
"notebook_did_change",
|
||||
params.notebook_document.uri.to_string()
|
||||
);
|
||||
let _mark = self.performance.measure_scope("lsp.notebook_did_change");
|
||||
let item = match self.url_map.notebook_did_change(params) {
|
||||
Ok(item) => item,
|
||||
Err(err) => {
|
||||
lsp_warn!("{:#}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let language_id = item.language_id.parse().unwrap_or_else(|err| {
|
||||
error!("{:#}", err);
|
||||
LanguageId::Unknown
|
||||
});
|
||||
if language_id == LanguageId::Unknown {
|
||||
lsp_warn!(
|
||||
"Unsupported language id \"{}\" received for document \"{}\".",
|
||||
item.language_id,
|
||||
item.uri.as_str()
|
||||
);
|
||||
}
|
||||
let specifier = self
|
||||
.url_map
|
||||
.uri_to_specifier2(&item.uri, LspUrlKind::File)
|
||||
.into_specifier();
|
||||
let mut language_id_changed = false;
|
||||
if let Some(old_document) = self.documents.close(&specifier) {
|
||||
if let Some(old_language_id) = old_document.maybe_language_id() {
|
||||
if language_id != old_language_id {
|
||||
self.project_changed(
|
||||
[(old_document.specifier(), ChangeKind::Closed)],
|
||||
false,
|
||||
);
|
||||
language_id_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
let file_referrer = Some(uri_to_url(&item.uri))
|
||||
.filter(|s| self.documents.is_valid_file_referrer(s));
|
||||
let specifier = self
|
||||
.url_map
|
||||
.uri_to_specifier2(&item.uri, LspUrlKind::File)
|
||||
.into_specifier();
|
||||
self.documents.close(&specifier);
|
||||
let document = self.documents.open(
|
||||
specifier.clone(),
|
||||
item.version,
|
||||
language_id,
|
||||
item.text.into(),
|
||||
file_referrer,
|
||||
);
|
||||
if document.is_diagnosable() {
|
||||
if language_id_changed {
|
||||
self
|
||||
.project_changed([(document.specifier(), ChangeKind::Opened)], false);
|
||||
} else {
|
||||
self.project_changed(
|
||||
[(document.specifier(), ChangeKind::Modified)],
|
||||
false,
|
||||
);
|
||||
}
|
||||
self.refresh_npm_specifiers().await;
|
||||
self.diagnostics_server.invalidate(&[specifier]);
|
||||
self.send_diagnostics_update();
|
||||
self.send_testing_update();
|
||||
}
|
||||
}
|
||||
|
||||
fn notebook_did_save(&mut self, params: DidSaveNotebookDocumentParams) {
|
||||
dbg!(
|
||||
"notebook_did_save",
|
||||
params.notebook_document.uri.to_string()
|
||||
);
|
||||
let _mark = self.performance.measure_scope("lsp.notebook_did_save");
|
||||
let params = DidSaveTextDocumentParams {
|
||||
text_document: TextDocumentIdentifier {
|
||||
uri: params.notebook_document.uri,
|
||||
},
|
||||
text: None,
|
||||
};
|
||||
self.did_save(params);
|
||||
}
|
||||
|
||||
async fn notebook_did_close(
|
||||
&mut self,
|
||||
params: DidCloseNotebookDocumentParams,
|
||||
) {
|
||||
dbg!(
|
||||
"notebook_did_close",
|
||||
params.notebook_document.uri.to_string()
|
||||
);
|
||||
let _mark = self.performance.measure_scope("lsp.notebook_did_close");
|
||||
let params = DidCloseTextDocumentParams {
|
||||
text_document: self.url_map.notebook_did_close(params),
|
||||
};
|
||||
self.did_close(params).await;
|
||||
}
|
||||
|
||||
async fn did_change_configuration(
|
||||
&mut self,
|
||||
params: DidChangeConfigurationParams,
|
||||
|
@ -1205,7 +1329,15 @@ impl Inner {
|
|||
let changes = params
|
||||
.changes
|
||||
.into_iter()
|
||||
.map(|e| (self.url_map.uri_to_specifier(&e.uri, LspUrlKind::File), e))
|
||||
.map(|e| {
|
||||
(
|
||||
self
|
||||
.url_map
|
||||
.uri_to_specifier2(&e.uri, LspUrlKind::File)
|
||||
.into_specifier(),
|
||||
e,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if changes
|
||||
.iter()
|
||||
|
@ -1283,11 +1415,12 @@ impl Inner {
|
|||
&self,
|
||||
params: DocumentSymbolParams,
|
||||
) -> LspResult<Option<DocumentSymbolResponse>> {
|
||||
let specifier = self
|
||||
let mapped_specifier = self
|
||||
.url_map
|
||||
.uri_to_specifier(¶ms.text_document.uri, LspUrlKind::File);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
.uri_to_specifier2(¶ms.text_document.uri, LspUrlKind::File);
|
||||
let specifier = mapped_specifier.specifier();
|
||||
if !self.is_diagnosable(specifier)
|
||||
|| !self.config.specifier_enabled(specifier)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
@ -1295,11 +1428,11 @@ impl Inner {
|
|||
let mark = self
|
||||
.performance
|
||||
.mark_with_args("lsp.document_symbol", ¶ms);
|
||||
let asset_or_document = self.get_asset_or_document(&specifier)?;
|
||||
let asset_or_document = self.get_asset_or_document(specifier)?;
|
||||
let line_index = asset_or_document.line_index();
|
||||
|
||||
let navigation_tree =
|
||||
self.get_navigation_tree(&specifier).await.map_err(|err| {
|
||||
self.get_navigation_tree(specifier).await.map_err(|err| {
|
||||
error!(
|
||||
"Error getting document symbols for \"{}\": {:#}",
|
||||
specifier, err
|
||||
|
@ -1313,6 +1446,28 @@ impl Inner {
|
|||
item
|
||||
.collect_document_symbols(line_index.clone(), &mut document_symbols);
|
||||
}
|
||||
if let Some(notebook_cell_info) = mapped_specifier.notebook_cell_info() {
|
||||
fn map_to_cell_ranges(
|
||||
mut symbol: DocumentSymbol,
|
||||
notebook_cell_info: &NotebookCellInfo,
|
||||
) -> Option<DocumentSymbol> {
|
||||
symbol.range =
|
||||
notebook_cell_info.range_server_to_client(symbol.range)?;
|
||||
symbol.selection_range = notebook_cell_info
|
||||
.range_server_to_client(symbol.selection_range)?;
|
||||
symbol.children = symbol.children.map(|children| {
|
||||
children
|
||||
.into_iter()
|
||||
.filter_map(|s| map_to_cell_ranges(s, notebook_cell_info))
|
||||
.collect()
|
||||
});
|
||||
Some(symbol)
|
||||
}
|
||||
document_symbols = document_symbols
|
||||
.into_iter()
|
||||
.filter_map(|s| map_to_cell_ranges(s, notebook_cell_info))
|
||||
.collect();
|
||||
}
|
||||
Some(DocumentSymbolResponse::Nested(document_symbols))
|
||||
} else {
|
||||
None
|
||||
|
@ -3125,6 +3280,34 @@ impl tower_lsp::LanguageServer for LanguageServer {
|
|||
self.inner.write().await.did_close(params).await
|
||||
}
|
||||
|
||||
async fn notebook_did_open(&self, params: DidOpenNotebookDocumentParams) {
|
||||
if !self.init_flag.is_raised() {
|
||||
self.init_flag.wait_raised().await;
|
||||
}
|
||||
self.inner.write().await.notebook_did_open(params).await
|
||||
}
|
||||
|
||||
async fn notebook_did_change(&self, params: DidChangeNotebookDocumentParams) {
|
||||
if !self.init_flag.is_raised() {
|
||||
self.init_flag.wait_raised().await;
|
||||
}
|
||||
self.inner.write().await.notebook_did_change(params).await
|
||||
}
|
||||
|
||||
async fn notebook_did_save(&self, params: DidSaveNotebookDocumentParams) {
|
||||
if !self.init_flag.is_raised() {
|
||||
self.init_flag.wait_raised().await;
|
||||
}
|
||||
self.inner.write().await.notebook_did_save(params)
|
||||
}
|
||||
|
||||
async fn notebook_did_close(&self, params: DidCloseNotebookDocumentParams) {
|
||||
if !self.init_flag.is_raised() {
|
||||
self.init_flag.wait_raised().await;
|
||||
}
|
||||
self.inner.write().await.notebook_did_close(params).await
|
||||
}
|
||||
|
||||
async fn did_change_configuration(
|
||||
&self,
|
||||
params: DidChangeConfigurationParams,
|
||||
|
|
|
@ -40,6 +40,21 @@ where
|
|||
left
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum IndexValid {
|
||||
All,
|
||||
UpTo(u32),
|
||||
}
|
||||
|
||||
impl IndexValid {
|
||||
pub fn covers(&self, line: u32) -> bool {
|
||||
match *self {
|
||||
IndexValid::UpTo(to) => to > line,
|
||||
IndexValid::All => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Utf16Char {
|
||||
pub start: TextSize,
|
||||
|
|
291
cli/lsp/urls.rs
291
cli/lsp/urls.rs
|
@ -1,19 +1,25 @@
|
|||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_ast::MediaType;
|
||||
use deno_core::error::custom_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::parking_lot::Mutex;
|
||||
use deno_core::url::Position;
|
||||
use deno_core::url::Url;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use indexmap::IndexMap;
|
||||
use lsp_types::Uri;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::lsp::text::IndexValid;
|
||||
|
||||
use super::cache::LspCache;
|
||||
use super::logging::lsp_warn;
|
||||
use super::text::LineIndex;
|
||||
|
||||
/// Used in situations where a default URL needs to be used where otherwise a
|
||||
/// panic is undesired.
|
||||
|
@ -101,10 +107,141 @@ fn from_deno_url(url: &Url) -> Option<Url> {
|
|||
Url::parse(&string).ok()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NotebookCell {
|
||||
item: lsp_types::TextDocumentItem,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Notebook {
|
||||
uri: Uri,
|
||||
cells: IndexMap<Uri, lsp_types::TextDocumentItem>,
|
||||
version: i32,
|
||||
script_language_id: Option<String>,
|
||||
script_cells: IndexMap<Uri, NotebookScriptCellInfo>,
|
||||
}
|
||||
|
||||
impl Notebook {
|
||||
fn new(
|
||||
uri: Uri,
|
||||
cells: IndexMap<Uri, lsp_types::TextDocumentItem>,
|
||||
version: i32,
|
||||
) -> Self {
|
||||
static SCRIPT_LANGUAGE_IDS: &[&str] = &[
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"jsx",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"tsx",
|
||||
];
|
||||
let script_language_id = cells.values().find_map(|i| {
|
||||
SCRIPT_LANGUAGE_IDS
|
||||
.contains(&i.language_id.as_str())
|
||||
.then(|| i.language_id.clone())
|
||||
});
|
||||
let mut script_cells = IndexMap::new();
|
||||
let mut script_line_offset = 0;
|
||||
if let Some(language_id) = &script_language_id {
|
||||
for item in cells.values() {
|
||||
if &item.language_id != language_id {
|
||||
continue;
|
||||
}
|
||||
let line_count =
|
||||
item.text.chars().filter(|c| *c == '\n').count() as u32;
|
||||
let cell_info = NotebookScriptCellInfo {
|
||||
line_offset: script_line_offset,
|
||||
line_count,
|
||||
};
|
||||
script_line_offset += line_count;
|
||||
script_cells.insert(item.uri.clone(), cell_info);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
uri,
|
||||
cells,
|
||||
version,
|
||||
script_language_id,
|
||||
script_cells,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_change(
|
||||
self,
|
||||
params: lsp_types::DidChangeNotebookDocumentParams,
|
||||
) -> Self {
|
||||
let mut cells = self.cells;
|
||||
if let Some(cell_change) = params.change.cells {
|
||||
if let Some(structure) = cell_change.structure {
|
||||
if let Some(did_close) = structure.did_close {
|
||||
let closed_cells =
|
||||
did_close.into_iter().map(|i| i.uri).collect::<Vec<_>>();
|
||||
cells.retain(|i, _| !closed_cells.contains(i));
|
||||
}
|
||||
if let Some(did_open) = structure.did_open {
|
||||
cells.extend(did_open.into_iter().map(|i| (i.uri.clone(), i)));
|
||||
}
|
||||
}
|
||||
if let Some(changes) = cell_change.text_content {
|
||||
for change in changes {
|
||||
let Some(item) =
|
||||
cells.values_mut().find(|i| &i.uri == &change.document.uri)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
item.version = change.document.version;
|
||||
let mut content = item.text.clone();
|
||||
let mut line_index = LineIndex::new(&item.text);
|
||||
let mut index_valid = IndexValid::All;
|
||||
for change in change.changes {
|
||||
if let Some(range) = change.range {
|
||||
if !index_valid.covers(range.start.line) {
|
||||
line_index = LineIndex::new(&content);
|
||||
}
|
||||
index_valid = IndexValid::UpTo(range.start.line);
|
||||
let Ok(range) = line_index.get_text_range(range) else {
|
||||
continue;
|
||||
};
|
||||
content.replace_range(Range::<usize>::from(range), &change.text);
|
||||
} else {
|
||||
content = change.text;
|
||||
index_valid = IndexValid::UpTo(0);
|
||||
}
|
||||
}
|
||||
item.text = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::new(self.uri, cells, params.notebook_document.version)
|
||||
}
|
||||
|
||||
fn script_text_document(&self) -> lsp_types::TextDocumentItem {
|
||||
let text = self
|
||||
.script_cells
|
||||
.iter()
|
||||
.map(|(u, _)| [self.cells.get(u).unwrap().text.as_str(), "\n"])
|
||||
.flatten()
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
dbg!(self.uri.as_str(), &text);
|
||||
lsp_types::TextDocumentItem {
|
||||
uri: self.uri.clone(),
|
||||
language_id: self
|
||||
.script_language_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| "markdown".to_string()),
|
||||
version: self.version,
|
||||
text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct LspUrlMapInner {
|
||||
specifier_to_uri: HashMap<ModuleSpecifier, Uri>,
|
||||
uri_to_specifier: HashMap<Uri, ModuleSpecifier>,
|
||||
notebooks: HashMap<Uri, Notebook>,
|
||||
script_cell_to_notebook_uri: HashMap<Uri, Uri>,
|
||||
}
|
||||
|
||||
impl LspUrlMapInner {
|
||||
|
@ -132,6 +269,53 @@ pub fn uri_to_url(uri: &Uri) -> Url {
|
|||
Url::parse(uri.as_str()).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy)]
|
||||
pub struct NotebookScriptCellInfo {
|
||||
pub line_offset: u32,
|
||||
pub line_count: u32,
|
||||
}
|
||||
|
||||
impl NotebookScriptCellInfo {
|
||||
pub fn range_server_to_client(
|
||||
&self,
|
||||
mut range: lsp_types::Range,
|
||||
) -> Option<lsp_types::Range> {
|
||||
range.start.line = range.start.line.checked_sub(self.line_offset)?;
|
||||
range.end.line = range.end.line.checked_sub(self.line_offset)?;
|
||||
if range.end.line > self.line_count {
|
||||
return None;
|
||||
}
|
||||
Some(range)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MappedSpecifier {
|
||||
Module(ModuleSpecifier),
|
||||
NotebookScript(ModuleSpecifier, NotebookScriptCellInfo),
|
||||
}
|
||||
|
||||
impl MappedSpecifier {
|
||||
pub fn specifier(&self) -> &ModuleSpecifier {
|
||||
match self {
|
||||
Self::Module(s) | Self::NotebookScript(s, _) => s,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_specifier(self) -> ModuleSpecifier {
|
||||
match self {
|
||||
Self::Module(s) | Self::NotebookScript(s, _) => s,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notebook_cell_info(&self) -> Option<&NotebookScriptCellInfo> {
|
||||
match self {
|
||||
Self::NotebookScript(_, i) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum LspUrlKind {
|
||||
File,
|
||||
|
@ -235,6 +419,113 @@ impl LspUrlMap {
|
|||
inner.put(specifier.clone(), uri.clone());
|
||||
specifier
|
||||
}
|
||||
|
||||
pub fn uri_to_specifier2(
|
||||
&self,
|
||||
uri: &Uri,
|
||||
kind: LspUrlKind,
|
||||
) -> MappedSpecifier {
|
||||
let notebook_script = (|| {
|
||||
let mut inner = self.inner.lock();
|
||||
let notebook_uri = inner.script_cell_to_notebook_uri.get(uri)?;
|
||||
let notebook = inner.notebooks.get(notebook_uri)?;
|
||||
let cell_info = *notebook.script_cells.get(uri)?;
|
||||
Some((notebook_uri.clone(), cell_info))
|
||||
})();
|
||||
if let Some((notebook_uri, cell_info)) = notebook_script {
|
||||
let specifier = self.uri_to_specifier(¬ebook_uri, kind);
|
||||
return MappedSpecifier::NotebookScript(specifier, cell_info);
|
||||
}
|
||||
MappedSpecifier::Module(self.uri_to_specifier(uri, kind))
|
||||
}
|
||||
|
||||
pub fn specifier_to_uri2(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
_line: Option<u32>,
|
||||
file_referrer: Option<&ModuleSpecifier>,
|
||||
) -> Result<(Uri, Option<NotebookScriptCellInfo>), AnyError> {
|
||||
// TODO(nayeemrmn): Implement!
|
||||
self
|
||||
.specifier_to_uri(specifier, file_referrer)
|
||||
.map(|s| (s, None))
|
||||
}
|
||||
|
||||
pub fn notebook_did_open(
|
||||
&self,
|
||||
params: lsp_types::DidOpenNotebookDocumentParams,
|
||||
) -> lsp_types::TextDocumentItem {
|
||||
let mut inner = self.inner.lock();
|
||||
let notebook = Notebook::new(
|
||||
params.notebook_document.uri,
|
||||
params
|
||||
.cell_text_documents
|
||||
.into_iter()
|
||||
.map(|i| (i.uri.clone(), i))
|
||||
.collect(),
|
||||
params.notebook_document.version,
|
||||
);
|
||||
for script_cell_uri in notebook.script_cells.keys() {
|
||||
inner
|
||||
.script_cell_to_notebook_uri
|
||||
.insert(script_cell_uri.clone(), notebook.uri.clone());
|
||||
}
|
||||
let item = notebook.script_text_document();
|
||||
inner.notebooks.insert(notebook.uri.clone(), notebook);
|
||||
item
|
||||
}
|
||||
|
||||
pub fn notebook_did_change(
|
||||
&self,
|
||||
params: lsp_types::DidChangeNotebookDocumentParams,
|
||||
) -> Result<lsp_types::TextDocumentItem, AnyError> {
|
||||
let mut inner = self.inner.lock();
|
||||
let Some(notebook) = inner.notebooks.remove(¶ms.notebook_document.uri)
|
||||
else {
|
||||
return Err(custom_error(
|
||||
"NotFound",
|
||||
format!(
|
||||
"The notebook \"{}\" was not found.",
|
||||
params.notebook_document.uri.as_str()
|
||||
),
|
||||
));
|
||||
};
|
||||
let structure_changed =
|
||||
params.change.cells.is_some_and(|c| c.structure.is_some());
|
||||
if structure_changed {
|
||||
for script_cell_uri in notebook.script_cells.keys() {
|
||||
inner.script_cell_to_notebook_uri.remove(&script_cell_uri);
|
||||
}
|
||||
}
|
||||
let notebook = notebook.with_change(params);
|
||||
if structure_changed {
|
||||
for script_cell_uri in notebook.script_cells.keys() {
|
||||
inner
|
||||
.script_cell_to_notebook_uri
|
||||
.insert(script_cell_uri.clone(), notebook.uri.clone());
|
||||
}
|
||||
}
|
||||
let item = notebook.script_text_document();
|
||||
inner.notebooks.insert(notebook.uri.clone(), notebook);
|
||||
Ok(item)
|
||||
}
|
||||
|
||||
pub fn notebook_did_close(
|
||||
&self,
|
||||
params: lsp_types::DidCloseNotebookDocumentParams,
|
||||
) -> lsp_types::TextDocumentIdentifier {
|
||||
let mut inner = self.inner.lock();
|
||||
if let Some(notebook) =
|
||||
inner.notebooks.remove(¶ms.notebook_document.uri)
|
||||
{
|
||||
for script_cell_uri in notebook.script_cells.keys() {
|
||||
inner.script_cell_to_notebook_uri.remove(&script_cell_uri);
|
||||
}
|
||||
}
|
||||
lsp_types::TextDocumentIdentifier {
|
||||
uri: params.notebook_document.uri.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a e.g. `deno-notebook-cell:` specifier to a `file:` specifier.
|
||||
|
|
Loading…
Add table
Reference in a new issue