diff options
Diffstat (limited to 'cli/lsp')
| -rw-r--r-- | cli/lsp/documents.rs | 7 | ||||
| -rw-r--r-- | cli/lsp/text.rs | 261 | ||||
| -rw-r--r-- | cli/lsp/tsc.rs | 236 |
3 files changed, 51 insertions, 453 deletions
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 0a383e781..4fb8c428e 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -200,6 +200,13 @@ impl AssetOrDocument { } } + pub fn media_type(&self) -> MediaType { + match self { + AssetOrDocument::Asset(_) => MediaType::TypeScript, // assets are always TypeScript + AssetOrDocument::Document(d) => d.media_type(), + } + } + pub fn get_maybe_dependency( &self, position: &lsp::Position, diff --git a/cli/lsp/text.rs b/cli/lsp/text.rs index 64721ebc7..33ac2048f 100644 --- a/cli/lsp/text.rs +++ b/cli/lsp/text.rs @@ -2,13 +2,9 @@ use deno_core::error::custom_error; use deno_core::error::AnyError; -use deno_core::serde_json::json; -use deno_core::serde_json::Value; use dissimilar::diff; use dissimilar::Chunk; use std::collections::HashMap; -use std::ops::Bound; -use std::ops::RangeBounds; use text_size::TextRange; use text_size::TextSize; use tower_lsp::jsonrpc; @@ -261,112 +257,6 @@ pub fn get_edits(a: &str, b: &str, line_index: &LineIndex) -> Vec<TextEdit> { text_edits } -/// Convert a difference between two strings into a change range used by the -/// TypeScript Language Service. -pub fn get_range_change(a: &str, b: &str) -> Value { - if a == b { - return json!(null); - } - let chunks = diff(a, b); - let mut iter = chunks.iter().peekable(); - let mut started = false; - let mut start = 0; - let mut end = 0; - let mut new_length = 0; - let mut equal = 0; - let mut a_pos = 0; - loop { - let diff = iter.next(); - match diff { - None => break, - Some(Chunk::Equal(e)) => { - a_pos += e.encode_utf16().count(); - equal += e.encode_utf16().count(); - } - Some(Chunk::Delete(d)) => { - if !started { - start = a_pos; - started = true; - equal = 0; - } - a_pos += d.encode_utf16().count(); - if started { - end = a_pos; - new_length += equal; - equal = 0; - } - } - Some(Chunk::Insert(i)) => { - if !started { - start = a_pos; - end = a_pos; - started = true; - equal = 0; - } else { - end += equal; - } - new_length += i.encode_utf16().count() + equal; - equal = 0; - } - } - } - - json!({ - "span": { - "start": start, - "length": end - start, - }, - "newLength": new_length, - }) -} - -/// Provide a slice of a string based on a character range. -pub fn slice(s: &str, range: impl RangeBounds<usize>) -> &str { - let start = match range.start_bound() { - Bound::Included(bound) | Bound::Excluded(bound) => *bound, - Bound::Unbounded => 0, - }; - let len = match range.end_bound() { - Bound::Included(bound) => *bound + 1, - Bound::Excluded(bound) => *bound, - Bound::Unbounded => s.encode_utf16().count(), - } - start; - substring(s, start, start + len) -} - -/// Provide a substring based on the start and end character index positions. -pub fn substring(s: &str, start: usize, end: usize) -> &str { - let len = end - start; - let mut char_pos = 0; - let mut byte_start = 0; - let mut it = s.chars(); - loop { - if char_pos == start { - break; - } - if let Some(c) = it.next() { - char_pos += c.len_utf16(); - byte_start += c.len_utf8(); - } else { - break; - } - } - char_pos = 0; - let mut byte_end = byte_start; - loop { - if char_pos == len { - break; - } - if let Some(c) = it.next() { - char_pos += c.len_utf16(); - byte_end += c.len_utf8(); - } else { - break; - } - } - &s[byte_start..byte_end] -} - #[cfg(test)] mod tests { use super::*; @@ -637,155 +527,4 @@ const C: char = \"γ‘ γ‘\"; ] ) } - - #[test] - fn test_get_range_change() { - let a = "abcdefg"; - let b = "abcdefg"; - let actual = get_range_change(a, b); - assert_eq!(actual, json!(null)); - - let a = "abcdefg"; - let b = "abedcfg"; - let actual = get_range_change(a, b); - assert_eq!( - actual, - json!({ - "span": { - "start": 2, - "length": 3, - }, - "newLength": 3 - }) - ); - - let a = "abfg"; - let b = "abcdefg"; - let actual = get_range_change(a, b); - assert_eq!( - actual, - json!({ - "span": { - "start": 2, - "length": 0, - }, - "newLength": 3 - }) - ); - - let a = "abcdefg"; - let b = "abfg"; - let actual = get_range_change(a, b); - assert_eq!( - actual, - json!({ - "span": { - "start": 2, - "length": 3, - }, - "newLength": 0 - }) - ); - - let a = "abcdefg"; - let b = "abfghij"; - let actual = get_range_change(a, b); - assert_eq!( - actual, - json!({ - "span": { - "start": 2, - "length": 5, - }, - "newLength": 5 - }) - ); - - let a = "abcdefghijk"; - let b = "axcxexfxixk"; - let actual = get_range_change(a, b); - assert_eq!( - actual, - json!({ - "span": { - "start": 1, - "length": 9, - }, - "newLength": 9 - }) - ); - - let a = "abcde"; - let b = "ab(c)de"; - let actual = get_range_change(a, b); - assert_eq!( - actual, - json!({ - "span" : { - "start": 2, - "length": 1, - }, - "newLength": 3 - }) - ); - - let a = "hello π¦!"; - let b = "hello deno!"; - let actual = get_range_change(a, b); - assert_eq!( - actual, - json!({ - "span": { - "start": 6, - "length": 2, - }, - "newLength": 4 - }) - ); - - let a = "hello deno!"; - let b = "hello denoπ¦!"; - let actual = get_range_change(a, b); - assert_eq!( - actual, - json!({ - "span": { - "start": 10, - "length": 0, - }, - "newLength": 2 - }) - ); - - // TODO(@kitsonk): https://github.com/dtolnay/dissimilar/issues/5 - // let a = r#" π¦πΊπΈπ "#; - // let b = r#" πΊπΈπ "#; - // let actual = get_range_change(a, b); - // assert_eq!( - // actual, - // json!({ - // "span": { - // "start": 1, - // "length": 2, - // }, - // "newLength": 0 - // }) - // ); - } - - #[test] - fn test_substring() { - assert_eq!(substring("Deno", 1, 3), "en"); - assert_eq!(substring("yΜyΜ", 2, 4), "yΜ"); - assert_eq!(substring("π¦π¦", 2, 4), "π¦"); - } - - #[test] - fn test_slice() { - assert_eq!(slice("Deno", 1..3), "en"); - assert_eq!(slice("Deno", 1..=3), "eno"); - assert_eq!(slice("Deno Land", 1..), "eno Land"); - assert_eq!(slice("Deno", ..3), "Den"); - assert_eq!(slice("Hello π¦", 6..8), "π¦"); - } } diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 27d32b2db..1448c8c96 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -13,7 +13,6 @@ use super::refactor::EXTRACT_INTERFACE; use super::refactor::EXTRACT_TYPE; use super::semantic_tokens; use super::semantic_tokens::SemanticTokensBuilder; -use super::text; use super::text::LineIndex; use super::urls::LspUrlMap; use super::urls::INVALID_SPECIFIER; @@ -47,7 +46,6 @@ use log::warn; use once_cell::sync::Lazy; use regex::Captures; use regex::Regex; -use std::borrow::Cow; use std::cmp; use std::collections::HashMap; use std::collections::HashSet; @@ -157,7 +155,6 @@ impl TsServer { struct AssetDocumentInner { specifier: ModuleSpecifier, text: Arc<String>, - length: usize, line_index: Arc<LineIndex>, maybe_navigation_tree: Option<Arc<NavigationTree>>, } @@ -173,7 +170,6 @@ impl AssetDocument { Self(Arc::new(AssetDocumentInner { specifier, text: Arc::new(text.to_string()), - length: text.encode_utf16().count(), line_index: Arc::new(LineIndex::new(text)), maybe_navigation_tree: None, })) @@ -197,14 +193,6 @@ impl AssetDocument { self.0.text.clone() } - pub fn text_str(&self) -> &str { - self.0.text.as_str() - } - - pub fn length(&self) -> usize { - self.0.length - } - pub fn line_index(&self) -> Arc<LineIndex> { self.0.line_index.clone() } @@ -2374,17 +2362,16 @@ struct Response { data: Value, } -struct State<'a> { +struct State { last_id: usize, performance: Arc<Performance>, response: Option<Response>, state_snapshot: Arc<StateSnapshot>, - snapshots: HashMap<(ModuleSpecifier, Cow<'a, str>), String>, specifiers: HashMap<String, String>, token: CancellationToken, } -impl<'a> State<'a> { +impl State { fn new( state_snapshot: Arc<StateSnapshot>, performance: Arc<Performance>, @@ -2394,7 +2381,6 @@ impl<'a> State<'a> { performance, response: None, state_snapshot, - snapshots: HashMap::default(), specifiers: HashMap::default(), token: Default::default(), } @@ -2426,31 +2412,37 @@ impl<'a> State<'a> { } ModuleSpecifier::parse(&specifier_str).map_err(|err| err.into()) } -} -/// If a snapshot is missing from the state cache, add it. -fn cache_snapshot( - state: &mut State, - specifier: &ModuleSpecifier, - version: String, -) -> Result<(), AnyError> { - if !state - .snapshots - .contains_key(&(specifier.clone(), version.clone().into())) - { - let content = state - .state_snapshot - .documents - .get(specifier) - .ok_or_else(|| { - anyhow!("Specifier unexpectedly doesn't exist: {}", specifier) - })? - .content(); - state - .snapshots - .insert((specifier.clone(), version.into()), content.to_string()); + fn get_asset_or_document( + &self, + specifier: &ModuleSpecifier, + ) -> Option<AssetOrDocument> { + let snapshot = &self.state_snapshot; + if specifier.scheme() == "asset" { + snapshot.assets.get(specifier).map(AssetOrDocument::Asset) + } else { + snapshot + .documents + .get(specifier) + .map(AssetOrDocument::Document) + } + } + + fn script_version(&self, specifier: &ModuleSpecifier) -> Option<String> { + if specifier.scheme() == "asset" { + if self.state_snapshot.assets.contains_key(specifier) { + Some("1".to_string()) + } else { + None + } + } else { + self + .state_snapshot + .documents + .get(specifier) + .map(|d| d.script_version()) + } } - Ok(()) } fn normalize_specifier<S: AsRef<str>>( @@ -2462,28 +2454,6 @@ fn normalize_specifier<S: AsRef<str>>( #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -struct SourceSnapshotArgs { - specifier: String, - version: String, -} - -/// The language service is dropping a reference to a source file snapshot, and -/// we can drop our version of that document. -#[op] -fn op_dispose( - state: &mut OpState, - args: SourceSnapshotArgs, -) -> Result<bool, AnyError> { - let state = state.borrow_mut::<State>(); - let mark = state.performance.mark("op_dispose", Some(&args)); - let specifier = state.normalize_specifier(&args.specifier)?; - state.snapshots.remove(&(specifier, args.version.into())); - state.performance.measure(mark); - Ok(true) -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] struct SpecifierArgs { specifier: String, } @@ -2506,116 +2476,6 @@ fn op_exists(state: &mut OpState, args: SpecifierArgs) -> bool { state.state_snapshot.documents.exists(&specifier) } -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -struct GetChangeRangeArgs { - specifier: String, - old_length: u32, - old_version: String, - version: String, -} - -/// The language service wants to compare an old snapshot with a new snapshot to -/// determine what source has changed. -#[op] -fn op_get_change_range( - state: &mut OpState, - args: GetChangeRangeArgs, -) -> Result<Value, AnyError> { - let state = state.borrow_mut::<State>(); - let mark = state.performance.mark("op_get_change_range", Some(&args)); - let specifier = state.normalize_specifier(&args.specifier)?; - cache_snapshot(state, &specifier, args.version.clone())?; - let r = if let Some(current) = state - .snapshots - .get(&(specifier.clone(), args.version.clone().into())) - { - if let Some(prev) = state - .snapshots - .get(&(specifier, args.old_version.clone().into())) - { - Ok(text::get_range_change(prev, current)) - } else { - let new_length = current.encode_utf16().count(); - // when a local file is opened up in the editor, the compiler might - // already have a snapshot of it in memory, and will request it, but we - // now are working off in memory versions of the document, and so need - // to tell tsc to reset the whole document - Ok(json!({ - "span": { - "start": 0, - "length": args.old_length, - }, - "newLength": new_length, - })) - } - } else { - Err(custom_error( - "MissingSnapshot", - format!( - "The current snapshot version is missing.\n Args: \"{:?}\"", - args - ), - )) - }; - - state.performance.measure(mark); - r -} - -#[op] -fn op_get_length( - state: &mut OpState, - args: SourceSnapshotArgs, -) -> Result<usize, AnyError> { - let state = state.borrow_mut::<State>(); - let mark = state.performance.mark("op_get_length", Some(&args)); - let specifier = state.normalize_specifier(args.specifier)?; - let r = if let Some(asset) = state.state_snapshot.assets.get(&specifier) { - Ok(asset.length()) - } else { - cache_snapshot(state, &specifier, args.version.clone())?; - let content = state - .snapshots - .get(&(specifier, args.version.into())) - .unwrap(); - Ok(content.encode_utf16().count()) - }; - state.performance.measure(mark); - r -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -struct GetTextArgs { - specifier: String, - version: String, - start: usize, - end: usize, -} - -#[op] -fn op_get_text( - state: &mut OpState, - args: GetTextArgs, -) -> Result<String, AnyError> { - let state = state.borrow_mut::<State>(); - let mark = state.performance.mark("op_get_text", Some(&args)); - let specifier = state.normalize_specifier(args.specifier)?; - let maybe_asset = state.state_snapshot.assets.get(&specifier); - let content = if let Some(content) = &maybe_asset { - content.text_str() - } else { - cache_snapshot(state, &specifier, args.version.clone())?; - state - .snapshots - .get(&(specifier, args.version.into())) - .unwrap() - }; - state.performance.measure(mark); - Ok(text::slice(content, args.start..args.end).to_string()) -} - #[op] fn op_is_cancelled(state: &mut OpState) -> bool { let state = state.borrow_mut::<State>(); @@ -2626,13 +2486,22 @@ fn op_is_cancelled(state: &mut OpState) -> bool { fn op_load( state: &mut OpState, args: SpecifierArgs, -) -> Result<Option<String>, AnyError> { +) -> Result<Value, AnyError> { let state = state.borrow_mut::<State>(); let mark = state.performance.mark("op_load", Some(&args)); let specifier = state.normalize_specifier(args.specifier)?; - let document = state.state_snapshot.documents.get(&specifier); + let asset_or_document = state.get_asset_or_document(&specifier); state.performance.measure(mark); - Ok(document.map(|d| d.content().to_string())) + Ok(match asset_or_document { + Some(doc) => { + json!({ + "data": doc.text(), + "scriptKind": crate::tsc::as_ts_script_kind(&doc.media_type()), + "version": state.script_version(&specifier), + }) + } + None => Value::Null, + }) } #[op] @@ -2705,20 +2574,7 @@ fn op_script_version( // this op is very "noisy" and measuring its performance is not useful, so we // don't measure it uniquely anymore. let specifier = state.normalize_specifier(args.specifier)?; - if specifier.scheme() == "asset" { - if state.state_snapshot.assets.contains_key(&specifier) { - Ok(Some("1".to_string())) - } else { - Ok(None) - } - } else { - let script_version = state - .state_snapshot - .documents - .get(&specifier) - .map(|d| d.script_version()); - Ok(script_version) - } + Ok(state.script_version(&specifier)) } /// Create and setup a JsRuntime based on a snapshot. It is expected that the @@ -2735,11 +2591,7 @@ fn js_runtime(performance: Arc<Performance>) -> JsRuntime { fn init_extension(performance: Arc<Performance>) -> Extension { Extension::builder() .ops(vec