mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-04 18:28:02 +00:00
feat: fully support onEnter
edits inside comments (#823)
- select with range - multiple cursor
This commit is contained in:
parent
a2eb405430
commit
8b3a0e986a
6 changed files with 168 additions and 23 deletions
|
@ -20,8 +20,8 @@ use crate::{prelude::*, syntax::node_ancestors, SyntaxRequest};
|
|||
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,
|
||||
/// The source code range to request for.
|
||||
pub range: LspRange,
|
||||
}
|
||||
|
||||
impl SyntaxRequest for OnEnterRequest {
|
||||
|
@ -33,7 +33,8 @@ impl SyntaxRequest for OnEnterRequest {
|
|||
position_encoding: PositionEncoding,
|
||||
) -> Option<Self::Response> {
|
||||
let root = LinkedNode::new(source.root());
|
||||
let cursor = lsp_to_typst::position(self.position, position_encoding, source)?;
|
||||
let rng = lsp_to_typst::range(self.range, position_encoding, source)?;
|
||||
let cursor = rng.end;
|
||||
let leaf = root.leaf_at_compat(cursor)?;
|
||||
|
||||
let worker = OnEnterWorker {
|
||||
|
@ -42,13 +43,13 @@ impl SyntaxRequest for OnEnterRequest {
|
|||
};
|
||||
|
||||
if matches!(leaf.kind(), SyntaxKind::LineComment) {
|
||||
return worker.enter_line_doc_comment(&leaf, cursor);
|
||||
return worker.enter_line_doc_comment(&leaf, rng);
|
||||
}
|
||||
|
||||
let math_node =
|
||||
node_ancestors(&leaf).find(|node| matches!(node.kind(), SyntaxKind::Equation));
|
||||
if let Some(mn) = math_node {
|
||||
return worker.enter_block_math(mn, cursor);
|
||||
return worker.enter_block_math(mn, rng);
|
||||
}
|
||||
|
||||
None
|
||||
|
@ -68,7 +69,11 @@ impl OnEnterWorker<'_> {
|
|||
" ".repeat(indent_size)
|
||||
}
|
||||
|
||||
fn enter_line_doc_comment(&self, leaf: &LinkedNode, cursor: usize) -> Option<Vec<TextEdit>> {
|
||||
fn enter_line_doc_comment(
|
||||
&self,
|
||||
leaf: &LinkedNode,
|
||||
rng: Range<usize>,
|
||||
) -> Option<Vec<TextEdit>> {
|
||||
let skipper = |n: &LinkedNode| {
|
||||
matches!(
|
||||
n.kind(),
|
||||
|
@ -100,8 +105,6 @@ impl OnEnterWorker<'_> {
|
|||
let indent = self.indent_of(leaf.offset());
|
||||
// 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"),
|
||||
|
@ -110,9 +113,13 @@ impl OnEnterWorker<'_> {
|
|||
Some(vec![edit])
|
||||
}
|
||||
|
||||
fn enter_block_math(&self, math_node: &LinkedNode<'_>, cursor: usize) -> Option<Vec<TextEdit>> {
|
||||
fn enter_block_math(
|
||||
&self,
|
||||
math_node: &LinkedNode<'_>,
|
||||
rng: Range<usize>,
|
||||
) -> Option<Vec<TextEdit>> {
|
||||
let o = math_node.range();
|
||||
if !o.contains(&cursor) {
|
||||
if !o.contains(&rng.end) {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -124,7 +131,6 @@ impl OnEnterWorker<'_> {
|
|||
}
|
||||
|
||||
let indent = self.indent_of(o.start);
|
||||
let rng = cursor..cursor;
|
||||
let edit = TextEdit {
|
||||
range: typst_to_lsp::range(rng, self.source, self.position_encoding),
|
||||
// todo: read indent configuration
|
||||
|
|
|
@ -818,13 +818,10 @@ impl LanguageState {
|
|||
run_query!(req_id, self.Symbol(pattern))
|
||||
}
|
||||
|
||||
fn on_enter(
|
||||
&mut self,
|
||||
req_id: RequestId,
|
||||
params: TextDocumentPositionParams,
|
||||
) -> ScheduledResult {
|
||||
let (path, position) = as_path_pos(params);
|
||||
run_query!(req_id, self.OnEnter(path, position))
|
||||
fn on_enter(&mut self, req_id: RequestId, params: OnEnterParams) -> ScheduledResult {
|
||||
let path = as_path(params.text_document);
|
||||
let range = params.range;
|
||||
run_query!(req_id, self.OnEnter(path, range))
|
||||
}
|
||||
|
||||
fn will_rename_files(
|
||||
|
@ -1119,9 +1116,22 @@ struct ExportOpts {
|
|||
page: PageSelection,
|
||||
}
|
||||
|
||||
/// A parameter for the `experimental/onEnter` command.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OnEnterParams {
|
||||
/// The text document.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
/// The visible document range for which `onEnter` edits should be computed.
|
||||
pub range: Range,
|
||||
}
|
||||
|
||||
struct OnEnter;
|
||||
impl lsp_types::request::Request for OnEnter {
|
||||
type Params = TextDocumentPositionParams;
|
||||
type Params = OnEnterParams;
|
||||
type Result = Option<Vec<TextEdit>>;
|
||||
const METHOD: &'static str = "experimental/onEnter";
|
||||
}
|
||||
|
|
|
@ -317,6 +317,12 @@
|
|||
"Do not use semantic tokens for syntax highlighting"
|
||||
]
|
||||
},
|
||||
"tinymist.typingContinueCommentsOnNewline": {
|
||||
"title": "Continue Comments on Newline",
|
||||
"markdownDescription": "Whether to prefix newlines after comments with the corresponding comment prefix.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"tinymist.onEnterEvent": {
|
||||
"title": "Handling on enter events",
|
||||
"description": "Enable or disable [experimental/onEnter](https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#on-enter) (LSP onEnter feature) to allow automatic insertion of characters on enter, such as `///` for comments. Note: restarting the editor is required to change this setting.",
|
||||
|
|
|
@ -78,6 +78,19 @@ export async function doActivate(context: ExtensionContext): Promise<void> {
|
|||
extensionState.features.dragAndDrop = config.dragAndDrop === "enable";
|
||||
extensionState.features.onEnter = !!config.onEnterEvent;
|
||||
extensionState.features.renderDocs = config.renderDocs === "enable";
|
||||
|
||||
// Configures advanced language configuration
|
||||
tinymist.configureLanguage(config["typingContinueCommentsOnNewline"]);
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration("tinymist.typingContinueCommentsOnNewline")) {
|
||||
const config = loadTinymistConfig();
|
||||
// Update language configuration
|
||||
tinymist.configureLanguage(config["typingContinueCommentsOnNewline"]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// Initializes language client
|
||||
const client = initClient(context, config);
|
||||
setClient(client);
|
||||
|
@ -108,6 +121,7 @@ export async function doActivate(context: ExtensionContext): Promise<void> {
|
|||
previewSetIsTinymist();
|
||||
previewActivate(context, false);
|
||||
}
|
||||
|
||||
// Starts language client
|
||||
return await startClient(client, context);
|
||||
}
|
||||
|
|
|
@ -5,9 +5,22 @@ import { applySnippetTextEdits } from "./snippets";
|
|||
import { activeTypstEditor } from "./util";
|
||||
import { extensionState } from "./state";
|
||||
|
||||
const onEnter = new lc.RequestType<lc.TextDocumentPositionParams, lc.TextEdit[], void>(
|
||||
"experimental/onEnter",
|
||||
);
|
||||
/**
|
||||
* A parameter literal used in requests to pass a text document and a range inside that
|
||||
* document.
|
||||
*/
|
||||
export interface OnEnterParams {
|
||||
/**
|
||||
* The text document.
|
||||
*/
|
||||
textDocument: lc.TextDocumentIdentifier;
|
||||
/**
|
||||
* The range inside the text document.
|
||||
*/
|
||||
range: lc.Range;
|
||||
}
|
||||
|
||||
const onEnter = new lc.RequestType<OnEnterParams, lc.TextEdit[], void>("experimental/onEnter");
|
||||
|
||||
export async function onEnterHandler() {
|
||||
try {
|
||||
|
@ -19,6 +32,8 @@ export async function onEnterHandler() {
|
|||
await vscode.commands.executeCommand("default:type", { text: "\n" });
|
||||
}
|
||||
|
||||
// The code copied from https://github.com/rust-lang/rust-analyzer/blob/fc98e0657abf3ce07eed513e38274c89bbb2f8ad/editors/code/src/commands.ts#L199
|
||||
// doesn't work, so we change `onEnter` to pass the `range` instead of `position` to the server.
|
||||
async function handleKeypress() {
|
||||
if (!extensionState.features.onEnter) return false;
|
||||
|
||||
|
@ -29,7 +44,7 @@ async function handleKeypress() {
|
|||
const lcEdits = await client
|
||||
.sendRequest(onEnter, {
|
||||
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
|
||||
position: client.code2ProtocolConverter.asPosition(editor.selection.active),
|
||||
range: client.code2ProtocolConverter.asRange(editor.selection),
|
||||
})
|
||||
.catch((_error: any) => {
|
||||
// client.handleFailedRequest(OnEnterRequest.type, error, null);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as vscode from "vscode";
|
||||
import { LanguageClient, SymbolInformation } from "vscode-languageclient/node";
|
||||
import { spawnSync } from "child_process";
|
||||
import { resolve } from "path";
|
||||
|
@ -71,6 +72,99 @@ export const tinymist = {
|
|||
client.outputChannel.show();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The code is borrowed from https://github.com/rust-lang/rust-analyzer/blob/fc98e0657abf3ce07eed513e38274c89bbb2f8ad/editors/code/src/config.ts#L98
|
||||
* Last checked time: 2024-11-14
|
||||
*
|
||||
* Sets up additional language configuration that's impossible to do via a
|
||||
* separate language-configuration.json file. See [1] for more information.
|
||||
*
|
||||
* [1]: https://github.com/Microsoft/vscode/issues/11514#issuecomment-244707076
|
||||
*/
|
||||
configureLang: undefined as vscode.Disposable | undefined,
|
||||
configureLanguage(typingContinueCommentsOnNewline: boolean) {
|
||||
// Only need to dispose of the config if there's a change
|
||||
if (this.configureLang) {
|
||||
this.configureLang.dispose();
|
||||
this.configureLang = undefined;
|
||||
}
|
||||
|
||||
let onEnterRules: vscode.OnEnterRule[] = [
|
||||
{
|
||||
// Carry indentation from the previous line
|
||||
// if it's only whitespace
|
||||
beforeText: /^\s+$/,
|
||||
action: { indentAction: vscode.IndentAction.None },
|
||||
},
|
||||
{
|
||||
// After the end of a function/field chain,
|
||||
// with the semicolon on the same line
|
||||
beforeText: /^\s+\..*;/,
|
||||
action: { indentAction: vscode.IndentAction.Outdent },
|
||||
},
|
||||
{
|
||||
// After the end of a function/field chain,
|
||||
// with semicolon detached from the rest
|
||||
beforeText: /^\s+;/,
|
||||
previousLineText: /^\s+\..*/,
|
||||
action: { indentAction: vscode.IndentAction.Outdent },
|
||||
},
|
||||
];
|
||||
|
||||
if (typingContinueCommentsOnNewline) {
|
||||
const indentAction = vscode.IndentAction.None;
|
||||
|
||||
onEnterRules = [
|
||||
...onEnterRules,
|
||||
{
|
||||
// Doc single-line comment
|
||||
// e.g. ///|
|
||||
beforeText: /^\s*\/{3}.*$/,
|
||||
action: { indentAction, appendText: "/// " },
|
||||
},
|
||||
{
|
||||
// Parent doc single-line comment
|
||||
// e.g. //!|
|
||||
beforeText: /^\s*\/{2}\!.*$/,
|
||||
action: { indentAction, appendText: "//! " },
|
||||
},
|
||||
{
|
||||
// Begins an auto-closed multi-line comment (standard or parent doc)
|
||||
// e.g. /** | */ or /*! | */
|
||||
beforeText: /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/,
|
||||
afterText: /^\s*\*\/$/,
|
||||
action: {
|
||||
indentAction: vscode.IndentAction.IndentOutdent,
|
||||
appendText: " * ",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Begins a multi-line comment (standard or parent doc)
|
||||
// e.g. /** ...| or /*! ...|
|
||||
beforeText: /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/,
|
||||
action: { indentAction, appendText: " * " },
|
||||
},
|
||||
{
|
||||
// Continues a multi-line comment
|
||||
// e.g. * ...|
|
||||
beforeText: /^(\ \ )*\ \*(\ ([^\*]|\*(?!\/))*)?$/,
|
||||
action: { indentAction, appendText: "* " },
|
||||
},
|
||||
{
|
||||
// Dedents after closing a multi-line comment
|
||||
// e.g. */|
|
||||
beforeText: /^(\ \ )*\ \*\/\s*$/,
|
||||
action: { indentAction, removeText: 1 },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
console.log("Setting up language configuration", typingContinueCommentsOnNewline);
|
||||
this.configureLang = vscode.languages.setLanguageConfiguration("typst", {
|
||||
onEnterRules,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/// kill the probe task after 60s
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue