mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-12-23 08:47:50 +00:00
Merge 9e22e19353 into 0bfe655ada
This commit is contained in:
commit
b2406b4d2d
18 changed files with 286 additions and 3 deletions
|
|
@ -17,3 +17,9 @@ let sys;
|
|||
```typc
|
||||
<module sys>
|
||||
```
|
||||
|
||||
|
||||
======
|
||||
|
||||
|
||||
[Show Full Value](command:tinymist.showFullValue)
|
||||
|
|
|
|||
|
|
@ -9,3 +9,9 @@ Range: 0:20:0:23
|
|||
```typc
|
||||
rgb("#ff4136")
|
||||
```
|
||||
|
||||
|
||||
======
|
||||
|
||||
|
||||
[Show Full Value](command:tinymist.showFullValue)
|
||||
|
|
|
|||
|
|
@ -17,3 +17,9 @@ let sys;
|
|||
```typc
|
||||
<module sys>
|
||||
```
|
||||
|
||||
|
||||
======
|
||||
|
||||
|
||||
[Show Full Value](command:tinymist.showFullValue)
|
||||
|
|
|
|||
|
|
@ -17,3 +17,9 @@ let sys;
|
|||
```typc
|
||||
<module sys>
|
||||
```
|
||||
|
||||
|
||||
======
|
||||
|
||||
|
||||
[Show Full Value](command:tinymist.showFullValue)
|
||||
|
|
|
|||
|
|
@ -9,3 +9,9 @@ Range: 2:23:2:27
|
|||
```typc
|
||||
"A"
|
||||
```
|
||||
|
||||
|
||||
======
|
||||
|
||||
|
||||
[Show Full Value](command:tinymist.showFullValue)
|
||||
|
|
|
|||
|
|
@ -15,3 +15,10 @@ Range: 2:24:2:31
|
|||
|
||||
|
||||
## The Module (Alias)
|
||||
|
||||
|
||||
|
||||
======
|
||||
|
||||
|
||||
[Show Full Value](command:tinymist.showFullValue)
|
||||
|
|
|
|||
|
|
@ -15,3 +15,9 @@ This star imports line
|
|||
```typc
|
||||
none
|
||||
```
|
||||
|
||||
|
||||
======
|
||||
|
||||
|
||||
[Show Full Value](command:tinymist.showFullValue)
|
||||
|
|
|
|||
|
|
@ -15,3 +15,10 @@ Range: 2:24:2:30
|
|||
|
||||
|
||||
## The Module
|
||||
|
||||
|
||||
|
||||
======
|
||||
|
||||
|
||||
[Show Full Value](command:tinymist.showFullValue)
|
||||
|
|
|
|||
89
crates/tinymist-query/src/full_value.rs
Normal file
89
crates/tinymist-query/src/full_value.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use std::fmt::Write;
|
||||
use tinymist_analysis::{analyze_expr, upstream::truncated_repr_};
|
||||
use typst::{engine::Sink, syntax::LinkedNode};
|
||||
use typst_shim::syntax::LinkedNodeExt;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A request to show the full tracked value at a specific position.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShowFullValueRequest {
|
||||
/// The source file.
|
||||
pub path: PathBuf,
|
||||
/// The cursor position.
|
||||
pub position: LspPosition,
|
||||
}
|
||||
|
||||
impl SemanticRequest for ShowFullValueRequest {
|
||||
type Response = String;
|
||||
|
||||
fn request(self, ctx: &mut LocalContext) -> Option<Self::Response> {
|
||||
let source = ctx.source_by_path(&self.path).ok()?;
|
||||
let offset = ctx.to_typst_pos(self.position, &source)?;
|
||||
// the typst's cursor is 1-based, so we need to add 1 to the offset
|
||||
let cursor = offset + 1;
|
||||
|
||||
let leaf = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
|
||||
let expr = get_inspected_expr(&leaf)?;
|
||||
let content = format_values(&ctx.world(), expr)?;
|
||||
|
||||
Some(content)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_inspected_expr<'a>(leaf: &'a LinkedNode<'a>) -> Option<&'a LinkedNode<'a>> {
|
||||
let mut ancestor = leaf;
|
||||
while !ancestor.is::<ast::Expr>() {
|
||||
ancestor = ancestor.parent()?;
|
||||
}
|
||||
|
||||
let expr = ancestor.cast::<ast::Expr>()?;
|
||||
if !expr.hash() && !matches!(expr, ast::Expr::MathIdent(_)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ancestor)
|
||||
}
|
||||
|
||||
fn format_values(world: &dyn World, expr: &LinkedNode) -> Option<String> {
|
||||
struct Piece<'a> {
|
||||
value: &'a Value,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
let values = analyze_expr(world, expr);
|
||||
|
||||
let mut pieces: Vec<Piece<'_>> = vec![];
|
||||
let mut last = None;
|
||||
for (value, _) in values.iter() {
|
||||
if last.replace(value).is_some_and(|last| *last == *value) {
|
||||
pieces.last_mut().unwrap().count += 1;
|
||||
} else {
|
||||
pieces.push(Piece { value, count: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
const SIZE_LIMIT: usize = 512 * 1024 * 1024; // 512MB
|
||||
|
||||
let mut buf = String::new();
|
||||
let mut limited = false;
|
||||
for piece in pieces {
|
||||
let item_repr = truncated_repr_::<SIZE_LIMIT>(piece.value);
|
||||
if buf.len() + item_repr.len() + 50 > SIZE_LIMIT {
|
||||
buf.push_str("... (reached size limit)\n");
|
||||
limited = true;
|
||||
break;
|
||||
}
|
||||
buf.push('#');
|
||||
buf.push_str(&item_repr);
|
||||
if piece.count > 1 {
|
||||
write!(buf, " // (x{})", piece.count).unwrap();
|
||||
}
|
||||
buf.push('\n');
|
||||
}
|
||||
if !limited && values.len() == Sink::MAX_VALUES {
|
||||
buf.push_str("... (reached max values limit)\n");
|
||||
}
|
||||
|
||||
Some(buf)
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
use core::fmt::{self, Write};
|
||||
|
||||
use serde_json::Value as JsonValue;
|
||||
use tinymist_std::typst::TypstDocument;
|
||||
use typst::foundations::repr::separated_list;
|
||||
use typst::syntax::{LinkedNode, ast};
|
||||
use typst_shim::syntax::LinkedNodeExt;
|
||||
|
||||
use crate::analysis::get_link_exprs_in;
|
||||
|
|
@ -108,10 +110,24 @@ impl HoverWorker<'_> {
|
|||
/// Dynamic analysis results
|
||||
fn dynamic_analysis(&mut self) -> Option<()> {
|
||||
let typst_tooltip = self.ctx.tooltip(&self.source, self.cursor)?;
|
||||
|
||||
self.value.push(match typst_tooltip {
|
||||
Tooltip::Text(text) => text.to_string(),
|
||||
Tooltip::Code(code) => format!("### Sampled Values\n```typc\n{code}\n```"),
|
||||
Tooltip::Code(code) => {
|
||||
// Add a button to show full values
|
||||
// todo - we may need only add this button if the code is too long
|
||||
self.actions.push(CommandLink {
|
||||
title: Some("Show Full Value".to_string()),
|
||||
command_or_links: vec![CommandOrLink::Command {
|
||||
id: "tinymist.showFullValue".to_string(),
|
||||
args: vec![],
|
||||
}],
|
||||
});
|
||||
|
||||
format!("### Sampled Values\n```typc\n{code}\n```")
|
||||
}
|
||||
});
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ pub use document_link::*;
|
|||
pub use document_metrics::*;
|
||||
pub use document_symbol::*;
|
||||
pub use folding_range::*;
|
||||
pub use full_value::*;
|
||||
pub use goto_declaration::*;
|
||||
pub use goto_definition::*;
|
||||
pub use hover::*;
|
||||
|
|
@ -71,6 +72,7 @@ mod document_link;
|
|||
mod document_metrics;
|
||||
mod document_symbol;
|
||||
mod folding_range;
|
||||
mod full_value;
|
||||
mod goto_declaration;
|
||||
mod goto_definition;
|
||||
mod hover;
|
||||
|
|
@ -263,6 +265,7 @@ mod polymorphic {
|
|||
SelectionRange(SelectionRangeRequest),
|
||||
/// A request to interact with the code context.
|
||||
InteractCodeContext(InteractCodeContextRequest),
|
||||
ShowFullValue(ShowFullValueRequest),
|
||||
|
||||
/// A request to get extra text edits on enter.
|
||||
OnEnter(OnEnterRequest),
|
||||
|
|
@ -306,6 +309,7 @@ mod polymorphic {
|
|||
Self::FoldingRange(..) => ContextFreeUnique,
|
||||
Self::SelectionRange(..) => ContextFreeUnique,
|
||||
Self::InteractCodeContext(..) => PinnedFirst,
|
||||
Self::ShowFullValue(..) => PinnedFirst,
|
||||
|
||||
Self::OnEnter(..) => ContextFreeUnique,
|
||||
|
||||
|
|
@ -343,6 +347,7 @@ mod polymorphic {
|
|||
Self::FoldingRange(req) => &req.path,
|
||||
Self::SelectionRange(req) => &req.path,
|
||||
Self::InteractCodeContext(req) => &req.path,
|
||||
Self::ShowFullValue(..) => return None, // No specific path needed
|
||||
|
||||
Self::OnEnter(req) => &req.path,
|
||||
|
||||
|
|
@ -408,6 +413,7 @@ mod polymorphic {
|
|||
SelectionRange(Option<Vec<SelectionRange>>),
|
||||
/// The response to the interact code context request.
|
||||
InteractCodeContext(Option<Vec<Option<InteractCodeContextResponse>>>),
|
||||
ShowFullValue(Option<String>),
|
||||
|
||||
/// The response to the on enter request.
|
||||
OnEnter(Option<Vec<TextEdit>>),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use serde_json::Value as JsonValue;
|
|||
use task::TraceParams;
|
||||
use tinymist_assets::TYPST_PREVIEW_HTML;
|
||||
use tinymist_query::package::PackageInfo;
|
||||
use tinymist_query::{LocalContextGuard, LspRange};
|
||||
use tinymist_query::{LocalContextGuard, LspPosition, LspRange};
|
||||
use tinymist_std::error::prelude::*;
|
||||
use typst::syntax::{LinkedNode, Source};
|
||||
|
||||
|
|
@ -34,6 +34,12 @@ struct ExportSyntaxRangeOpts {
|
|||
range: Option<LspRange>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ExportValueOpts {
|
||||
position: Option<LspPosition>,
|
||||
}
|
||||
|
||||
/// Here are implemented the handlers for each command.
|
||||
impl ServerState {
|
||||
/// Export a range of the current document as Ansi highlighted text.
|
||||
|
|
@ -72,6 +78,17 @@ impl ServerState {
|
|||
just_ok(JsonValue::String(output))
|
||||
}
|
||||
|
||||
/// Export the full tracked value at a specific position.
|
||||
pub fn export_value(&mut self, mut args: Vec<JsonValue>) -> AnySchedulableResponse {
|
||||
let path = get_arg!(args[0] as PathBuf);
|
||||
let opts = get_arg_or_default!(args[1] as ExportValueOpts);
|
||||
let position = opts
|
||||
.position
|
||||
.ok_or_else(|| internal_error("no position provided"))?;
|
||||
|
||||
run_query!(self.ShowFullValue(path, position))
|
||||
}
|
||||
|
||||
fn select_range<T>(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
|
|
|
|||
|
|
@ -304,6 +304,7 @@ impl ServerState {
|
|||
Symbol(req) => snap.run_semantic(req, R::Symbol),
|
||||
WorkspaceLabel(req) => snap.run_semantic(req, R::WorkspaceLabel),
|
||||
DocumentMetrics(req) => snap.run_semantic(req, R::DocumentMetrics),
|
||||
ShowFullValue(req) => snap.run_semantic(req, R::ShowFullValue),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -335,6 +335,7 @@ impl ServerState {
|
|||
.with_command_("tinymist.exportQuery", State::export_query)
|
||||
.with_command("tinymist.exportAnsiHighlight", State::export_ansi_hl)
|
||||
.with_command("tinymist.exportAst", State::export_ast)
|
||||
.with_command("tinymist.exportValue", State::export_value)
|
||||
.with_command("tinymist.doClearCache", State::clear_cache)
|
||||
.with_command("tinymist.pinMain", State::pin_document)
|
||||
.with_command("tinymist.focusMain", State::focus_document)
|
||||
|
|
|
|||
|
|
@ -1217,6 +1217,11 @@
|
|||
"title": "%extension.tinymist.command.tinymist.viewAst%",
|
||||
"category": "Typst"
|
||||
},
|
||||
{
|
||||
"command": "tinymist.showFullValue",
|
||||
"title": "%extension.tinymist.command.tinymist.showFullValue%",
|
||||
"category": "Typst"
|
||||
},
|
||||
{
|
||||
"command": "tinymist.showLog",
|
||||
"title": "%extension.tinymist.command.tinymist.showLog%",
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ async function languageActivate(context: IContext) {
|
|||
commands.registerCommand("tinymist.runCodeLens", commandRunCodeLens),
|
||||
commands.registerCommand("tinymist.copyAnsiHighlight", commandCopyAnsiHighlight),
|
||||
commands.registerCommand("tinymist.viewAst", commandViewAst(context)),
|
||||
commands.registerCommand("tinymist.showFullValue", commandShowFullValue(context)),
|
||||
|
||||
commands.registerCommand("tinymist.pinMainToCurrent", () => commandPinMain(true)),
|
||||
commands.registerCommand("tinymist.unpinMain", () => commandPinMain(false)),
|
||||
|
|
@ -371,6 +372,98 @@ function commandViewAst(ctx: IContext) {
|
|||
};
|
||||
}
|
||||
|
||||
function commandShowFullValue(ctx: IContext) {
|
||||
const scheme = "tinymist-full-value";
|
||||
const uri = `${scheme}://showFullValue/values.typ`;
|
||||
|
||||
const FullValueDoc = new (class implements vscode.TextDocumentContentProvider {
|
||||
readonly uri = vscode.Uri.parse(uri);
|
||||
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
|
||||
|
||||
constructor() {
|
||||
vscode.workspace.onDidChangeTextDocument(
|
||||
this.onDidChangeTextDocument,
|
||||
this,
|
||||
ctx.subscriptions,
|
||||
);
|
||||
vscode.window.onDidChangeActiveTextEditor(
|
||||
this.onDidChangeActiveTextEditor,
|
||||
this,
|
||||
ctx.subscriptions,
|
||||
);
|
||||
vscode.window.onDidChangeTextEditorSelection(
|
||||
this.onDidChangeTextSelection,
|
||||
this,
|
||||
ctx.subscriptions,
|
||||
);
|
||||
}
|
||||
|
||||
emitChange() {
|
||||
this.eventEmitter.fire(this.uri);
|
||||
}
|
||||
|
||||
private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
|
||||
if (isTypstDocument(event.document)) {
|
||||
// commandActivateDoc(event.document);
|
||||
// We need to order this after language server updates, but there's no API for that.
|
||||
// Hence, good old sleep().
|
||||
setTimeout(() => this.emitChange(), 10);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
|
||||
if (editor && isTypstDocument(editor.document)) {
|
||||
// commandActivateDoc(editor.document);
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeTextSelection(event: vscode.TextEditorSelectionChangeEvent) {
|
||||
if (isTypstDocument(event.textEditor.document)) {
|
||||
// commandActivateDoc(event.textEditor.document);
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
||||
|
||||
async provideTextDocumentContent(
|
||||
_uri: vscode.Uri,
|
||||
_ct: vscode.CancellationToken,
|
||||
): Promise<string> {
|
||||
const editor = ctx.currentActiveEditor();
|
||||
if (!editor) return "No active editor, change selection to view full value.";
|
||||
|
||||
try {
|
||||
const res = await tinymist.exportValue(editor.document.uri.fsPath, {
|
||||
position: (await tinymist.clientPromise).code2ProtocolConverter.asPosition(
|
||||
editor.selection.active,
|
||||
),
|
||||
});
|
||||
|
||||
return res || "No value found at this position.";
|
||||
} catch (error) {
|
||||
return `Error: ${error}`;
|
||||
}
|
||||
}
|
||||
|
||||
get onDidChange(): vscode.Event<vscode.Uri> {
|
||||
return this.eventEmitter.event;
|
||||
}
|
||||
})();
|
||||
|
||||
ctx.subscriptions.push(
|
||||
vscode.workspace.registerTextDocumentContentProvider(scheme, FullValueDoc),
|
||||
);
|
||||
|
||||
return async () => {
|
||||
const document = await vscode.workspace.openTextDocument(FullValueDoc.uri);
|
||||
setTimeout(() => FullValueDoc.emitChange(), 10);
|
||||
void (await vscode.window.showTextDocument(document, {
|
||||
viewColumn: vscode.ViewColumn.Two,
|
||||
preserveFocus: true,
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
async function commandClearCache(): Promise<void> {
|
||||
const activeEditor = window.activeTextEditor;
|
||||
if (activeEditor === undefined) {
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ export class LanguageState {
|
|||
};
|
||||
|
||||
const trustedCommands = {
|
||||
enabledCommands: ["tinymist.openInternal", "tinymist.openExternal"],
|
||||
enabledCommands: ["tinymist.openInternal", "tinymist.openExternal", "tinymist.showFullValue"],
|
||||
};
|
||||
const hoverStorage =
|
||||
extensionState.features.renderDocs && LanguageState.HoverTmpStorage
|
||||
|
|
@ -349,6 +349,7 @@ export class LanguageState {
|
|||
exportQuery = exportCommand("tinymist.exportQuery");
|
||||
exportAnsiHighlight = exportStringCommand("tinymist.exportAnsiHighlight");
|
||||
exportAst = exportStringCommand("tinymist.exportAst");
|
||||
exportValue = exportStringCommand("tinymist.exportValue");
|
||||
|
||||
getResource<T extends keyof ResourceRoutes>(path: T, ...args: any[]) {
|
||||
return tinymist.executeCommand<ResourceRoutes[T]>("tinymist.getResources", [path, ...args]);
|
||||
|
|
|
|||
|
|
@ -241,6 +241,10 @@ zh-TW = "複製為 ANSI 代碼"
|
|||
en = "View the AST of the current file"
|
||||
zh = "查看当前文件的 AST"
|
||||
|
||||
[extension.tinymist.command.tinymist.showFullValue]
|
||||
en = "View the full value of the expression under cursor"
|
||||
zh = "查看光标下表达式的完整值"
|
||||
|
||||
[extension.tinymist.command.tinymist.showLog]
|
||||
en = "Tinymist: Show Log"
|
||||
ar = "Tinymist: عرض السجل"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue