mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
refactor(lsp): refactor completions and add tests (#9789)
This commit is contained in:
parent
2ff9b01551
commit
506b321d47
9 changed files with 800 additions and 148 deletions
|
@ -55,7 +55,12 @@ pub fn server_capabilities(
|
|||
)),
|
||||
hover_provider: Some(HoverProviderCapability::Simple(true)),
|
||||
completion_provider: Some(CompletionOptions {
|
||||
all_commit_characters: None,
|
||||
all_commit_characters: Some(vec![
|
||||
".".to_string(),
|
||||
",".to_string(),
|
||||
";".to_string(),
|
||||
"(".to_string(),
|
||||
]),
|
||||
trigger_characters: Some(vec![
|
||||
".".to_string(),
|
||||
"\"".to_string(),
|
||||
|
@ -66,7 +71,7 @@ pub fn server_capabilities(
|
|||
"<".to_string(),
|
||||
"#".to_string(),
|
||||
]),
|
||||
resolve_provider: None,
|
||||
resolve_provider: Some(true),
|
||||
work_done_progress_options: WorkDoneProgressOptions {
|
||||
work_done_progress: None,
|
||||
},
|
||||
|
@ -77,7 +82,7 @@ pub fn server_capabilities(
|
|||
"(".to_string(),
|
||||
"<".to_string(),
|
||||
]),
|
||||
retrigger_characters: None,
|
||||
retrigger_characters: Some(vec![")".to_string()]),
|
||||
work_done_progress_options: WorkDoneProgressOptions {
|
||||
work_done_progress: None,
|
||||
},
|
||||
|
|
|
@ -15,7 +15,7 @@ pub struct ClientCapabilities {
|
|||
pub workspace_did_change_watched_files: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeLensSettings {
|
||||
/// Flag for providing implementation code lenses.
|
||||
|
@ -30,13 +30,50 @@ pub struct CodeLensSettings {
|
|||
pub references_all_functions: bool,
|
||||
}
|
||||
|
||||
impl Default for CodeLensSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
implementations: false,
|
||||
references: false,
|
||||
references_all_functions: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionSettings {
|
||||
#[serde(default)]
|
||||
pub complete_function_calls: bool,
|
||||
#[serde(default)]
|
||||
pub names: bool,
|
||||
#[serde(default)]
|
||||
pub paths: bool,
|
||||
#[serde(default)]
|
||||
pub auto_imports: bool,
|
||||
}
|
||||
|
||||
impl Default for CompletionSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
complete_function_calls: false,
|
||||
names: true,
|
||||
paths: true,
|
||||
auto_imports: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceSettings {
|
||||
pub enable: bool,
|
||||
pub config: Option<String>,
|
||||
pub import_map: Option<String>,
|
||||
pub code_lens: Option<CodeLensSettings>,
|
||||
#[serde(default)]
|
||||
pub code_lens: CodeLensSettings,
|
||||
#[serde(default)]
|
||||
pub suggest: CompletionSettings,
|
||||
|
||||
#[serde(default)]
|
||||
pub lint: bool,
|
||||
|
@ -48,36 +85,7 @@ impl WorkspaceSettings {
|
|||
/// Determine if any code lenses are enabled at all. This allows short
|
||||
/// circuiting when there are no code lenses enabled.
|
||||
pub fn enabled_code_lens(&self) -> bool {
|
||||
if let Some(code_lens) = &self.code_lens {
|
||||
// This should contain all the "top level" code lens references
|
||||
code_lens.implementations || code_lens.references
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enabled_code_lens_implementations(&self) -> bool {
|
||||
if let Some(code_lens) = &self.code_lens {
|
||||
code_lens.implementations
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enabled_code_lens_references(&self) -> bool {
|
||||
if let Some(code_lens) = &self.code_lens {
|
||||
code_lens.references
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enabled_code_lens_references_all_functions(&self) -> bool {
|
||||
if let Some(code_lens) = &self.code_lens {
|
||||
code_lens.references_all_functions
|
||||
} else {
|
||||
false
|
||||
}
|
||||
self.code_lens.implementations || self.code_lens.references
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -917,7 +917,7 @@ impl Inner {
|
|||
let mut code_lenses = cl.borrow_mut();
|
||||
|
||||
// TSC Implementations Code Lens
|
||||
if self.config.settings.enabled_code_lens_implementations() {
|
||||
if self.config.settings.code_lens.implementations {
|
||||
let source = CodeLensSource::Implementations;
|
||||
match i.kind {
|
||||
tsc::ScriptElementKind::InterfaceElement => {
|
||||
|
@ -941,7 +941,7 @@ impl Inner {
|
|||
}
|
||||
|
||||
// TSC References Code Lens
|
||||
if self.config.settings.enabled_code_lens_references() {
|
||||
if self.config.settings.code_lens.references {
|
||||
let source = CodeLensSource::References;
|
||||
if let Some(parent) = &mp {
|
||||
if parent.kind == tsc::ScriptElementKind::EnumElement {
|
||||
|
@ -950,11 +950,7 @@ impl Inner {
|
|||
}
|
||||
match i.kind {
|
||||
tsc::ScriptElementKind::FunctionElement => {
|
||||
if self
|
||||
.config
|
||||
.settings
|
||||
.enabled_code_lens_references_all_functions()
|
||||
{
|
||||
if self.config.settings.code_lens.references_all_functions {
|
||||
code_lenses.push(i.to_code_lens(
|
||||
&line_index,
|
||||
&specifier,
|
||||
|
@ -1358,7 +1354,6 @@ impl Inner {
|
|||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document_position.text_document.uri);
|
||||
// TODO(lucacasonato): handle error correctly
|
||||
let line_index =
|
||||
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
||||
line_index
|
||||
|
@ -1368,13 +1363,22 @@ impl Inner {
|
|||
specifier
|
||||
)));
|
||||
};
|
||||
let trigger_character = if let Some(context) = ¶ms.context {
|
||||
context.trigger_character.clone()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let position =
|
||||
line_index.offset_tsc(params.text_document_position.position)?;
|
||||
let req = tsc::RequestMethod::GetCompletions((
|
||||
specifier,
|
||||
line_index.offset_tsc(params.text_document_position.position)?,
|
||||
tsc::UserPreferences {
|
||||
// TODO(lucacasonato): enable this. see https://github.com/denoland/deno/pull/8651
|
||||
include_completions_with_insert_text: Some(false),
|
||||
..Default::default()
|
||||
specifier.clone(),
|
||||
position,
|
||||
tsc::GetCompletionsAtPositionOptions {
|
||||
user_preferences: tsc::UserPreferences {
|
||||
include_completions_with_insert_text: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
trigger_character,
|
||||
},
|
||||
));
|
||||
let maybe_completion_info: Option<tsc::CompletionInfo> = self
|
||||
|
@ -1387,7 +1391,12 @@ impl Inner {
|
|||
})?;
|
||||
|
||||
if let Some(completions) = maybe_completion_info {
|
||||
let results = completions.into_completion_response(&line_index);
|
||||
let results = completions.as_completion_response(
|
||||
&line_index,
|
||||
&self.config.settings.suggest,
|
||||
&specifier,
|
||||
position,
|
||||
);
|
||||
self.performance.measure(mark);
|
||||
Ok(Some(results))
|
||||
} else {
|
||||
|
@ -1396,6 +1405,47 @@ impl Inner {
|
|||
}
|
||||
}
|
||||
|
||||
async fn completion_resolve(
|
||||
&mut self,
|
||||
params: CompletionItem,
|
||||
) -> LspResult<CompletionItem> {
|
||||
let mark = self.performance.mark("completion_resolve");
|
||||
if let Some(data) = ¶ms.data {
|
||||
let data: tsc::CompletionItemData = serde_json::from_value(data.clone())
|
||||
.map_err(|err| {
|
||||
error!("{}", err);
|
||||
LspError::invalid_params(
|
||||
"Could not decode data field of completion item.",
|
||||
)
|
||||
})?;
|
||||
let req = tsc::RequestMethod::GetCompletionDetails(data.into());
|
||||
let maybe_completion_info: Option<tsc::CompletionEntryDetails> = self
|
||||
.ts_server
|
||||
.request(self.snapshot(), req)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Unable to get completion info from TypeScript: {}", err);
|
||||
LspError::internal_error()
|
||||
})?;
|
||||
if let Some(completion_info) = maybe_completion_info {
|
||||
let completion_item = completion_info.as_completion_item(¶ms);
|
||||
self.performance.measure(mark);
|
||||
Ok(completion_item)
|
||||
} else {
|
||||
error!(
|
||||
"Received an undefined response from tsc for completion details."
|
||||
);
|
||||
self.performance.measure(mark);
|
||||
Ok(params)
|
||||
}
|
||||
} else {
|
||||
self.performance.measure(mark);
|
||||
Err(LspError::invalid_params(
|
||||
"The completion item is missing the data field.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn goto_implementation(
|
||||
&mut self,
|
||||
params: GotoImplementationParams,
|
||||
|
@ -1715,6 +1765,13 @@ impl lspower::LanguageServer for LanguageServer {
|
|||
self.0.lock().await.completion(params).await
|
||||
}
|
||||
|
||||
async fn completion_resolve(
|
||||
&self,
|
||||
params: CompletionItem,
|
||||
) -> LspResult<CompletionItem> {
|
||||
self.0.lock().await.completion_resolve(params).await
|
||||
}
|
||||
|
||||
async fn goto_implementation(
|
||||
&self,
|
||||
params: GotoImplementationParams,
|
||||
|
@ -2740,6 +2797,58 @@ mod tests {
|
|||
harness.run().await;
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CompletionResult {
|
||||
pub result: Option<CompletionResponse>,
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_completions() {
|
||||
let mut harness = LspTestHarness::new(vec![
|
||||
("initialize_request.json", LspResponse::RequestAny),
|
||||
("initialized_notification.json", LspResponse::None),
|
||||
("did_open_notification_completions.json", LspResponse::None),
|
||||
(
|
||||
"completion_request.json",
|
||||
LspResponse::RequestAssert(|value| {
|
||||
let response: CompletionResult =
|
||||
serde_json::from_value(value).unwrap();
|
||||
let result = response.result.unwrap();
|
||||
match result {
|
||||
CompletionResponse::List(list) => {
|
||||
// there should be at least 90 completions for `Deno.`
|
||||
assert!(list.items.len() > 90);
|
||||
}
|
||||
_ => panic!("unexpected result"),
|
||||
}
|
||||
}),
|
||||
),
|
||||
(
|
||||
"completion_resolve_request.json",
|
||||
LspResponse::Request(
|
||||
4,
|
||||
json!({
|
||||
"label": "build",
|
||||
"kind": 6,
|
||||
"detail": "const Deno.build: {\n target: string;\n arch: \"x86_64\";\n os: \"darwin\" | \"linux\" | \"windows\";\n vendor: string;\n env?: string | undefined;\n}",
|
||||
"documentation": {
|
||||
"kind": "markdown",
|
||||
"value": "Build related information."
|
||||
},
|
||||
"sortText": "1",
|
||||
"insertTextFormat": 1,
|
||||
}),
|
||||
),
|
||||
),
|
||||
(
|
||||
"shutdown_request.json",
|
||||
LspResponse::Request(3, json!(null)),
|
||||
),
|
||||
("exit_notification.json", LspResponse::None),
|
||||
]);
|
||||
harness.run().await;
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PerformanceAverages {
|
||||
averages: Vec<PerformanceAverage>,
|
||||
|
|
649
cli/lsp/tsc.rs
649
cli/lsp/tsc.rs
|
@ -3,6 +3,7 @@
|
|||
use super::analysis::CodeLensSource;
|
||||
use super::analysis::ResolvedDependency;
|
||||
use super::analysis::ResolvedDependencyErr;
|
||||
use super::config;
|
||||
use super::language_server;
|
||||
use super::language_server::StateSnapshot;
|
||||
use super::text;
|
||||
|
@ -35,11 +36,15 @@ use regex::Captures;
|
|||
use regex::Regex;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::thread;
|
||||
use text_size::TextSize;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
const FILE_EXTENSION_KIND_MODIFIERS: &[&str] =
|
||||
&[".d.ts", ".ts", ".tsx", ".js", ".jsx", ".json"];
|
||||
|
||||
type Request = (
|
||||
RequestMethod,
|
||||
StateSnapshot,
|
||||
|
@ -170,10 +175,10 @@ pub async fn get_asset(
|
|||
}
|
||||
}
|
||||
|
||||
fn display_parts_to_string(parts: Vec<SymbolDisplayPart>) -> String {
|
||||
fn display_parts_to_string(parts: &[SymbolDisplayPart]) -> String {
|
||||
parts
|
||||
.into_iter()
|
||||
.map(|p| p.text)
|
||||
.iter()
|
||||
.map(|p| p.text.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
}
|
||||
|
@ -276,7 +281,12 @@ fn replace_links(text: &str) -> String {
|
|||
.to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||
fn parse_kind_modifier(kind_modifiers: &str) -> HashSet<&str> {
|
||||
let re = Regex::new(r",|\s+").unwrap();
|
||||
re.split(kind_modifiers).collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub enum ScriptElementKind {
|
||||
#[serde(rename = "")]
|
||||
Unknown,
|
||||
|
@ -348,42 +358,58 @@ pub enum ScriptElementKind {
|
|||
String,
|
||||
}
|
||||
|
||||
impl Default for ScriptElementKind {
|
||||
fn default() -> Self {
|
||||
Self::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScriptElementKind> for lsp::CompletionItemKind {
|
||||
fn from(kind: ScriptElementKind) -> Self {
|
||||
use lspower::lsp::CompletionItemKind;
|
||||
|
||||
match kind {
|
||||
ScriptElementKind::PrimitiveType | ScriptElementKind::Keyword => {
|
||||
CompletionItemKind::Keyword
|
||||
lsp::CompletionItemKind::Keyword
|
||||
}
|
||||
ScriptElementKind::ConstElement => CompletionItemKind::Constant,
|
||||
ScriptElementKind::LetElement
|
||||
ScriptElementKind::ConstElement
|
||||
| ScriptElementKind::LetElement
|
||||
| ScriptElementKind::VariableElement
|
||||
| ScriptElementKind::LocalVariableElement
|
||||
| ScriptElementKind::Alias => CompletionItemKind::Variable,
|
||||
| ScriptElementKind::Alias
|
||||
| ScriptElementKind::ParameterElement => {
|
||||
lsp::CompletionItemKind::Variable
|
||||
}
|
||||
ScriptElementKind::MemberVariableElement
|
||||
| ScriptElementKind::MemberGetAccessorElement
|
||||
| ScriptElementKind::MemberSetAccessorElement => {
|
||||
CompletionItemKind::Field
|
||||
lsp::CompletionItemKind::Field
|
||||
}
|
||||
ScriptElementKind::FunctionElement
|
||||
| ScriptElementKind::LocalFunctionElement => {
|
||||
lsp::CompletionItemKind::Function
|
||||
}
|
||||
ScriptElementKind::FunctionElement => CompletionItemKind::Function,
|
||||
ScriptElementKind::MemberFunctionElement
|
||||
| ScriptElementKind::ConstructSignatureElement
|
||||
| ScriptElementKind::CallSignatureElement
|
||||
| ScriptElementKind::IndexSignatureElement => CompletionItemKind::Method,
|
||||
ScriptElementKind::EnumElement => CompletionItemKind::Enum,
|
||||
| ScriptElementKind::IndexSignatureElement => {
|
||||
lsp::CompletionItemKind::Method
|
||||
}
|
||||
ScriptElementKind::EnumElement => lsp::CompletionItemKind::Enum,
|
||||
ScriptElementKind::EnumMemberElement => {
|
||||
lsp::CompletionItemKind::EnumMember
|
||||
}
|
||||
ScriptElementKind::ModuleElement
|
||||
| ScriptElementKind::ExternalModuleName => CompletionItemKind::Module,
|
||||
| ScriptElementKind::ExternalModuleName => {
|
||||
lsp::CompletionItemKind::Module
|
||||
}
|
||||
ScriptElementKind::ClassElement | ScriptElementKind::TypeElement => {
|
||||
CompletionItemKind::Class
|
||||
lsp::CompletionItemKind::Class
|
||||
}
|
||||
ScriptElementKind::InterfaceElement => CompletionItemKind::Interface,
|
||||
ScriptElementKind::Warning | ScriptElementKind::ScriptElement => {
|
||||
CompletionItemKind::File
|
||||
}
|
||||
ScriptElementKind::Directory => CompletionItemKind::Folder,
|
||||
ScriptElementKind::String => CompletionItemKind::Constant,
|
||||
_ => CompletionItemKind::Property,
|
||||
ScriptElementKind::InterfaceElement => lsp::CompletionItemKind::Interface,
|
||||
ScriptElementKind::Warning => lsp::CompletionItemKind::Text,
|
||||
ScriptElementKind::ScriptElement => lsp::CompletionItemKind::File,
|
||||
ScriptElementKind::Directory => lsp::CompletionItemKind::Folder,
|
||||
ScriptElementKind::String => lsp::CompletionItemKind::Constant,
|
||||
_ => lsp::CompletionItemKind::Property,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -432,16 +458,20 @@ pub struct QuickInfo {
|
|||
impl QuickInfo {
|
||||
pub fn to_hover(&self, line_index: &LineIndex) -> lsp::Hover {
|
||||
let mut contents = Vec::<lsp::MarkedString>::new();
|
||||
if let Some(display_string) =
|
||||
self.display_parts.clone().map(display_parts_to_string)
|
||||
if let Some(display_string) = self
|
||||
.display_parts
|
||||
.clone()
|
||||
.map(|p| display_parts_to_string(&p))
|
||||
{
|
||||
contents.push(lsp::MarkedString::from_language_code(
|
||||
"typescript".to_string(),
|
||||
display_string,
|
||||
));
|
||||
}
|
||||
if let Some(documentation) =
|
||||
self.documentation.clone().map(display_parts_to_string)
|
||||
if let Some(documentation) = self
|
||||
.documentation
|
||||
.clone()
|
||||
.map(|p| display_parts_to_string(&p))
|
||||
{
|
||||
contents.push(lsp::MarkedString::from_markdown(documentation));
|
||||
}
|
||||
|
@ -824,6 +854,15 @@ impl FileTextChanges {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeAction {
|
||||
description: String,
|
||||
changes: Vec<FileTextChanges>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
commands: Option<Vec<Value>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeFixAction {
|
||||
|
@ -882,99 +921,308 @@ impl ReferenceEntry {
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionInfo {
|
||||
entries: Vec<CompletionEntry>,
|
||||
is_member_completion: bool,
|
||||
pub struct CompletionEntryDetails {
|
||||
name: String,
|
||||
kind: ScriptElementKind,
|
||||
kind_modifiers: String,
|
||||
display_parts: Vec<SymbolDisplayPart>,
|
||||
documentation: Option<Vec<SymbolDisplayPart>>,
|
||||
tags: Option<Vec<JSDocTagInfo>>,
|
||||
code_actions: Option<Vec<CodeAction>>,
|
||||
source: Option<Vec<SymbolDisplayPart>>,
|
||||
}
|
||||
|
||||
impl CompletionInfo {
|
||||
pub fn into_completion_response(
|
||||
self,
|
||||
line_index: &LineIndex,
|
||||
) -> lsp::CompletionResponse {
|
||||
let items = self
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|entry| entry.into_completion_item(line_index))
|
||||
.collect();
|
||||
lsp::CompletionResponse::Array(items)
|
||||
impl CompletionEntryDetails {
|
||||
pub fn as_completion_item(
|
||||
&self,
|
||||
original_item: &lsp::CompletionItem,
|
||||
) -> lsp::CompletionItem {
|
||||
let detail = if original_item.detail.is_some() {
|
||||
original_item.detail.clone()
|
||||
} else if !self.display_parts.is_empty() {
|
||||
Some(replace_links(&display_parts_to_string(&self.display_parts)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let documentation = if let Some(parts) = &self.documentation {
|
||||
let mut value = display_parts_to_string(parts);
|
||||
if let Some(tags) = &self.tags {
|
||||
let tag_documentation = tags
|
||||
.iter()
|
||||
.map(get_tag_documentation)
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
value = format!("{}\n\n{}", value, tag_documentation);
|
||||
}
|
||||
Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
|
||||
kind: lsp::MarkupKind::Markdown,
|
||||
value,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// TODO(@kitsonk) add `self.code_actions`
|
||||
// TODO(@kitsonk) add `use_code_snippet`
|
||||
|
||||
lsp::CompletionItem {
|
||||
data: None,
|
||||
detail,
|
||||
documentation,
|
||||
..original_item.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionInfo {
|
||||
entries: Vec<CompletionEntry>,
|
||||
is_global_completion: bool,
|
||||
is_member_completion: bool,
|
||||
is_new_identifier_location: bool,
|
||||
metadata: Option<Value>,
|
||||
optional_replacement_span: Option<TextSpan>,
|
||||
}
|
||||
|
||||
impl CompletionInfo {
|
||||
pub fn as_completion_response(
|
||||
&self,
|
||||
line_index: &LineIndex,
|
||||
settings: &config::CompletionSettings,
|
||||
specifier: &ModuleSpecifier,
|
||||
position: u32,
|
||||
) -> lsp::CompletionResponse {
|
||||
let items = self
|
||||
.entries
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
entry
|
||||
.as_completion_item(line_index, self, settings, specifier, position)
|
||||
})
|
||||
.collect();
|
||||
let is_incomplete = self
|
||||
.metadata
|
||||
.clone()
|
||||
.map(|v| {
|
||||
v.as_object()
|
||||
.unwrap()
|
||||
.get("isIncomplete")
|
||||
.unwrap_or(&json!(false))
|
||||
.as_bool()
|
||||
.unwrap()
|
||||
})
|
||||
.unwrap_or(false);
|
||||
lsp::CompletionResponse::List(lsp::CompletionList {
|
||||
is_incomplete,
|
||||
items,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionItemData {
|
||||
pub specifier: ModuleSpecifier,
|
||||
pub position: u32,
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<Value>,
|
||||
pub use_code_snippet: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionEntry {
|
||||
kind: ScriptElementKind,
|
||||
kind_modifiers: Option<String>,
|
||||
name: String,
|
||||
kind: ScriptElementKind,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
kind_modifiers: Option<String>,
|
||||
sort_text: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
insert_text: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
replacement_span: Option<TextSpan>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
has_action: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
source: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
is_recommended: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
is_from_unchecked_file: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
data: Option<Value>,
|
||||
}
|
||||
|
||||
impl CompletionEntry {
|
||||
pub fn into_completion_item(
|
||||
self,
|
||||
fn get_commit_characters(
|
||||
&self,
|
||||
info: &CompletionInfo,
|
||||
settings: &config::CompletionSettings,
|
||||
) -> Option<Vec<String>> {
|
||||
if info.is_new_identifier_location {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut commit_characters = vec![];
|
||||
match self.kind {
|
||||
ScriptElementKind::MemberGetAccessorElement
|
||||
| ScriptElementKind::MemberSetAccessorElement
|
||||
| ScriptElementKind::ConstructSignatureElement
|
||||
| ScriptElementKind::CallSignatureElement
|
||||
| ScriptElementKind::IndexSignatureElement
|
||||
| ScriptElementKind::EnumElement
|
||||
| ScriptElementKind::InterfaceElement => {
|
||||
commit_characters.push(".");
|
||||
commit_characters.push(";");
|
||||
}
|
||||
ScriptElementKind::ModuleElement
|
||||
| ScriptElementKind::Alias
|
||||
| ScriptElementKind::ConstElement
|
||||
| ScriptElementKind::LetElement
|
||||
| ScriptElementKind::VariableElement
|
||||
| ScriptElementKind::LocalVariableElement
|
||||
| ScriptElementKind::MemberVariableElement
|
||||
| ScriptElementKind::ClassElement
|
||||
| ScriptElementKind::FunctionElement
|
||||
| ScriptElementKind::MemberFunctionElement
|
||||
| ScriptElementKind::Keyword
|
||||
| ScriptElementKind::ParameterElement => {
|
||||
commit_characters.push(".");
|
||||
commit_characters.push(",");
|
||||
commit_characters.push(";");
|
||||
if !settings.complete_function_calls {
|
||||
commit_characters.push("(");
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if commit_characters.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(commit_characters.into_iter().map(String::from).collect())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_filter_text(&self) -> Option<String> {
|
||||
// TODO(@kitsonk) this is actually quite a bit more complex.
|
||||
// See `MyCompletionItem.getFilterText` in vscode completion.ts.
|
||||
if self.name.starts_with('#') && self.insert_text.is_none() {
|
||||
return Some(self.name.clone());
|
||||
}
|
||||
|
||||
if let Some(insert_text) = &self.insert_text {
|
||||
if insert_text.starts_with("this.") {
|
||||
return None;
|
||||
}
|
||||
if insert_text.starts_with('[') {
|
||||
let re = Regex::new(r#"^\[['"](.+)['"]\]$"#).unwrap();
|
||||
let insert_text = re.replace(insert_text, ".$1").to_string();
|
||||
return Some(insert_text);
|
||||
}
|
||||
}
|
||||
|
||||
self.insert_text.clone()
|
||||
}
|
||||
|
||||
pub fn as_completion_item(
|
||||
&self,
|
||||
line_index: &LineIndex,
|
||||
info: &CompletionInfo,
|
||||
settings: &config::CompletionSettings,
|
||||
specifier: &ModuleSpecifier,
|
||||
position: u32,
|
||||
) -> lsp::CompletionItem {
|
||||
let mut item = lsp::CompletionItem {
|
||||
label: self.name,
|
||||
kind: Some(self.kind.into()),
|
||||
sort_text: Some(self.sort_text.clone()),
|
||||
// TODO(lucacasonato): missing commit_characters
|
||||
..Default::default()
|
||||
let mut label = self.name.clone();
|
||||
let mut kind: Option<lsp::CompletionItemKind> =
|
||||
Some(self.kind.clone().into());
|
||||
|
||||
let sort_text = if self.source.is_some() {
|
||||
Some(format!("\u{ffff}{}", self.sort_text))
|
||||
} else {
|
||||
Some(self.sort_text.clone())
|
||||
};
|
||||
|
||||
if let Some(true) = self.is_recommended {
|
||||
// Make sure isRecommended property always comes first
|
||||
// https://github.com/Microsoft/vscode/issues/40325
|
||||
item.preselect = Some(true);
|
||||
} else if self.source.is_some() {
|
||||
// De-prioritze auto-imports
|
||||
// https://github.com/Microsoft/vscode/issues/40311
|
||||
item.sort_text = Some("\u{ffff}".to_string() + &self.sort_text)
|
||||
}
|
||||
let preselect = self.is_recommended;
|
||||
let use_code_snippet = settings.complete_function_calls
|
||||
&& (kind == Some(lsp::CompletionItemKind::Function)
|
||||
|| kind == Some(lsp::CompletionItemKind::Method));
|
||||
// TODO(@kitsonk) missing from types: https://github.com/gluon-lang/lsp-types/issues/204
|
||||
let _commit_characters = self.get_commit_characters(info, settings);
|
||||
let mut insert_text = self.insert_text.clone();
|
||||
let range = self.replacement_span.clone();
|
||||
let mut filter_text = self.get_filter_text();
|
||||
let mut tags = None;
|
||||
let mut detail = None;
|
||||
|
||||
match item.kind {
|
||||
Some(lsp::CompletionItemKind::Function)
|
||||
| Some(lsp::CompletionItemKind::Method) => {
|
||||
item.insert_text_format = Some(lsp::InsertTextFormat::Snippet);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut insert_text = self.insert_text;
|
||||
let replacement_range: Option<lsp::Range> =
|
||||
self.replacement_span.map(|span| span.to_range(line_index));
|
||||
|
||||
// TODO(lucacasonato): port other special cases from https://github.com/theia-ide/typescript-language-server/blob/fdf28313833cd6216d00eb4e04dc7f00f4c04f09/server/src/completion.ts#L49-L55
|
||||
|
||||
if let Some(kind_modifiers) = self.kind_modifiers {
|
||||
if kind_modifiers.contains("\\optional\\") {
|
||||
if let Some(kind_modifiers) = &self.kind_modifiers {
|
||||
let kind_modifiers = parse_kind_modifier(kind_modifiers);
|
||||
if kind_modifiers.contains("optional") {
|
||||
if insert_text.is_none() {
|
||||
insert_text = Some(item.label.clone());
|
||||
insert_text = Some(label.clone());
|
||||
}
|
||||
if item.filter_text.is_none() {
|
||||
item.filter_text = Some(item.label.clone());
|
||||
if filter_text.is_none() {
|
||||
filter_text = Some(label.clone());
|
||||
}
|
||||
label += "?";
|
||||
}
|
||||
if kind_modifiers.contains("deprecated") {
|
||||
tags = Some(vec![lsp::CompletionItemTag::Deprecated]);
|
||||
}
|
||||
if kind_modifiers.contains("color") {
|
||||
kind = Some(lsp::CompletionItemKind::Color);
|
||||
}
|
||||
if self.kind == ScriptElementKind::ScriptElement {
|
||||
for ext_modifier in FILE_EXTENSION_KIND_MODIFIERS {
|
||||
if kind_modifiers.contains(ext_modifier) {
|
||||
detail = if self.name.to_lowercase().ends_with(ext_modifier) {
|
||||
Some(self.name.clone())
|
||||
} else {
|
||||
Some(format!("{}{}", self.name, ext_modifier))
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
item.label += "?";
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(insert_text) = insert_text {
|
||||
if let Some(replacement_range) = replacement_range {
|
||||
item.text_edit = Some(lsp::CompletionTextEdit::Edit(
|
||||
lsp::TextEdit::new(replacement_range, insert_text),
|
||||
));
|
||||
let text_edit =
|
||||
if let (Some(text_span), Some(new_text)) = (range, insert_text) {
|
||||
let range = text_span.to_range(line_index);
|
||||
let insert_replace_edit = lsp::InsertReplaceEdit {
|
||||
new_text,
|
||||
insert: range,
|
||||
replace: range,
|
||||
};
|
||||
Some(insert_replace_edit.into())
|
||||
} else {
|
||||
item.insert_text = Some(insert_text);
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
item
|
||||
let data = CompletionItemData {
|
||||
specifier: specifier.clone(),
|
||||
position,
|
||||
name: self.name.clone(),
|
||||
source: self.source.clone(),
|
||||
data: self.data.clone(),
|
||||
use_code_snippet,
|
||||
};
|
||||
|
||||
lsp::CompletionItem {
|
||||
label,
|
||||
kind,
|
||||
sort_text,
|
||||
preselect,
|
||||
text_edit,
|
||||
filter_text,
|
||||
detail,
|
||||
tags,
|
||||
data: Some(serde_json::to_value(data).unwrap()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1016,18 +1264,18 @@ pub struct SignatureHelpItem {
|
|||
|
||||
impl SignatureHelpItem {
|
||||
pub fn into_signature_information(self) -> lsp::SignatureInformation {
|
||||
let prefix_text = display_parts_to_string(self.prefix_display_parts);
|
||||
let prefix_text = display_parts_to_string(&self.prefix_display_parts);
|
||||
let params_text = self
|
||||
.parameters
|
||||
.iter()
|
||||
.map(|param| display_parts_to_string(param.display_parts.clone()))
|
||||
.map(|param| display_parts_to_string(¶m.display_parts))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let suffix_text = display_parts_to_string(self.suffix_display_parts);
|
||||
let suffix_text = display_parts_to_string(&self.suffix_display_parts);
|
||||
lsp::SignatureInformation {
|
||||
label: format!("{}{}{}", prefix_text, params_text, suffix_text),
|
||||
documentation: Some(lsp::Documentation::String(display_parts_to_string(
|
||||
self.documentation,
|
||||
&self.documentation,
|
||||
))),
|
||||
parameters: Some(
|
||||
self
|
||||
|
@ -1054,10 +1302,10 @@ impl SignatureHelpParameter {
|
|||
pub fn into_parameter_information(self) -> lsp::ParameterInformation {
|
||||
lsp::ParameterInformation {
|
||||
label: lsp::ParameterLabel::Simple(display_parts_to_string(
|
||||
self.display_parts,
|
||||
&self.display_parts,
|
||||
)),
|
||||
documentation: Some(lsp::Documentation::String(display_parts_to_string(
|
||||
self.documentation,
|
||||
&self.documentation,
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
@ -1479,6 +1727,15 @@ pub enum IncludePackageJsonAutoImports {
|
|||
Off,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetCompletionsAtPositionOptions {
|
||||
#[serde(flatten)]
|
||||
pub user_preferences: UserPreferences,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub trigger_character: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserPreferences {
|
||||
|
@ -1542,6 +1799,30 @@ pub struct SignatureHelpTriggerReason {
|
|||
pub trigger_character: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetCompletionDetailsArgs {
|
||||
pub specifier: ModuleSpecifier,
|
||||
pub position: u32,
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<Value>,
|
||||
}
|
||||
|
||||
impl From<CompletionItemData> for GetCompletionDetailsArgs {
|
||||
fn from(item_data: CompletionItemData) -> Self {
|
||||
Self {
|
||||
specifier: item_data.specifier,
|
||||
position: item_data.position,
|
||||
name: item_data.name,
|
||||
source: item_data.source,
|
||||
data: item_data.data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Methods that are supported by the Language Service in the compiler isolate.
|
||||
#[derive(Debug)]
|
||||
pub enum RequestMethod {
|
||||
|
@ -1554,7 +1835,9 @@ pub enum RequestMethod {
|
|||
/// Retrieve code fixes for a range of a file with the provided error codes.
|
||||
GetCodeFixes((ModuleSpecifier, u32, u32, Vec<String>)),
|
||||
/// Get completion information at a given position (IntelliSense).
|
||||
GetCompletions((ModuleSpecifier, u32, UserPreferences)),
|
||||
GetCompletions((ModuleSpecifier, u32, GetCompletionsAtPositionOptions)),
|
||||
/// Get details about a specific completion entry.
|
||||
GetCompletionDetails(GetCompletionDetailsArgs),
|
||||
/// Retrieve the combined code fixes for a fix id for a module.
|
||||
GetCombinedCodeFix((ModuleSpecifier, Value)),
|
||||
/// Get declaration information for a specific position.
|
||||
|
@ -1626,6 +1909,11 @@ impl RequestMethod {
|
|||
"specifier": specifier,
|
||||
"fixId": fix_id,
|
||||
}),
|
||||
RequestMethod::GetCompletionDetails(args) => json!({
|
||||
"id": id,
|
||||
"method": "getCompletionDetails",
|
||||
"args": args
|
||||
}),
|
||||
RequestMethod::GetCompletions((specifier, position, preferences)) => {
|
||||
json!({
|
||||
"id": id,
|
||||
|
@ -1738,6 +2026,7 @@ mod tests {
|
|||
use crate::lsp::analysis;
|
||||
use crate::lsp::documents::DocumentCache;
|
||||
use crate::lsp::sources::Sources;
|
||||
use crate::lsp::text::LineIndex;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::TempDir;
|
||||
|
@ -2228,4 +2517,170 @@ mod tests {
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_entry_filter_text() {
|
||||
let fixture = CompletionEntry {
|
||||
kind: ScriptElementKind::MemberVariableElement,
|
||||
name: "['foo']".to_string(),
|
||||
insert_text: Some("['foo']".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let actual = fixture.get_filter_text();
|
||||
assert_eq!(actual, Some(".foo".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completions() {
|
||||
let fixture = r#"
|
||||
import { B } from "https://deno.land/x/b/mod.ts";
|
||||
|
||||
const b = new B();
|
||||
|
||||
console.
|
||||
"#;
|
||||
let line_index = LineIndex::new(fixture);
|
||||
let position = line_index
|
||||
.offset_tsc(lsp::Position {
|
||||
line: 5,
|
||||
character: 16,
|
||||
})
|
||||
.unwrap();
|
||||
let (mut runtime, state_snapshot, _) = setup(
|
||||
false,
|
||||
json!({
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"lib": ["deno.ns", "deno.window"],
|
||||
"noEmit": true,
|
||||
}),
|
||||
&[("file:///a.ts", fixture, 1)],
|
||||
);
|
||||
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
|
||||
let result = request(
|
||||
&mut runtime,
|
||||
state_snapshot.clone(),
|
||||
RequestMethod::GetDiagnostics(vec![specifier.clone()]),
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
let result = request(
|
||||
&mut runtime,
|
||||
state_snapshot.clone(),
|
||||
RequestMethod::GetCompletions((
|
||||
specifier.clone(),
|
||||
position,
|
||||
GetCompletionsAtPositionOptions {
|
||||
user_preferences: UserPreferences {
|
||||
include_completions_with_insert_text: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
trigger_character: Some(".".to_string()),
|
||||
},
|
||||
)),
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
let response: CompletionInfo =
|
||||
serde_json::from_value(result.unwrap()).unwrap();
|
||||
assert_eq!(response.entries.len(), 20);
|
||||
let result = request(
|
||||
&mut runtime,
|
||||
state_snapshot,
|
||||
RequestMethod::GetCompletionDetails(GetCompletionDetailsArgs {
|
||||
specifier,
|
||||
position,
|
||||
name: "log".to_string(),
|
||||
source: None,
|
||||
data: None,
|
||||
}),
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
let response = result.unwrap();
|
||||
assert_eq!(
|
||||
response,
|
||||
json!({
|
||||
"name": "log",
|
||||
"kindModifiers": "declare",
|
||||
"kind": "method",
|
||||
"displayParts": [
|
||||
{
|
||||
"text": "(",
|
||||
"kind": "punctuation"
|
||||
},
|
||||
{
|
||||
"text": "method",
|
||||
"kind": "text"
|
||||
},
|
||||
{
|
||||
"text": ")",
|
||||
"kind": "punctuation"
|
||||
},
|
||||
{
|
||||
"text": " ",
|
||||
"kind": "space"
|
||||
},
|
||||
{
|
||||
"text": "Console",
|
||||
"kind": "interfaceName"
|
||||
},
|
||||
{
|
||||
"text": ".",
|
||||
"kind": "punctuation"
|
||||
},
|
||||
{
|
||||
"text": "log",
|
||||
"kind": "methodName"
|
||||
},
|
||||
{
|
||||
"text": "(",
|
||||
"kind": "punctuation"
|
||||
},
|
||||
{
|
||||
"text": "...",
|
||||
"kind": "punctuation"
|
||||
},
|
||||
{
|
||||
"text": "data",
|
||||
"kind": "parameterName"
|
||||
},
|
||||
{
|
||||
"text": ":",
|
||||
"kind": "punctuation"
|
||||
},
|
||||
{
|
||||
"text": " ",
|
||||
"kind": "space"
|
||||
},
|
||||
{
|
||||
"text": "any",
|
||||
"kind": "keyword"
|
||||
},
|
||||
{
|
||||
"text": "[",
|
||||
"kind": "punctuation"
|
||||
},
|
||||
{
|
||||
"text": "]",
|
||||
"kind": "punctuation"
|
||||
},
|
||||
{
|
||||
"text": ")",
|
||||
"kind": "punctuation"
|
||||
},
|
||||
{
|
||||
"text": ":",
|
||||
"kind": "punctuation"
|
||||
},
|
||||
{
|
||||
"text": " ",
|
||||
"kind": "space"
|
||||
},
|
||||
{
|
||||
"text": "void",
|
||||
"kind": "keyword"
|
||||
}
|
||||
],
|
||||
"documentation": []
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
18
cli/tests/lsp/completion_request.json
Normal file
18
cli/tests/lsp/completion_request.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "textDocument/completion",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts"
|
||||
},
|
||||
"position": {
|
||||
"line": 0,
|
||||
"character": 5
|
||||
},
|
||||
"context": {
|
||||
"triggerKind": 2,
|
||||
"triggerCharacter": "."
|
||||
}
|
||||
}
|
||||
}
|
17
cli/tests/lsp/completion_resolve_request.json
Normal file
17
cli/tests/lsp/completion_resolve_request.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"method": "completionItem/resolve",
|
||||
"params": {
|
||||
"label": "build",
|
||||
"kind": 6,
|
||||
"sortText": "1",
|
||||
"insertTextFormat": 1,
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"position": 5,
|
||||
"name": "build",
|
||||
"useCodeSnippet": false
|
||||
}
|
||||
}
|
||||
}
|
12
cli/tests/lsp/did_open_notification_completions.json
Normal file
12
cli/tests/lsp/did_open_notification_completions.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": "Deno."
|
||||
}
|
||||
}
|
||||
}
|
|
@ -594,6 +594,22 @@ delete Object.prototype.__proto__;
|
|||
),
|
||||
);
|
||||
}
|
||||
case "getCompletionDetails": {
|
||||
debug("request", request);
|
||||
return respond(
|
||||
id,
|
||||
languageService.getCompletionEntryDetails(
|
||||
request.args.specifier,
|
||||
request.args.position,
|
||||
request.args.name,
|
||||
undefined,
|
||||
request.args.source,
|
||||
undefined,
|
||||
// @ts-expect-error this exists in 4.3 but not part of the d.ts
|
||||
request.args.data,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "getCompletions": {
|
||||
return respond(
|
||||
id,
|
||||
|
|
14
cli/tsc/compiler.d.ts
vendored
14
cli/tsc/compiler.d.ts
vendored
|
@ -51,6 +51,7 @@ declare global {
|
|||
| GetAsset
|
||||
| GetCodeFixes
|
||||
| GetCombinedCodeFix
|
||||
| GetCompletionDetails
|
||||
| GetCompletionsRequest
|
||||
| GetDefinitionRequest
|
||||
| GetDiagnosticsRequest
|
||||
|
@ -102,11 +103,22 @@ declare global {
|
|||
fixId: {};
|
||||
}
|
||||
|
||||
interface GetCompletionDetails extends BaseLanguageServerRequest {
|
||||
method: "getCompletionDetails";
|
||||
args: {
|
||||
specifier: string;
|
||||
position: number;
|
||||
name: string;
|
||||
source?: string;
|
||||
data?: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetCompletionsRequest extends BaseLanguageServerRequest {
|
||||
method: "getCompletions";
|
||||
specifier: string;
|
||||
position: number;
|
||||
preferences: ts.UserPreferences;
|
||||
preferences: ts.GetCompletionsAtPositionOptions;
|
||||
}
|
||||
|
||||
interface GetDiagnosticsRequest extends BaseLanguageServerRequest {
|
||||
|
|
Loading…
Reference in a new issue