dev: make eco completion repr and reduce clones (#1079)

* dev: create pair

* dev: make eco completion repr

* dev: reduce some clones
This commit is contained in:
Myriad-Dreamin 2024-12-28 09:52:47 +08:00 committed by GitHub
parent 451dc9dbdd
commit ef4714c195
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 897 additions and 797 deletions

File diff suppressed because it is too large Load diff

View file

@ -43,6 +43,12 @@ use crate::{
use super::TypeEnv;
macro_rules! interned_str {
($name:ident, $value:expr) => {
static $name: LazyLock<Interned<str>> = LazyLock::new(|| $value.into());
};
}
/// The analysis data holds globally.
#[derive(Default, Clone)]
pub struct Analysis {
@ -146,14 +152,16 @@ impl Analysis {
}
/// Get configured trigger suggest command.
pub fn trigger_suggest(&self, context: bool) -> Option<&'static str> {
(self.completion_feat.trigger_suggest && context).then_some("editor.action.triggerSuggest")
pub fn trigger_suggest(&self, context: bool) -> Option<Interned<str>> {
interned_str!(INTERNED, "editor.action.triggerSuggest");
(self.completion_feat.trigger_suggest && context).then(|| INTERNED.clone())
}
/// Get configured trigger parameter hints command.
pub fn trigger_parameter_hints(&self, context: bool) -> Option<&'static str> {
(self.completion_feat.trigger_parameter_hints && context)
.then_some("editor.action.triggerParameterHints")
pub fn trigger_parameter_hints(&self, context: bool) -> Option<Interned<str>> {
interned_str!(INTERNED, "editor.action.triggerParameterHints");
(self.completion_feat.trigger_parameter_hints && context).then(|| INTERNED.clone())
}
/// Get configured trigger suggest after snippet command.
@ -162,7 +170,7 @@ impl Analysis {
/// > typing (word starts or trigger characters). However, you can use
/// > editor.action.triggerSuggest as command on a suggestion to "manually"
/// > retrigger suggest after inserting one
pub fn trigger_on_snippet(&self, context: bool) -> Option<&'static str> {
pub fn trigger_on_snippet(&self, context: bool) -> Option<Interned<str>> {
if !self.completion_feat.trigger_on_snippet_placeholders {
return None;
}
@ -171,13 +179,14 @@ impl Analysis {
}
/// Get configured trigger on positional parameter hints command.
pub fn trigger_on_snippet_with_param_hint(&self, context: bool) -> Option<&'static str> {
pub fn trigger_on_snippet_with_param_hint(&self, context: bool) -> Option<Interned<str>> {
interned_str!(INTERNED, "tinymist.triggerSuggestAndParameterHints");
if !self.completion_feat.trigger_on_snippet_placeholders {
return self.trigger_parameter_hints(context);
}
(self.completion_feat.trigger_suggest_and_parameter_hints && context)
.then_some("tinymist.triggerSuggestAndParameterHints")
.then(|| INTERNED.clone())
}
}

View file

@ -28,7 +28,7 @@ pub enum InteractCodeContextQuery {
}
/// A response to a `InteractCodeContextQuery`.
#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum InteractCodeContextResponse {
/// Get the mode at a specific position in a text document.

View file

@ -1,9 +1,10 @@
use lsp_types::CompletionList;
use crate::analysis::{CompletionCursor, CompletionWorker};
use crate::prelude::*;
pub(crate) mod proto;
pub use proto::*;
pub(crate) mod snippet;
pub use snippet::*;
/// The [`textDocument/completion`] request is sent from the client to the
/// server to compute completion items at a given cursor position.
@ -38,7 +39,7 @@ pub struct CompletionRequest {
}
impl StatefulRequest for CompletionRequest {
type Response = CompletionResponse;
type Response = CompletionList;
fn request(
self,
@ -86,10 +87,10 @@ impl StatefulRequest for CompletionRequest {
// To response completions in fine-grained manner, we need to mark result as
// incomplete. This follows what rust-analyzer does.
// https://github.com/rust-lang/rust-analyzer/blob/f5a9250147f6569d8d89334dc9cca79c0322729f/crates/rust-analyzer/src/handlers/request.rs#L940C55-L940C75
Some(CompletionResponse::List(CompletionList {
Some(CompletionList {
is_incomplete: false,
items,
}))
})
}
}
@ -98,10 +99,9 @@ mod tests {
use std::collections::HashSet;
use insta::with_settings;
use lsp_types::CompletionItem;
use super::*;
use crate::{syntax::find_module_level_docs, tests::*};
use crate::{completion::proto::CompletionItem, syntax::find_module_level_docs, tests::*};
struct TestConfig {
pkg_mode: bool,
@ -175,13 +175,13 @@ mod tests {
explicit: false,
trigger_character,
};
results.push(request.request(ctx, doc.clone()).map(|resp| match resp {
CompletionResponse::List(list) => CompletionResponse::List(CompletionList {
let result = request
.request(ctx, doc.clone())
.map(|list| CompletionList {
is_incomplete: list.is_incomplete,
items: get_items(list.items),
}),
CompletionResponse::Array(items) => CompletionResponse::Array(get_items(items)),
}));
});
results.push(result);
}
with_settings!({
description => format!("Completion on {text} ({rng:?})"),

View file

@ -0,0 +1,297 @@
use ecow::EcoString;
use lsp_types::InsertTextFormat;
use serde::{Deserialize, Serialize};
use crate::ty::Interned;
use super::LspRange;
/// A kind of item that can be completed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CompletionKind {
/// A syntactical structure.
Syntax,
/// A function.
Func,
/// A type.
Type,
/// A function parameter.
Param,
/// A field.
Field,
/// A constant.
#[default]
Constant,
/// A reference.
Reference,
/// A symbol.
Symbol(char),
/// A variable.
Variable,
/// A module.
Module,
/// A file.
File,
/// A folder.
Folder,
}
impl From<CompletionKind> for lsp_types::CompletionItemKind {
fn from(value: CompletionKind) -> Self {
match value {
CompletionKind::Syntax => Self::SNIPPET,
CompletionKind::Func => Self::FUNCTION,
CompletionKind::Param => Self::VARIABLE,
CompletionKind::Field => Self::FIELD,
CompletionKind::Variable => Self::VARIABLE,
CompletionKind::Constant => Self::CONSTANT,
CompletionKind::Reference => Self::REFERENCE,
CompletionKind::Symbol(_) => Self::FIELD,
CompletionKind::Type => Self::CLASS,
CompletionKind::Module => Self::MODULE,
CompletionKind::File => Self::FILE,
CompletionKind::Folder => Self::FOLDER,
}
}
}
impl serde::Serialize for CompletionKind {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
<Self as Into<lsp_types::CompletionItemKind>>::into(*self).serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for CompletionKind {
fn deserialize<D>(deserializer: D) -> Result<CompletionKind, D::Error>
where
D: serde::Deserializer<'de>,
{
let kind = lsp_types::CompletionItemKind::deserialize(deserializer)?;
Ok(match kind {
lsp_types::CompletionItemKind::SNIPPET => CompletionKind::Syntax,
lsp_types::CompletionItemKind::FUNCTION => CompletionKind::Func,
lsp_types::CompletionItemKind::VARIABLE => CompletionKind::Param,
lsp_types::CompletionItemKind::FIELD => CompletionKind::Field,
lsp_types::CompletionItemKind::CONSTANT => CompletionKind::Constant,
lsp_types::CompletionItemKind::REFERENCE => CompletionKind::Reference,
lsp_types::CompletionItemKind::CLASS => CompletionKind::Type,
lsp_types::CompletionItemKind::MODULE => CompletionKind::Module,
lsp_types::CompletionItemKind::FILE => CompletionKind::File,
lsp_types::CompletionItemKind::FOLDER => CompletionKind::Folder,
_ => CompletionKind::Variable,
})
}
}
/// An autocompletion option.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Completion {
/// The kind of item this completes to.
pub kind: CompletionKind,
/// The label the completion is shown with.
pub label: EcoString,
/// The label the completion is shown with.
pub label_details: Option<EcoString>,
/// The label the completion is shown with.
pub sort_text: Option<EcoString>,
/// The composed text used for filtering.
pub filter_text: Option<EcoString>,
/// The completed version of the input, possibly described with snippet
/// syntax like `${lhs} + ${rhs}`.
///
/// Should default to the `label` if `None`.
pub apply: Option<EcoString>,
/// An optional short description, at most one sentence.
pub detail: Option<EcoString>,
/// An optional array of additional text edits that are applied when
/// selecting this completion. Edits must not overlap with the main edit
/// nor with themselves.
pub additional_text_edits: Option<Vec<EcoTextEdit>>,
/// An optional command to run when the completion is selected.
pub command: Option<LspCompletionCommand>,
}
/// Represents a collection of [completion items](#CompletionItem) to be
/// presented in the editor.
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionList {
/// This list it not complete. Further typing should result in recomputing
/// this list.
pub is_incomplete: bool,
/// The completion items.
pub items: Vec<CompletionItem>,
}
/// Additional details for a completion item label.
///
/// @since 3.17.0
#[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CompletionItemLabelDetails {
/// An optional string which is rendered less prominently directly after
/// {@link CompletionItemLabel.label label}, without any spacing. Should be
/// used for function signatures or type annotations.
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<EcoString>,
/// An optional string which is rendered less prominently after
/// {@link CompletionItemLabel.detail}. Should be used for fully qualified
/// names or file path.
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<EcoString>,
}
impl From<EcoString> for CompletionItemLabelDetails {
fn from(description: EcoString) -> Self {
Self {
detail: None,
description: Some(description),
}
}
}
/// A textual edit applicable to a text document.
///
/// If n `EcoTextEdit`s are applied to a text document all text edits describe
/// changes to the initial document version. Execution wise text edits should
/// applied from the bottom to the top of the text document. Overlapping text
/// edits are not supported.
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EcoTextEdit {
/// The range of the text document to be manipulated. To insert
/// text into a document create a range where start === end.
pub range: LspRange,
/// The string to be inserted. For delete operations use an
/// empty string.
pub new_text: EcoString,
}
impl EcoTextEdit {
pub fn new(range: LspRange, new_text: EcoString) -> EcoTextEdit {
EcoTextEdit { range, new_text }
}
}
/// Represents a completion item.
#[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CompletionItem {
/// The label of this completion item. By default
/// also the text that is inserted when selecting
/// this completion.
pub label: EcoString,
/// Additional details for the label
///
/// @since 3.17.0
#[serde(skip_serializing_if = "Option::is_none")]
pub label_details: Option<CompletionItemLabelDetails>,
/// The kind of this completion item. Based of the kind
/// an icon is chosen by the editor.
pub kind: CompletionKind,
/// A human-readable string with additional information
/// about this item, like type or symbol information.
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<EcoString>,
/// A string that should be used when comparing this item
/// with other items. When `falsy` the label is used
/// as the sort text for this item.
#[serde(skip_serializing_if = "Option::is_none")]
pub sort_text: Option<EcoString>,
/// A string that should be used when filtering a set of
/// completion items. When `falsy` the label is used as the
/// filter text for this item.
#[serde(skip_serializing_if = "Option::is_none")]
pub filter_text: Option<EcoString>,
/// A string that should be inserted into a document when selecting
/// this completion. When `falsy` the label is used as the insert text
/// for this item.
///
/// The `insertText` is subject to interpretation by the client side.
/// Some tools might not take the string literally. For example
/// VS Code when code complete is requested in this example
/// `con<cursor position>` and a completion item with an `insertText` of
/// `console` is provided it will only insert `sole`. Therefore it is
/// recommended to use `textEdit` instead since it avoids additional client
/// side interpretation.
#[serde(skip_serializing_if = "Option::is_none")]
pub insert_text: Option<EcoString>,
/// The format of the insert text. The format applies to both the
/// `insertText` property and the `newText` property of a provided
/// `textEdit`. If omitted defaults to `InsertTextFormat.PlainText`.
///
/// @since 3.16.0
#[serde(skip_serializing_if = "Option::is_none")]
pub insert_text_format: Option<InsertTextFormat>,
/// An edit which is applied to a document when selecting
/// this completion. When an edit is provided the value of
/// insertText is ignored.
///
/// Most editors support two different operation when accepting a completion
/// item. One is to insert a completion text and the other is to replace an
/// existing text with a completion text. Since this can usually not
/// predetermined by a server it can report both ranges. Clients need to
/// signal support for `InsertReplaceEdits` via the
/// `textDocument.completion.insertReplaceSupport` client capability
/// property.
///
/// *Note 1:* The text edit's range as well as both ranges from a insert
/// replace edit must be a [single line] and they must contain the
/// position at which completion has been requested. *Note 2:* If an
/// `InsertReplaceEdit` is returned the edit's insert range must be a prefix
/// of the edit's replace range, that means it must be contained and
/// starting at the same position.
///
/// @since 3.16.0 additional type `InsertReplaceEdit`
#[serde(skip_serializing_if = "Option::is_none")]
pub text_edit: Option<EcoTextEdit>,
/// An optional array of additional text edits that are applied when
/// selecting this completion. Edits must not overlap with the main edit
/// nor with themselves.
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_text_edits: Option<Vec<EcoTextEdit>>,
/// An optional command that is executed *after* inserting this completion.
/// *Note* that additional modifications to the current document should
/// be described with the additionalTextEdits-property.
#[serde(skip_serializing_if = "Option::is_none")]
pub command: Option<LspCompletionCommand>,
}
/// Represents a reference to a command. Provides a title which will be used to
/// represent a command in the UI. Commands are identified by a string
/// identifier. The recommended way to handle commands is to implement
/// their execution on the server side if the client and server provides the
/// corresponding capabilities. Alternatively the tool extension code could
/// handle the command. The protocol currently doesnt specify a set of
/// well-known commands.
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
pub struct LspCompletionCommand {
/// The title of command.
pub title: EcoString,
/// The identifier of the actual command handler.
pub command: Interned<str>,
}
impl From<Interned<str>> for LspCompletionCommand {
fn from(command: Interned<str>) -> Self {
Self {
title: EcoString::default(),
command,
}
}
}

View file

@ -16,7 +16,7 @@ pub mod ty;
mod upstream;
pub use analysis::{CompletionFeat, LocalContext, LocalContextGuard, LspWorldExt};
pub use snippet::PostfixSnippet;
pub use completion::PostfixSnippet;
pub use upstream::with_vm;
mod entry;
@ -30,7 +30,7 @@ pub use code_context::*;
mod code_lens;
pub use code_lens::*;
mod completion;
pub use completion::*;
pub use completion::CompletionRequest;
mod color_presentation;
pub use color_presentation::*;
mod document_color;
@ -154,6 +154,7 @@ macro_rules! log_debug_ct {
#[allow(missing_docs)]
mod polymorphic {
use completion::CompletionList;
use lsp_types::TextEdit;
use serde::{Deserialize, Serialize};
use typst::foundations::Dict;
@ -370,7 +371,8 @@ mod polymorphic {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CompilerQueryResponse {
OnExport(Option<PathBuf>),
Hover(Option<Hover>),
@ -384,7 +386,7 @@ mod polymorphic {
ColorPresentation(Option<Vec<ColorPresentation>>),
CodeAction(Option<Vec<CodeActionOrCommand>>),
CodeLens(Option<Vec<CodeLens>>),
Completion(Option<CompletionResponse>),
Completion(Option<CompletionList>),
SignatureHelp(Option<SignatureHelp>),
PrepareRename(Option<PrepareRenameResponse>),
Rename(Option<WorkspaceEdit>),
@ -404,42 +406,6 @@ mod polymorphic {
DocumentMetrics(Option<DocumentMetricsResponse>),
ServerInfo(Option<HashMap<String, ServerInfoResponse>>),
}
impl CompilerQueryResponse {
pub fn to_untyped(self) -> serde_json::Result<JsonValue> {
match self {
Self::OnExport(res) => serde_json::to_value(res),
Self::Hover(res) => serde_json::to_value(res),
Self::GotoDefinition(res) => serde_json::to_value(res),
Self::GotoDeclaration(res) => serde_json::to_value(res),
Self::References(res) => serde_json::to_value(res),
Self::InlayHint(res) => serde_json::to_value(res),
Self::DocumentColor(res) => serde_json::to_value(res),
Self::DocumentLink(res) => serde_json::to_value(res),
Self::DocumentHighlight(res) => serde_json::to_value(res),
Self::ColorPresentation(res) => serde_json::to_value(res),
Self::CodeAction(res) => serde_json::to_value(res),
Self::CodeLens(res) => serde_json::to_value(res),
Self::Completion(res) => serde_json::to_value(res),
Self::SignatureHelp(res) => serde_json::to_value(res),
Self::PrepareRename(res) => serde_json::to_value(res),
Self::Rename(res) => serde_json::to_value(res),
Self::WillRenameFiles(res) => serde_json::to_value(res),
Self::DocumentSymbol(res) => serde_json::to_value(res),
Self::Symbol(res) => serde_json::to_value(res),
Self::WorkspaceLabel(res) => serde_json::to_value(res),
Self::SemanticTokensFull(res) => serde_json::to_value(res),
Self::SemanticTokensDelta(res) => serde_json::to_value(res),
Self::Formatting(res) => serde_json::to_value(res),
Self::FoldingRange(res) => serde_json::to_value(res),
Self::SelectionRange(res) => serde_json::to_value(res),
Self::InteractCodeContext(res) => serde_json::to_value(res),
Self::OnEnter(res) => serde_json::to_value(res),
Self::DocumentMetrics(res) => serde_json::to_value(res),
Self::ServerInfo(res) => serde_json::to_value(res),
}
}
}
}
pub use polymorphic::*;

View file

@ -8,14 +8,13 @@ pub use ecow::{eco_vec, EcoVec};
pub use itertools::{Format, Itertools};
pub use lsp_types::{
request::GotoDeclarationResponse, CodeAction, CodeActionKind, CodeActionOrCommand, CodeLens,
ColorInformation, ColorPresentation, CompletionResponse, Diagnostic,
DiagnosticRelatedInformation, DiagnosticSeverity, DocumentHighlight, DocumentLink,
DocumentSymbol, DocumentSymbolResponse, Documentation, FoldingRange, GotoDefinitionResponse,
Hover, HoverContents, InlayHint, LanguageString, Location as LspLocation, LocationLink,
MarkedString, MarkupContent, MarkupKind, ParameterInformation, Position as LspPosition,
PrepareRenameResponse, SelectionRange, SemanticTokens, SemanticTokensDelta,
SemanticTokensFullDeltaResult, SemanticTokensResult, SignatureHelp, SignatureInformation,
SymbolInformation, TextEdit, Url, WorkspaceEdit,
ColorInformation, ColorPresentation, Diagnostic, DiagnosticRelatedInformation,
DiagnosticSeverity, DocumentHighlight, DocumentLink, DocumentSymbol, DocumentSymbolResponse,
Documentation, FoldingRange, GotoDefinitionResponse, Hover, HoverContents, InlayHint,
LanguageString, Location as LspLocation, LocationLink, MarkedString, MarkupContent, MarkupKind,
ParameterInformation, Position as LspPosition, PrepareRenameResponse, SelectionRange,
SemanticTokens, SemanticTokensDelta, SemanticTokensFullDeltaResult, SemanticTokensResult,
SignatureHelp, SignatureInformation, SymbolInformation, TextEdit, Url, WorkspaceEdit,
};
pub use reflexo::vector::ir::DefId;
pub use serde_json::Value as JsonValue;