diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs index 601373145305b0..6e8388eca07ce9 100644 --- a/cli/lsp/completions.rs +++ b/cli/lsp/completions.rs @@ -7,6 +7,9 @@ use super::tsc; use crate::fs_util::is_supported_ext; use crate::fs_util::specifier_to_file_path; +use deno_ast::swc::common::BytePos; +use deno_ast::LineAndColumnIndex; +use deno_ast::SourceTextInfo; use deno_core::normalize_path; use deno_core::resolve_path; use deno_core::resolve_url; @@ -91,17 +94,32 @@ async fn check_auto_config_registry( } } -/// Ranges from the graph for specifiers include the leading and trailing quote, +/// Ranges from the graph for specifiers include the leading and maybe trailing quote, /// which we want to ignore when replacing text. -fn to_narrow_lsp_range(range: &deno_graph::Range) -> lsp::Range { +fn to_narrow_lsp_range( + text_info: &SourceTextInfo, + range: &deno_graph::Range, +) -> lsp::Range { + let end_byte_index = text_info.byte_index(LineAndColumnIndex { + line_index: range.end.line, + column_index: range.end.character, + }); + let text_bytes = text_info.text_str().as_bytes(); + let has_trailing_quote = + matches!(text_bytes[end_byte_index.0 as usize - 1], (b'"' | b'\'')); lsp::Range { start: lsp::Position { line: range.start.line as u32, + // skip the leading quote character: (range.start.character + 1) as u32, }, end: lsp::Position { line: range.end.line as u32, - character: (range.end.character - 1) as u32, + character: if has_trailing_quote { + range.end.character - 1 // do not include it + } else { + range.end.character + } as u32, }, } } @@ -117,7 +135,7 @@ pub(crate) async fn get_import_completions( ) -> Option { let document = state_snapshot.documents.get(specifier)?; let (text, _, range) = document.get_maybe_dependency(position)?; - let range = to_narrow_lsp_range(&range); + let range = to_narrow_lsp_range(&document.text_info(), &range); // completions for local relative modules if text.starts_with("./") || text.starts_with("../") { Some(lsp::CompletionResponse::List(lsp::CompletionList { @@ -430,6 +448,7 @@ mod tests { use crate::lsp::documents::Documents; use crate::lsp::documents::LanguageId; use deno_core::resolve_url; + use deno_graph::Range; use std::collections::HashMap; use std::path::Path; use std::sync::Arc; @@ -672,4 +691,52 @@ mod tests { }] ); } + + #[test] + fn test_to_narrow_lsp_range() { + let text_info = SourceTextInfo::from_string(r#""te""#.to_string()); + let range = to_narrow_lsp_range( + &text_info, + &Range { + specifier: ModuleSpecifier::parse("https://deno.land").unwrap(), + start: deno_graph::Position { + line: 0, + character: 0, + }, + end: deno_graph::Position { + line: 0, + character: text_info.text_str().chars().count(), + }, + }, + ); + assert_eq!(range.start.character, 1); + assert_eq!( + range.end.character, + (text_info.text_str().chars().count() - 1) as u32 + ); + } + + #[test] + fn test_to_narrow_lsp_range_no_trailing_quote() { + let text_info = SourceTextInfo::from_string(r#""te"#.to_string()); + let range = to_narrow_lsp_range( + &text_info, + &Range { + specifier: ModuleSpecifier::parse("https://deno.land").unwrap(), + start: deno_graph::Position { + line: 0, + character: 0, + }, + end: deno_graph::Position { + line: 0, + character: text_info.text_str().chars().count(), + }, + }, + ); + assert_eq!(range.start.character, 1); + assert_eq!( + range.end.character, + text_info.text_str().chars().count() as u32 + ); + } }