summaryrefslogtreecommitdiff
path: root/cli/lsp/tsc.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/lsp/tsc.rs')
-rw-r--r--cli/lsp/tsc.rs623
1 files changed, 539 insertions, 84 deletions
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 31434f529..a60f15eb8 100644
--- a/cli/lsp/tsc.rs
+++ b/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::ClassElement | ScriptElementKind::TypeElement => {
- CompletionItemKind::Class
+ | ScriptElementKind::ExternalModuleName => {
+ lsp::CompletionItemKind::Module
}
- ScriptElementKind::InterfaceElement => CompletionItemKind::Interface,
- ScriptElementKind::Warning | ScriptElementKind::ScriptElement => {
- CompletionItemKind::File
+ ScriptElementKind::ClassElement | ScriptElementKind::TypeElement => {
+ lsp::CompletionItemKind::Class
}
- 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 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 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, 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 into_completion_response(
- self,
+ pub fn as_completion_response(
+ &self,
line_index: &LineIndex,
+ settings: &config::CompletionSettings,
+ specifier: &ModuleSpecifier,
+ position: u32,
) -> lsp::CompletionResponse {
let items = self
.entries
- .into_iter()
- .map(|entry| entry.into_completion_item(line_index))
+ .iter()
+ .map(|entry| {
+ entry
+ .as_completion_item(line_index, self, settings, specifier, position)
+ })
.collect();
- lsp::CompletionResponse::Array(items)
+ 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)]
+#[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 {
+ name: String,
kind: ScriptElementKind,
+ #[serde(skip_serializing_if = "Option::is_none")]
kind_modifiers: Option<String>,
- name: 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,
- line_index: &LineIndex,
- ) -> 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()
- };
+ fn get_commit_characters(
+ &self,
+ info: &CompletionInfo,
+ settings: &config::CompletionSettings,
+ ) -> Option<Vec<String>> {
+ if info.is_new_identifier_location {
+ return None;
+ }
- 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 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("(");
+ }
+ }
+ _ => (),
}
- match item.kind {
- Some(lsp::CompletionItemKind::Function)
- | Some(lsp::CompletionItemKind::Method) => {
- item.insert_text_format = Some(lsp::InsertTextFormat::Snippet);
+ 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);
}
- _ => {}
}
- let mut insert_text = self.insert_text;
- let replacement_range: Option<lsp::Range> =
- self.replacement_span.map(|span| span.to_range(line_index));
+ self.insert_text.clone()
+ }
- // TODO(lucacasonato): port other special cases from https://github.com/theia-ide/typescript-language-server/blob/fdf28313833cd6216d00eb4e04dc7f00f4c04f09/server/src/completion.ts#L49-L55
+ pub fn as_completion_item(
+ &self,
+ line_index: &LineIndex,
+ info: &CompletionInfo,
+ settings: &config::CompletionSettings,
+ specifier: &ModuleSpecifier,
+ position: u32,
+ ) -> lsp::CompletionItem {
+ let mut label = self.name.clone();
+ let mut kind: Option<lsp::CompletionItemKind> =
+ Some(self.kind.clone().into());
- if let Some(kind_modifiers) = self.kind_modifiers {
- if kind_modifiers.contains("\\optional\\") {
+ let sort_text = if self.source.is_some() {
+ Some(format!("\u{ffff}{}", self.sort_text))
+ } else {
+ Some(self.sort_text.clone())
+ };
+
+ 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;
+
+ 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
+ };
+
+ let data = CompletionItemData {
+ specifier: specifier.clone(),
+ position,
+ name: self.name.clone(),
+ source: self.source.clone(),
+ data: self.data.clone(),
+ use_code_snippet,
+ };
- item
+ 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(&param.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,
))),
}
}
@@ -1481,6 +1729,15 @@ pub enum IncludePackageJsonAutoImports {
#[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 {
#[serde(skip_serializing_if = "Option::is_none")]
pub disable_suggestions: Option<bool>,
@@ -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": []
+ })
+ );
+ }
}