feat: implements experimental/onEnter (#328)

* feat: implements `experimental/onEnter`

* docs: update readme

* dev: update snapshot

* dev: allow configuration
This commit is contained in:
Myriad-Dreamin 2024-06-16 17:33:46 +08:00 committed by GitHub
parent 8d753d8c56
commit 95a68d2559
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 373 additions and 3 deletions

View file

@ -65,6 +65,8 @@ pub(crate) mod signature_help;
pub use signature_help::*;
pub(crate) mod symbol;
pub use symbol::*;
pub(crate) mod on_enter;
pub use on_enter::*;
pub(crate) mod prepare_rename;
pub use prepare_rename::*;
pub(crate) mod references;
@ -222,6 +224,8 @@ mod polymorphic {
SelectionRange(SelectionRangeRequest),
InteractCodeContext(InteractCodeContextRequest),
OnEnter(OnEnterRequest),
DocumentMetrics(DocumentMetricsRequest),
ServerInfo(ServerInfoRequest),
}
@ -255,6 +259,8 @@ mod polymorphic {
CompilerQueryRequest::SelectionRange(..) => ContextFreeUnique,
CompilerQueryRequest::InteractCodeContext(..) => PinnedFirst,
CompilerQueryRequest::OnEnter(..) => ContextFreeUnique,
CompilerQueryRequest::DocumentMetrics(..) => PinnedFirst,
CompilerQueryRequest::ServerInfo(..) => Mergeable,
}
@ -286,6 +292,7 @@ mod polymorphic {
CompilerQueryRequest::FoldingRange(req) => &req.path,
CompilerQueryRequest::SelectionRange(req) => &req.path,
CompilerQueryRequest::InteractCodeContext(req) => &req.path,
CompilerQueryRequest::OnEnter(req) => &req.path,
CompilerQueryRequest::DocumentMetrics(req) => &req.path,
CompilerQueryRequest::ServerInfo(..) => return None,
@ -320,6 +327,8 @@ mod polymorphic {
SelectionRange(Option<Vec<SelectionRange>>),
InteractCodeContext(Option<Vec<InteractCodeContextResponse>>),
OnEnter(Option<Vec<TextEdit>>),
DocumentMetrics(Option<DocumentMetricsResponse>),
ServerInfo(Option<HashMap<String, ServerInfoResponse>>),
}

View file

@ -0,0 +1,98 @@
//! <https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#on-enter>
use crate::{prelude::*, SyntaxRequest};
/// The [`experimental/onEnter`] request is sent from client to server to handle
/// the <kbd>Enter</kbd> key press.
///
/// - kbd:[Enter] inside triple-slash comments automatically inserts `///`
/// - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//`
/// - kbd:[Enter] inside `//!` doc comments automatically inserts `//!`
///
/// [`experimental/onEnter`]: https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#on-enter
///
/// # Compatibility
///
/// This request was introduced in specification version 3.10.0.
#[derive(Debug, Clone)]
pub struct OnEnterRequest {
/// The path of the document to get folding ranges for.
pub path: PathBuf,
/// The source code position to request for.
pub position: LspPosition,
}
impl SyntaxRequest for OnEnterRequest {
type Response = Vec<TextEdit>;
fn request(
self,
source: &Source,
position_encoding: PositionEncoding,
) -> Option<Self::Response> {
let root = LinkedNode::new(source.root());
let cursor = lsp_to_typst::position(self.position, position_encoding, source)?;
let leaf = root.leaf_at(cursor)?;
let worker = OnEnterWorker {
source,
position_encoding,
};
if matches!(leaf.kind(), SyntaxKind::LineComment) {
return worker.enter_line_doc_comment(&leaf, cursor);
}
None
}
}
struct OnEnterWorker<'a> {
source: &'a Source,
position_encoding: PositionEncoding,
}
impl OnEnterWorker<'_> {
fn enter_line_doc_comment(&self, leaf: &LinkedNode, cursor: usize) -> Option<Vec<TextEdit>> {
let skipper = |n: &LinkedNode| {
matches!(
n.kind(),
SyntaxKind::Space | SyntaxKind::Linebreak | SyntaxKind::LineComment
)
};
let parent = leaf.parent()?;
let till_curr = parent.children().take(leaf.index());
let first_index = till_curr.rev().take_while(skipper).count();
let comment_group_cnt = parent
.children()
.skip(leaf.index().saturating_sub(first_index))
.take_while(skipper)
.filter(|e| matches!(e.kind(), SyntaxKind::LineComment))
.count();
let comment_prefix = {
let mut p = unscanny::Scanner::new(leaf.text());
p.eat_while('/');
p.eat_if('!');
p.before()
};
// Continuing single-line non-doc comments (like this one :) ) is annoying
if comment_group_cnt <= 1 && comment_prefix == "//" {
return None;
}
// todo: indent
let indent = "";
// todo: remove_trailing_whitespace
let rng = cursor..cursor;
let edit = TextEdit {
range: typst_to_lsp::range(rng, self.source, self.position_encoding),
new_text: format!("\n{indent}{comment_prefix} $0"),
};
Some(vec![edit])
}
}