mirror of
https://github.com/denoland/deno
synced 2024-09-19 08:02:31 +00:00
feat(lsp): Implement textDocument/semanticTokens/full (#10233)
Co-authored-by: Kitson Kelly <me@kitsonkelly.com>
This commit is contained in:
parent
b6203cb465
commit
6d404ec54b
|
@ -18,6 +18,9 @@ use lspower::lsp::ImplementationProviderCapability;
|
||||||
use lspower::lsp::OneOf;
|
use lspower::lsp::OneOf;
|
||||||
use lspower::lsp::SaveOptions;
|
use lspower::lsp::SaveOptions;
|
||||||
use lspower::lsp::SelectionRangeProviderCapability;
|
use lspower::lsp::SelectionRangeProviderCapability;
|
||||||
|
use lspower::lsp::SemanticTokensFullOptions;
|
||||||
|
use lspower::lsp::SemanticTokensOptions;
|
||||||
|
use lspower::lsp::SemanticTokensServerCapabilities;
|
||||||
use lspower::lsp::ServerCapabilities;
|
use lspower::lsp::ServerCapabilities;
|
||||||
use lspower::lsp::SignatureHelpOptions;
|
use lspower::lsp::SignatureHelpOptions;
|
||||||
use lspower::lsp::TextDocumentSyncCapability;
|
use lspower::lsp::TextDocumentSyncCapability;
|
||||||
|
@ -25,6 +28,8 @@ use lspower::lsp::TextDocumentSyncKind;
|
||||||
use lspower::lsp::TextDocumentSyncOptions;
|
use lspower::lsp::TextDocumentSyncOptions;
|
||||||
use lspower::lsp::WorkDoneProgressOptions;
|
use lspower::lsp::WorkDoneProgressOptions;
|
||||||
|
|
||||||
|
use super::semantic_tokens::get_legend;
|
||||||
|
|
||||||
fn code_action_capabilities(
|
fn code_action_capabilities(
|
||||||
client_capabilities: &ClientCapabilities,
|
client_capabilities: &ClientCapabilities,
|
||||||
) -> CodeActionProviderCapability {
|
) -> CodeActionProviderCapability {
|
||||||
|
@ -116,7 +121,16 @@ pub fn server_capabilities(
|
||||||
color_provider: None,
|
color_provider: None,
|
||||||
execute_command_provider: None,
|
execute_command_provider: None,
|
||||||
call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
|
call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
|
||||||
semantic_tokens_provider: None,
|
semantic_tokens_provider: Some(
|
||||||
|
SemanticTokensServerCapabilities::SemanticTokensOptions(
|
||||||
|
SemanticTokensOptions {
|
||||||
|
legend: get_legend(),
|
||||||
|
range: Some(true),
|
||||||
|
full: Some(SemanticTokensFullOptions::Bool(true)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
workspace: None,
|
workspace: None,
|
||||||
experimental: None,
|
experimental: None,
|
||||||
linked_editing_range_provider: None,
|
linked_editing_range_provider: None,
|
||||||
|
|
|
@ -1961,6 +1961,99 @@ impl Inner {
|
||||||
Ok(Some(selection_ranges))
|
Ok(Some(selection_ranges))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn semantic_tokens_full(
|
||||||
|
&self,
|
||||||
|
params: SemanticTokensParams,
|
||||||
|
) -> LspResult<Option<SemanticTokensResult>> {
|
||||||
|
if !self.enabled() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let mark = self.performance.mark("semantic_tokens_full");
|
||||||
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||||
|
|
||||||
|
let line_index =
|
||||||
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
||||||
|
line_index
|
||||||
|
} else {
|
||||||
|
return Err(LspError::invalid_params(format!(
|
||||||
|
"An unexpected specifier ({}) was provided.",
|
||||||
|
specifier
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let req = tsc::RequestMethod::GetEncodedSemanticClassifications((
|
||||||
|
specifier.clone(),
|
||||||
|
tsc::TextSpan {
|
||||||
|
start: 0,
|
||||||
|
length: line_index.text_content_length_utf16().into(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
let semantic_classification: tsc::Classifications = self
|
||||||
|
.ts_server
|
||||||
|
.request(self.snapshot(), req)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
error!("Failed to request to tsserver {}", err);
|
||||||
|
LspError::invalid_request()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let semantic_tokens: SemanticTokens =
|
||||||
|
semantic_classification.to_semantic_tokens(&line_index);
|
||||||
|
let response = if !semantic_tokens.data.is_empty() {
|
||||||
|
Some(SemanticTokensResult::Tokens(semantic_tokens))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.performance.measure(mark);
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn semantic_tokens_range(
|
||||||
|
&self,
|
||||||
|
params: SemanticTokensRangeParams,
|
||||||
|
) -> LspResult<Option<SemanticTokensRangeResult>> {
|
||||||
|
if !self.enabled() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let mark = self.performance.mark("semantic_tokens_range");
|
||||||
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||||
|
|
||||||
|
let line_index =
|
||||||
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
||||||
|
line_index
|
||||||
|
} else {
|
||||||
|
return Err(LspError::invalid_params(format!(
|
||||||
|
"An unexpected specifier ({}) was provided.",
|
||||||
|
specifier
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let start = line_index.offset_tsc(params.range.start)?;
|
||||||
|
let length = line_index.offset_tsc(params.range.end)? - start;
|
||||||
|
let req = tsc::RequestMethod::GetEncodedSemanticClassifications((
|
||||||
|
specifier.clone(),
|
||||||
|
tsc::TextSpan { start, length },
|
||||||
|
));
|
||||||
|
let semantic_classification: tsc::Classifications = self
|
||||||
|
.ts_server
|
||||||
|
.request(self.snapshot(), req)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
error!("Failed to request to tsserver {}", err);
|
||||||
|
LspError::invalid_request()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let semantic_tokens: SemanticTokens =
|
||||||
|
semantic_classification.to_semantic_tokens(&line_index);
|
||||||
|
let response = if !semantic_tokens.data.is_empty() {
|
||||||
|
Some(SemanticTokensRangeResult::Tokens(semantic_tokens))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.performance.measure(mark);
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
async fn signature_help(
|
async fn signature_help(
|
||||||
&self,
|
&self,
|
||||||
params: SignatureHelpParams,
|
params: SignatureHelpParams,
|
||||||
|
@ -2200,6 +2293,20 @@ impl lspower::LanguageServer for LanguageServer {
|
||||||
self.0.lock().await.selection_range(params).await
|
self.0.lock().await.selection_range(params).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn semantic_tokens_full(
|
||||||
|
&self,
|
||||||
|
params: SemanticTokensParams,
|
||||||
|
) -> LspResult<Option<SemanticTokensResult>> {
|
||||||
|
self.0.lock().await.semantic_tokens_full(params).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn semantic_tokens_range(
|
||||||
|
&self,
|
||||||
|
params: SemanticTokensRangeParams,
|
||||||
|
) -> LspResult<Option<SemanticTokensRangeResult>> {
|
||||||
|
self.0.lock().await.semantic_tokens_range(params).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn signature_help(
|
async fn signature_help(
|
||||||
&self,
|
&self,
|
||||||
params: SignatureHelpParams,
|
params: SignatureHelpParams,
|
||||||
|
@ -3539,6 +3646,43 @@ mod tests {
|
||||||
harness.run().await;
|
harness.run().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
async fn test_semantic_tokens() {
|
||||||
|
let mut harness = LspTestHarness::new(vec![
|
||||||
|
(LspFixture::Path("initialize_request.json"), LspResponse::RequestAny),
|
||||||
|
(LspFixture::Path("initialized_notification.json"), LspResponse::None),
|
||||||
|
(
|
||||||
|
LspFixture::Path("semantic_tokens_did_open_notification.json"),
|
||||||
|
LspResponse::None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
LspFixture::Path("semantic_tokens_full_request.json"),
|
||||||
|
LspResponse::Request(
|
||||||
|
2,
|
||||||
|
json!({
|
||||||
|
"data": [0, 5, 6, 1, 1, 0, 9, 6, 8, 9, 0, 8, 6, 8, 9, 2, 15 ,3, 10 ,5, 0, 4, 1, 6, 1, 0, 12 ,7, 2, 16 ,1, 8, 1, 7, 41 ,0, 4, 1, 6, 0, 0, 2, 5, 11 ,16 ,1, 9, 1, 7, 40 ,3, 10 ,4, 2, 1, 1, 11 ,1, 9, 9, 1, 2, 3, 11 ,1, 3, 6, 3, 0, 1, 0, 15 ,4, 2, 0, 1, 30 ,1, 6, 9, 1, 2, 3, 11 ,1, 1, 9, 9, 9, 3, 0, 16 ,3, 0, 0, 1, 17 ,12 ,11 ,3, 0, 24 ,3, 0, 0, 0, 4, 9, 9, 2]
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
LspFixture::Path("semantic_tokens_range_request.json"),
|
||||||
|
LspResponse::Request(
|
||||||
|
4,
|
||||||
|
json!({
|
||||||
|
"data": [0, 5, 6, 1, 1, 0, 9, 6, 8, 9, 0, 8, 6, 8, 9, 2, 15 ,3, 10 ,5, 0, 4, 1, 6, 1, 0, 12 ,7, 2, 16 ,1, 8, 1, 7, 41 ,0, 4, 1, 6, 0, 0, 2, 5, 11 ,16 ,1, 9, 1, 7, 40]
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
LspFixture::Path("shutdown_request.json"),
|
||||||
|
LspResponse::Request(3, json!(null)),
|
||||||
|
),
|
||||||
|
(LspFixture::Path("exit_notification.json"), LspResponse::None),
|
||||||
|
]);
|
||||||
|
harness.run().await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_code_lens_request() {
|
async fn test_code_lens_request() {
|
||||||
let mut harness = LspTestHarness::new(vec![
|
let mut harness = LspTestHarness::new(vec![
|
||||||
|
|
|
@ -13,6 +13,7 @@ pub(crate) mod language_server;
|
||||||
mod path_to_regex;
|
mod path_to_regex;
|
||||||
mod performance;
|
mod performance;
|
||||||
mod registries;
|
mod registries;
|
||||||
|
mod semantic_tokens;
|
||||||
mod sources;
|
mod sources;
|
||||||
mod text;
|
mod text;
|
||||||
mod tsc;
|
mod tsc;
|
||||||
|
|
356
cli/lsp/semantic_tokens.rs
Normal file
356
cli/lsp/semantic_tokens.rs
Normal file
|
@ -0,0 +1,356 @@
|
||||||
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
// The logic of this module is heavily influenced by
|
||||||
|
// https://github.com/microsoft/vscode/blob/main/extensions/typescript-language-features/src/languageFeatures/semanticTokens.ts
|
||||||
|
// and https://github.com/microsoft/vscode/blob/main/src/vs/workbench/api/common/extHostTypes.ts
|
||||||
|
// for the SemanticTokensBuilder implementation.
|
||||||
|
|
||||||
|
use lspower::lsp::SemanticToken;
|
||||||
|
use lspower::lsp::SemanticTokenModifier;
|
||||||
|
use lspower::lsp::SemanticTokenType;
|
||||||
|
use lspower::lsp::SemanticTokens;
|
||||||
|
use lspower::lsp::SemanticTokensLegend;
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
|
enum TokenType {
|
||||||
|
Class = 0,
|
||||||
|
Enum = 1,
|
||||||
|
Interface = 2,
|
||||||
|
Namespace = 3,
|
||||||
|
TypeParameter = 4,
|
||||||
|
Type = 5,
|
||||||
|
Parameter = 6,
|
||||||
|
Variable = 7,
|
||||||
|
EnumMember = 8,
|
||||||
|
Property = 9,
|
||||||
|
Function = 10,
|
||||||
|
Method = 11,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Index<TokenType> for Vec<T> {
|
||||||
|
type Output = T;
|
||||||
|
fn index(&self, idx: TokenType) -> &T {
|
||||||
|
&self[idx as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IndexMut<TokenType> for Vec<T> {
|
||||||
|
fn index_mut(&mut self, idx: TokenType) -> &mut T {
|
||||||
|
&mut self[idx as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TokenModifier {
|
||||||
|
Declaration = 0,
|
||||||
|
Static = 1,
|
||||||
|
Async = 2,
|
||||||
|
Readonly = 3,
|
||||||
|
DefaultLibrary = 4,
|
||||||
|
Local = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Index<TokenModifier> for Vec<T> {
|
||||||
|
type Output = T;
|
||||||
|
fn index(&self, idx: TokenModifier) -> &T {
|
||||||
|
&self[idx as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IndexMut<TokenModifier> for Vec<T> {
|
||||||
|
fn index_mut(&mut self, idx: TokenModifier) -> &mut T {
|
||||||
|
&mut self[idx as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_legend() -> SemanticTokensLegend {
|
||||||
|
let mut token_types = vec![SemanticTokenType::from(""); 12];
|
||||||
|
token_types[TokenType::Class] = "class".into();
|
||||||
|
token_types[TokenType::Enum] = "enum".into();
|
||||||
|
token_types[TokenType::Interface] = "interface".into();
|
||||||
|
token_types[TokenType::Namespace] = "namespace".into();
|
||||||
|
token_types[TokenType::TypeParameter] = "typeParameter".into();
|
||||||
|
token_types[TokenType::Type] = "type".into();
|
||||||
|
token_types[TokenType::Parameter] = "parameter".into();
|
||||||
|
token_types[TokenType::Variable] = "variable".into();
|
||||||
|
token_types[TokenType::EnumMember] = "enumMember".into();
|
||||||
|
token_types[TokenType::Property] = "property".into();
|
||||||
|
token_types[TokenType::Function] = "function".into();
|
||||||
|
token_types[TokenType::Method] = "method".into();
|
||||||
|
|
||||||
|
let mut token_modifiers = vec![SemanticTokenModifier::from(""); 6];
|
||||||
|
token_modifiers[TokenModifier::Declaration] = "declaration".into();
|
||||||
|
token_modifiers[TokenModifier::Static] = "static".into();
|
||||||
|
token_modifiers[TokenModifier::Async] = "async".into();
|
||||||
|
token_modifiers[TokenModifier::Readonly] = "readonly".into();
|
||||||
|
token_modifiers[TokenModifier::DefaultLibrary] = "defaultLibrary".into();
|
||||||
|
token_modifiers[TokenModifier::Local] = "local".into();
|
||||||
|
|
||||||
|
SemanticTokensLegend {
|
||||||
|
token_types,
|
||||||
|
token_modifiers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum TsTokenEncodingConsts {
|
||||||
|
TypeOffset = 8,
|
||||||
|
ModifierMask = 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SemanticTokensBuilder {
|
||||||
|
prev_line: u32,
|
||||||
|
prev_char: u32,
|
||||||
|
data_is_sorted_and_delta_encoded: bool,
|
||||||
|
data: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SemanticTokensBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
prev_line: 0,
|
||||||
|
prev_char: 0,
|
||||||
|
data_is_sorted_and_delta_encoded: true,
|
||||||
|
data: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(
|
||||||
|
&mut self,
|
||||||
|
line: u32,
|
||||||
|
char: u32,
|
||||||
|
length: u32,
|
||||||
|
token_type: u32,
|
||||||
|
token_modifiers: u32,
|
||||||
|
) {
|
||||||
|
if self.data_is_sorted_and_delta_encoded
|
||||||
|
&& (line < self.prev_line
|
||||||
|
|| (line == self.prev_line && char < self.prev_char))
|
||||||
|
{
|
||||||
|
// push calls were ordered and are no longer ordered
|
||||||
|
self.data_is_sorted_and_delta_encoded = false;
|
||||||
|
|
||||||
|
// Remove delta encoding from data
|
||||||
|
let token_count = self.data.len() / 5;
|
||||||
|
let mut prev_line = 0;
|
||||||
|
let mut prev_char = 0;
|
||||||
|
for i in 0..token_count {
|
||||||
|
let mut line = self.data[5 * i];
|
||||||
|
let mut char = self.data[5 * i + 1];
|
||||||
|
|
||||||
|
if line == 0 {
|
||||||
|
// on the same line as previous token
|
||||||
|
line = prev_line;
|
||||||
|
char += prev_char;
|
||||||
|
} else {
|
||||||
|
// on a different line than previous token
|
||||||
|
line += prev_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.data[5 * i] = line;
|
||||||
|
self.data[5 * i + 1] = char;
|
||||||
|
|
||||||
|
prev_line = line;
|
||||||
|
prev_char = char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut push_line = line;
|
||||||
|
let mut push_char = char;
|
||||||
|
if self.data_is_sorted_and_delta_encoded && !self.data.is_empty() {
|
||||||
|
push_line -= self.prev_line;
|
||||||
|
if push_line == 0 {
|
||||||
|
push_char -= self.prev_char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.data.reserve(5);
|
||||||
|
self.data.push(push_line);
|
||||||
|
self.data.push(push_char);
|
||||||
|
self.data.push(length);
|
||||||
|
self.data.push(token_type);
|
||||||
|
self.data.push(token_modifiers);
|
||||||
|
|
||||||
|
self.prev_line = line;
|
||||||
|
self.prev_char = char;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_to_semantic_token_vec(
|
||||||
|
data: &[u32],
|
||||||
|
data_is_sorted_and_delta_encoded: bool,
|
||||||
|
) -> Vec<SemanticToken> {
|
||||||
|
let token_count = data.len() / 5;
|
||||||
|
let mut result: Vec<SemanticToken> = Vec::with_capacity(token_count);
|
||||||
|
if data_is_sorted_and_delta_encoded {
|
||||||
|
for i in 0..token_count {
|
||||||
|
let src_offset = 5 * i;
|
||||||
|
result.push(SemanticToken {
|
||||||
|
delta_line: data[src_offset],
|
||||||
|
delta_start: data[src_offset + 1],
|
||||||
|
length: data[src_offset + 2],
|
||||||
|
token_type: data[src_offset + 3],
|
||||||
|
token_modifiers_bitset: data[src_offset + 4],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pos: Vec<usize> = (0..token_count).collect();
|
||||||
|
pos.sort_by(|a, b| {
|
||||||
|
let a_line = data[5 * a];
|
||||||
|
let b_line = data[5 * b];
|
||||||
|
if a_line == b_line {
|
||||||
|
let a_char = data[5 * a + 1];
|
||||||
|
let b_char = data[5 * b + 1];
|
||||||
|
return a_char.cmp(&b_char);
|
||||||
|
}
|
||||||
|
a_line.cmp(&b_line)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut prev_line = 0;
|
||||||
|
let mut prev_char = 0;
|
||||||
|
for i in pos.iter() {
|
||||||
|
let src_offset = 5 * i;
|
||||||
|
let line = data[src_offset];
|
||||||
|
let char = data[src_offset + 1];
|
||||||
|
let length = data[src_offset + 2];
|
||||||
|
let token_type = data[src_offset + 3];
|
||||||
|
let token_modifiers_bitset = data[src_offset + 4];
|
||||||
|
|
||||||
|
let delta_line = line - prev_line;
|
||||||
|
let delta_start = if delta_line == 0 {
|
||||||
|
char - prev_char
|
||||||
|
} else {
|
||||||
|
char
|
||||||
|
};
|
||||||
|
|
||||||
|
result.push(SemanticToken {
|
||||||
|
delta_line,
|
||||||
|
delta_start,
|
||||||
|
length,
|
||||||
|
token_type,
|
||||||
|
token_modifiers_bitset,
|
||||||
|
});
|
||||||
|
|
||||||
|
prev_line = line;
|
||||||
|
prev_char = char;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self, result_id: Option<String>) -> SemanticTokens {
|
||||||
|
SemanticTokens {
|
||||||
|
result_id,
|
||||||
|
data: SemanticTokensBuilder::data_to_semantic_token_vec(
|
||||||
|
&self.data,
|
||||||
|
self.data_is_sorted_and_delta_encoded,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_semantic_tokens_builder_simple() {
|
||||||
|
let mut builder = SemanticTokensBuilder::new();
|
||||||
|
builder.push(1, 0, 5, 1, 1);
|
||||||
|
builder.push(1, 10, 4, 2, 2);
|
||||||
|
builder.push(2, 2, 3, 2, 2);
|
||||||
|
assert_eq!(
|
||||||
|
builder.build(None).data,
|
||||||
|
vec![
|
||||||
|
SemanticToken {
|
||||||
|
delta_line: 1,
|
||||||
|
delta_start: 0,
|
||||||
|
length: 5,
|
||||||
|
token_type: 1,
|
||||||
|
token_modifiers_bitset: 1
|
||||||
|
},
|
||||||
|
SemanticToken {
|
||||||
|
delta_line: 0,
|
||||||
|
delta_start: 10,
|
||||||
|
length: 4,
|
||||||
|
token_type: 2,
|
||||||
|
token_modifiers_bitset: 2
|
||||||
|
},
|
||||||
|
SemanticToken {
|
||||||
|
delta_line: 1,
|
||||||
|
delta_start: 2,
|
||||||
|
length: 3,
|
||||||
|
token_type: 2,
|
||||||
|
token_modifiers_bitset: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_semantic_tokens_builder_out_of_order_1() {
|
||||||
|
let mut builder = SemanticTokensBuilder::new();
|
||||||
|
builder.push(2, 0, 5, 1, 1);
|
||||||
|
builder.push(2, 10, 1, 2, 2);
|
||||||
|
builder.push(2, 15, 2, 3, 3);
|
||||||
|
builder.push(1, 0, 4, 4, 4);
|
||||||
|
assert_eq!(
|
||||||
|
builder.build(None).data,
|
||||||
|
vec![
|
||||||
|
SemanticToken {
|
||||||
|
delta_line: 1,
|
||||||
|
delta_start: 0,
|
||||||
|
length: 4,
|
||||||
|
token_type: 4,
|
||||||
|
token_modifiers_bitset: 4
|
||||||
|
},
|
||||||
|
SemanticToken {
|
||||||
|
delta_line: 1,
|
||||||
|
delta_start: 0,
|
||||||
|
length: 5,
|
||||||
|
token_type: 1,
|
||||||
|
token_modifiers_bitset: 1
|
||||||
|
},
|
||||||
|
SemanticToken {
|
||||||
|
delta_line: 0,
|
||||||
|
delta_start: 10,
|
||||||
|
length: 1,
|
||||||
|
token_type: 2,
|
||||||
|
token_modifiers_bitset: 2
|
||||||
|
},
|
||||||
|
SemanticToken {
|
||||||
|
delta_line: 0,
|
||||||
|
delta_start: 5,
|
||||||
|
length: 2,
|
||||||
|
token_type: 3,
|
||||||
|
token_modifiers_bitset: 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_semantic_tokens_builder_out_of_order_2() {
|
||||||
|
let mut builder = SemanticTokensBuilder::new();
|
||||||
|
builder.push(2, 10, 5, 1, 1);
|
||||||
|
builder.push(2, 2, 4, 2, 2);
|
||||||
|
assert_eq!(
|
||||||
|
builder.build(None).data,
|
||||||
|
vec![
|
||||||
|
SemanticToken {
|
||||||
|
delta_line: 2,
|
||||||
|
delta_start: 2,
|
||||||
|
length: 4,
|
||||||
|
token_type: 2,
|
||||||
|
token_modifiers_bitset: 2
|
||||||
|
},
|
||||||
|
SemanticToken {
|
||||||
|
delta_line: 0,
|
||||||
|
delta_start: 8,
|
||||||
|
length: 5,
|
||||||
|
token_type: 1,
|
||||||
|
token_modifiers_bitset: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ where
|
||||||
P: FnMut(&T) -> bool,
|
P: FnMut(&T) -> bool,
|
||||||
{
|
{
|
||||||
let mut left = 0;
|
let mut left = 0;
|
||||||
let mut right = slice.len();
|
let mut right = slice.len() - 1;
|
||||||
|
|
||||||
while left != right {
|
while left != right {
|
||||||
let mid = left + (right - left) / 2;
|
let mid = left + (right - left) / 2;
|
||||||
|
@ -31,7 +31,7 @@ where
|
||||||
// In both cases left <= right is satisfied.
|
// In both cases left <= right is satisfied.
|
||||||
// Therefore if left < right in a step,
|
// Therefore if left < right in a step,
|
||||||
// left <= right is satisfied in the next step.
|
// left <= right is satisfied in the next step.
|
||||||
// Therefore as long as left != right, 0 <= left < right <= len is satisfied
|
// Therefore as long as left != right, 0 <= left < right < len is satisfied
|
||||||
// and if this case 0 <= mid < len is satisfied too.
|
// and if this case 0 <= mid < len is satisfied too.
|
||||||
let value = unsafe { slice.get_unchecked(mid) };
|
let value = unsafe { slice.get_unchecked(mid) };
|
||||||
if predicate(value) {
|
if predicate(value) {
|
||||||
|
@ -109,6 +109,10 @@ impl LineIndex {
|
||||||
curr_col += c_len;
|
curr_col += c_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// utf8_offsets and utf16_offsets length is equal to (# of lines + 1)
|
||||||
|
utf8_offsets.push(curr_row);
|
||||||
|
utf16_offsets.push(curr_offset_u16);
|
||||||
|
|
||||||
if !utf16_chars.is_empty() {
|
if !utf16_chars.is_empty() {
|
||||||
utf16_lines.insert(line, utf16_chars);
|
utf16_lines.insert(line, utf16_chars);
|
||||||
}
|
}
|
||||||
|
@ -185,6 +189,10 @@ impl LineIndex {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn text_content_length_utf16(&self) -> TextSize {
|
||||||
|
*self.utf16_offsets.last().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
fn utf16_to_utf8_col(&self, line: u32, mut col: u32) -> TextSize {
|
fn utf16_to_utf8_col(&self, line: u32, mut col: u32) -> TextSize {
|
||||||
if let Some(utf16_chars) = self.utf16_lines.get(&line) {
|
if let Some(utf16_chars) = self.utf16_lines.get(&line) {
|
||||||
for c in utf16_chars {
|
for c in utf16_chars {
|
||||||
|
|
|
@ -6,6 +6,8 @@ use super::analysis::ResolvedDependencyErr;
|
||||||
use super::config;
|
use super::config;
|
||||||
use super::language_server;
|
use super::language_server;
|
||||||
use super::language_server::StateSnapshot;
|
use super::language_server::StateSnapshot;
|
||||||
|
use super::semantic_tokens::SemanticTokensBuilder;
|
||||||
|
use super::semantic_tokens::TsTokenEncodingConsts;
|
||||||
use super::text;
|
use super::text;
|
||||||
use super::text::LineIndex;
|
use super::text::LineIndex;
|
||||||
|
|
||||||
|
@ -886,6 +888,56 @@ impl FileTextChanges {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Classifications {
|
||||||
|
spans: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Classifications {
|
||||||
|
pub fn to_semantic_tokens(
|
||||||
|
&self,
|
||||||
|
line_index: &LineIndex,
|
||||||
|
) -> lsp::SemanticTokens {
|
||||||
|
let token_count = self.spans.len() / 3;
|
||||||
|
let mut builder = SemanticTokensBuilder::new();
|
||||||
|
for i in 0..token_count {
|
||||||
|
let src_offset = 3 * i;
|
||||||
|
let offset = self.spans[src_offset];
|
||||||
|
let length = self.spans[src_offset + 1];
|
||||||
|
let ts_classification = self.spans[src_offset + 2];
|
||||||
|
|
||||||
|
let token_type =
|
||||||
|
Classifications::get_token_type_from_classification(ts_classification);
|
||||||
|
let token_modifiers =
|
||||||
|
Classifications::get_token_modifier_from_classification(
|
||||||
|
ts_classification,
|
||||||
|
);
|
||||||
|
|
||||||
|
let start_pos = line_index.position_tsc(offset.into());
|
||||||
|
let end_pos = line_index.position_tsc(TextSize::from(offset + length));
|
||||||
|
|
||||||
|
// start_pos.line == end_pos.line is always true as there are no multiline tokens
|
||||||
|
builder.push(
|
||||||
|
start_pos.line,
|
||||||
|
start_pos.character,
|
||||||
|
end_pos.character - start_pos.character,
|
||||||
|
token_type,
|
||||||
|
token_modifiers,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
builder.build(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_token_type_from_classification(ts_classification: u32) -> u32 {
|
||||||
|
(ts_classification >> (TsTokenEncodingConsts::TypeOffset as u32)) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_token_modifier_from_classification(ts_classification: u32) -> u32 {
|
||||||
|
ts_classification & (TsTokenEncodingConsts::ModifierMask as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CodeAction {
|
pub struct CodeAction {
|
||||||
|
@ -2150,6 +2202,8 @@ pub enum RequestMethod {
|
||||||
GetDiagnostics(Vec<ModuleSpecifier>),
|
GetDiagnostics(Vec<ModuleSpecifier>),
|
||||||
/// Return document highlights at position.
|
/// Return document highlights at position.
|
||||||
GetDocumentHighlights((ModuleSpecifier, u32, Vec<ModuleSpecifier>)),
|
GetDocumentHighlights((ModuleSpecifier, u32, Vec<ModuleSpecifier>)),
|
||||||
|
/// Get semantic highlights information for a particular file.
|
||||||
|
GetEncodedSemanticClassifications((ModuleSpecifier, TextSpan)),
|
||||||
/// Get implementation information for a specific position.
|
/// Get implementation information for a specific position.
|
||||||
GetImplementation((ModuleSpecifier, u32)),
|
GetImplementation((ModuleSpecifier, u32)),
|
||||||
/// Get a "navigation tree" for a specifier.
|
/// Get a "navigation tree" for a specifier.
|
||||||
|
@ -2259,6 +2313,14 @@ impl RequestMethod {
|
||||||
"position": position,
|
"position": position,
|
||||||
"filesToSearch": files_to_search,
|
"filesToSearch": files_to_search,
|
||||||
}),
|
}),
|
||||||
|
RequestMethod::GetEncodedSemanticClassifications((specifier, span)) => {
|
||||||
|
json!({
|
||||||
|
"id": id,
|
||||||
|
"method": "getEncodedSemanticClassifications",
|
||||||
|
"specifier": specifier,
|
||||||
|
"span": span,
|
||||||
|
})
|
||||||
|
}
|
||||||
RequestMethod::GetImplementation((specifier, position)) => json!({
|
RequestMethod::GetImplementation((specifier, position)) => json!({
|
||||||
"id": id,
|
"id": id,
|
||||||
"method": "getImplementation",
|
"method": "getImplementation",
|
||||||
|
|
12
cli/tests/lsp/semantic_tokens_did_open_notification.json
Normal file
12
cli/tests/lsp/semantic_tokens_did_open_notification.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "textDocument/didOpen",
|
||||||
|
"params": {
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.ts",
|
||||||
|
"languageId": "typescript",
|
||||||
|
"version": 1,
|
||||||
|
"text": "enum Values { value1, value2 }\n\nasync function baz(s: string): Promise<string> {\n const r = s.slice(0);\n return r;\n}\n\ninterface IFoo {\n readonly x: number;\n foo(): boolean;\n}\n\nclass Bar implements IFoo {\n constructor(public readonly x: number) { }\n foo() { return true; }\n static staticBar = new Bar(0);\n private static getStaticBar() { return Bar.staticBar; }\n}\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
cli/tests/lsp/semantic_tokens_full_request.json
Normal file
10
cli/tests/lsp/semantic_tokens_full_request.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 2,
|
||||||
|
"method": "textDocument/semanticTokens/full",
|
||||||
|
"params": {
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
cli/tests/lsp/semantic_tokens_range_request.json
Normal file
20
cli/tests/lsp/semantic_tokens_range_request.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 4,
|
||||||
|
"method": "textDocument/semanticTokens/range",
|
||||||
|
"params": {
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.ts"
|
||||||
|
},
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 0
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 6,
|
||||||
|
"character": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -660,6 +660,16 @@ delete Object.prototype.__proto__;
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case "getEncodedSemanticClassifications": {
|
||||||
|
return respond(
|
||||||
|
id,
|
||||||
|
languageService.getEncodedSemanticClassifications(
|
||||||
|
request.specifier,
|
||||||
|
request.span,
|
||||||
|
ts.SemanticClassificationFormat.TwentyTwenty,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
case "getImplementation": {
|
case "getImplementation": {
|
||||||
return respond(
|
return respond(
|
||||||
id,
|
id,
|
||||||
|
|
8
cli/tsc/compiler.d.ts
vendored
8
cli/tsc/compiler.d.ts
vendored
|
@ -56,6 +56,7 @@ declare global {
|
||||||
| GetDefinitionRequest
|
| GetDefinitionRequest
|
||||||
| GetDiagnosticsRequest
|
| GetDiagnosticsRequest
|
||||||
| GetDocumentHighlightsRequest
|
| GetDocumentHighlightsRequest
|
||||||
|
| GetEncodedSemanticClassifications
|
||||||
| GetImplementationRequest
|
| GetImplementationRequest
|
||||||
| GetNavigationTree
|
| GetNavigationTree
|
||||||
| GetOutliningSpans
|
| GetOutliningSpans
|
||||||
|
@ -144,6 +145,13 @@ declare global {
|
||||||
filesToSearch: string[];
|
filesToSearch: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetEncodedSemanticClassifications
|
||||||
|
extends BaseLanguageServerRequest {
|
||||||
|
method: "getEncodedSemanticClassifications";
|
||||||
|
specifier: string;
|
||||||
|
span: ts.TextSpan;
|
||||||
|
}
|
||||||
|
|
||||||
interface GetImplementationRequest extends BaseLanguageServerRequest {
|
interface GetImplementationRequest extends BaseLanguageServerRequest {
|
||||||
method: "getImplementation";
|
method: "getImplementation";
|
||||||
specifier: string;
|
specifier: string;
|
||||||
|
|
Loading…
Reference in a new issue