1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

implement cell formatting

This commit is contained in:
Nayeem Rahman 2024-09-12 03:41:39 +01:00
parent 3f3fb886f3
commit 403130f81f
2 changed files with 157 additions and 71 deletions

View file

@ -104,8 +104,9 @@ use crate::http_util::HttpClientProvider;
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::file_like_to_file_specifier;
use crate::lsp::urls::LspUrlKind;
use crate::lsp::urls::NotebookCellInfo;
use crate::lsp::urls::NotebookScriptCellInfo;
use crate::tools::fmt::format_file;
use crate::tools::fmt::format_parsed_source;
use crate::tools::upgrade::check_for_upgrades_for_lsp;
@ -1445,26 +1446,25 @@ impl Inner {
item
.collect_document_symbols(line_index.clone(), &mut document_symbols);
}
if let Some(notebook_cell_info) = mapped_specifier.notebook_cell_info() {
if let Some(cell_info) = mapped_specifier.notebook_script_cell_info() {
fn map_to_cell_ranges(
mut symbol: DocumentSymbol,
notebook_cell_info: &NotebookCellInfo,
cell_info: &NotebookScriptCellInfo,
) -> 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.range = cell_info.range_server_to_client(symbol.range)?;
symbol.selection_range =
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))
.filter_map(|s| map_to_cell_ranges(s, cell_info))
.collect()
});
Some(symbol)
}
document_symbols = document_symbols
.into_iter()
.filter_map(|s| map_to_cell_ranges(s, notebook_cell_info))
.filter_map(|s| map_to_cell_ranges(s, cell_info))
.collect();
}
Some(DocumentSymbolResponse::Nested(document_symbols))
@ -1479,12 +1479,48 @@ impl Inner {
&self,
params: DocumentFormattingParams,
) -> LspResult<Option<Vec<TextEdit>>> {
let file_referrer = Some(uri_to_url(&params.text_document.uri))
.filter(|s| self.documents.is_valid_file_referrer(s));
let mut specifier = self
.url_map
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
// skip formatting any files ignored by the config file
dbg!(params.text_document.uri.as_str());
let raw_url = uri_to_url(&params.text_document.uri);
let mut specifier;
let language_id;
let content;
let parsed_source;
let line_index;
let is_notebook_cell;
if let Some(notebook_cell) =
self.url_map.notebook_cell(&params.text_document.uri)
{
match file_like_to_file_specifier(&raw_url) {
Some(file_specifier) => {
specifier = file_specifier;
}
None => {
specifier = raw_url.clone();
}
}
language_id = notebook_cell.language_id.parse().ok();
content = notebook_cell.text.clone();
parsed_source = None;
line_index = notebook_cell.line_index.clone();
is_notebook_cell = true;
} else {
let file_referrer =
Some(&raw_url).filter(|s| self.documents.is_valid_file_referrer(s));
specifier = self
.url_map
.uri_to_specifier2(&params.text_document.uri, LspUrlKind::File)
.into_specifier();
let Some(document) =
self.documents.get_or_load(&specifier, file_referrer)
else {
return Ok(None);
};
content = document.content().clone();
language_id = document.maybe_language_id();
parsed_source = document.maybe_parsed_source().cloned();
line_index = document.line_index();
is_notebook_cell = false;
}
if !self
.config
.tree
@ -1494,19 +1530,13 @@ impl Inner {
{
return Ok(None);
}
let document = self
.documents
.get_or_load(&specifier, file_referrer.as_ref());
let Some(document) = document else {
return Ok(None);
};
// Detect vendored paths. Vendor file URLs will normalize to their remote
// counterparts, but for formatting we want to favour the file URL.
// TODO(nayeemrmn): Implement `Document::file_resource_path()` or similar.
if specifier.scheme() != "file"
&& params.text_document.uri.scheme().map(|s| s.as_str()) == Some("file")
{
specifier = uri_to_url(&params.text_document.uri);
specifier = raw_url;
}
let file_path = specifier_to_file_path(&specifier).map_err(|err| {
error!("{:#}", err);
@ -1543,37 +1573,38 @@ impl Inner {
.map(|w| w.has_unstable("fmt-yaml"))
.unwrap_or(false),
};
let document = document.clone();
move || {
let format_result = match document.maybe_parsed_source() {
let format_result = match parsed_source {
Some(Ok(parsed_source)) => {
format_parsed_source(parsed_source, &fmt_options)
format_parsed_source(&parsed_source, &fmt_options)
}
Some(Err(err)) => Err(anyhow!("{:#}", err)),
None => {
// the file path is only used to determine what formatter should
// be used to format the file, so give the filepath an extension
// that matches what the user selected as the language
let file_path = document
.maybe_language_id()
let file_path = language_id
.and_then(|id| id.as_extension())
.map(|ext| file_path.with_extension(ext))
.unwrap_or(file_path);
// it's not a js/ts file, so attempt to format its contents
format_file(
&file_path,
document.content(),
content.as_ref(),
&fmt_options,
&unstable_options,
)
}
};
match format_result {
Ok(Some(new_text)) => Some(text::get_edits(
document.content(),
&new_text,
document.line_index().as_ref(),
)),
Ok(Some(new_text)) => {
let text = if is_notebook_cell {
new_text.trim_end_matches('\n')
} else {
new_text.as_str()
};
Some(text::get_edits(content.as_ref(), text, line_index.as_ref()))
}
Ok(None) => Some(Vec::new()),
Err(err) => {
lsp_warn!("Format error: {:#}", err);

View file

@ -126,15 +126,32 @@ fn from_deno_url(url: &Url) -> Option<Url> {
Url::parse(&string).ok()
}
#[derive(Debug)]
struct NotebookCell {
item: lsp_types::TextDocumentItem,
#[derive(Debug, Clone)]
pub struct NotebookCell {
pub uri: Uri,
pub language_id: String,
pub version: i32,
pub text: Arc<str>,
pub line_index: Arc<LineIndex>,
}
impl NotebookCell {
fn new(item: lsp_types::TextDocumentItem) -> Self {
let line_index = Arc::new(LineIndex::new(&item.text));
Self {
uri: item.uri,
language_id: item.language_id,
version: item.version,
text: item.text.into(),
line_index,
}
}
}
#[derive(Debug)]
struct Notebook {
uri: Uri,
cells: IndexMap<Uri, lsp_types::TextDocumentItem>,
cells: IndexMap<Uri, Arc<NotebookCell>>,
version: i32,
script_language_id: Option<String>,
script_cells: IndexMap<Uri, NotebookScriptCellInfo>,
@ -143,7 +160,7 @@ struct Notebook {
impl Notebook {
fn new(
uri: Uri,
cells: IndexMap<Uri, lsp_types::TextDocumentItem>,
cells: IndexMap<Uri, Arc<NotebookCell>>,
version: i32,
) -> Self {
static SCRIPT_LANGUAGE_IDS: &[&str] = &[
@ -154,26 +171,26 @@ impl Notebook {
"typescriptreact",
"tsx",
];
let script_language_id = cells.values().find_map(|i| {
let script_language_id = cells.values().find_map(|c| {
SCRIPT_LANGUAGE_IDS
.contains(&i.language_id.as_str())
.then(|| i.language_id.clone())
.contains(&c.language_id.as_str())
.then(|| c.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 {
for cell in cells.values() {
if &cell.language_id != language_id {
continue;
}
let line_count =
item.text.chars().filter(|c| *c == '\n').count() as u32;
cell.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);
script_cells.insert(cell.uri.clone(), cell_info);
}
}
Self {
@ -198,36 +215,50 @@ impl Notebook {
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)));
cells.extend(
did_open
.into_iter()
.map(|i| (i.uri.clone(), Arc::new(NotebookCell::new(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)
let Some(cell) =
cells.values_mut().find(|c| c.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 text = cell.text.to_string();
let mut line_index = cell.line_index.clone();
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);
line_index = Arc::new(LineIndex::new(&text));
}
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);
text.replace_range(Range::<usize>::from(range), &change.text);
} else {
content = change.text;
text = change.text;
index_valid = IndexValid::UpTo(0);
}
}
item.text = content;
let line_index = if index_valid == IndexValid::All {
line_index
} else {
Arc::new(LineIndex::new(text.as_str()))
};
*cell = Arc::new(NotebookCell {
uri: cell.uri.clone(),
language_id: cell.language_id.clone(),
version: cell.version,
text: text.into(),
line_index,
});
}
}
}
@ -238,8 +269,7 @@ impl Notebook {
let text = self
.script_cells
.iter()
.map(|(u, _)| [self.cells.get(u).unwrap().text.as_str(), "\n"])
.flatten()
.flat_map(|(u, _)| [self.cells.get(u).unwrap().text.as_ref(), "\n"])
.collect::<Vec<_>>()
.join("");
dbg!(self.uri.as_str(), &text);
@ -313,7 +343,7 @@ pub fn uri_to_url(uri: &Uri) -> Url {
Url::parse(uri.as_str()).unwrap()
}
#[derive(Debug, Copy)]
#[derive(Debug, Copy, Clone)]
pub struct NotebookScriptCellInfo {
pub line_offset: u32,
pub line_count: u32,
@ -352,7 +382,7 @@ impl MappedSpecifier {
}
}
pub fn notebook_cell_info(&self) -> Option<&NotebookScriptCellInfo> {
pub fn notebook_script_cell_info(&self) -> Option<&NotebookScriptCellInfo> {
match self {
Self::NotebookScript(_, i) => Some(i),
_ => None,
@ -470,7 +500,7 @@ impl LspUrlMap {
kind: LspUrlKind,
) -> MappedSpecifier {
let notebook_script = (|| {
let mut inner = self.inner.lock();
let 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)?;
@ -486,13 +516,35 @@ impl LspUrlMap {
pub fn specifier_to_uri2(
&self,
specifier: &ModuleSpecifier,
_line: Option<u32>,
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))
let uri = self.specifier_to_uri(specifier, file_referrer)?;
let cell_entry = (|| {
let line = line?;
let inner = self.inner.lock();
let notebook = inner.notebooks.get(&uri)?;
let (uri, cell_info) = notebook
.script_cells
.iter()
.rev()
.find(|(_, i)| i.line_offset <= line)?;
if cell_info.line_offset + cell_info.line_count <= line {
return None;
}
Some((uri.clone(), *cell_info))
})();
if let Some((uri, cell_info)) = cell_entry {
return Ok((uri, Some(cell_info)));
}
Ok((uri, None))
}
pub fn notebook_cell(&self, uri: &Uri) -> Option<Arc<NotebookCell>> {
let inner = self.inner.lock();
let notebook_uri = inner.script_cell_to_notebook_uri.get(uri)?;
let notebook = inner.notebooks.get(notebook_uri)?;
notebook.cells.get(uri).cloned()
}
pub fn notebook_did_open(
@ -505,7 +557,7 @@ impl LspUrlMap {
params
.cell_text_documents
.into_iter()
.map(|i| (i.uri.clone(), i))
.map(|i| (i.uri.clone(), Arc::new(NotebookCell::new(i))))
.collect(),
params.notebook_document.version,
);
@ -534,11 +586,14 @@ impl LspUrlMap {
),
));
};
let structure_changed =
params.change.cells.is_some_and(|c| c.structure.is_some());
let structure_changed = params
.change
.cells
.as_ref()
.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);
inner.script_cell_to_notebook_uri.remove(script_cell_uri);
}
}
let notebook = notebook.with_change(params);
@ -563,7 +618,7 @@ impl LspUrlMap {
inner.notebooks.remove(&params.notebook_document.uri)
{
for script_cell_uri in notebook.script_cells.keys() {
inner.script_cell_to_notebook_uri.remove(&script_cell_uri);
inner.script_cell_to_notebook_uri.remove(script_cell_uri);
}
}
lsp_types::TextDocumentIdentifier {
@ -580,7 +635,7 @@ impl LspUrlMap {
/// ),
/// Some(Url::parse("file:///path/to/file.ipynb.ts?scheme=deno-notebook-cell#abc").unwrap()),
/// );
fn file_like_to_file_specifier(specifier: &Url) -> Option<Url> {
pub fn file_like_to_file_specifier(specifier: &Url) -> Option<Url> {
if matches!(specifier.scheme(), "untitled" | "deno-notebook-cell") {
if let Ok(mut s) = ModuleSpecifier::parse(&format!(
"file://{}",