feat: init log in web (#2068)

This commit is contained in:
Myriad-Dreamin 2025-08-24 08:45:51 +08:00 committed by GitHub
parent 5ee01b59a9
commit d85717f81e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 150 additions and 35 deletions

View file

@ -132,28 +132,12 @@ fn main() -> Result<()> {
// Loads translations
#[cfg(feature = "l10n")]
set_translations(load_translations(tinymist_assets::L10N_DATA)?);
// Starts logging
let _ = {
use log::LevelFilter::*;
let is_transient_cmd = matches!(cmd, Commands::Compile(..));
let is_test_no_verbose = matches!(&cmd, Commands::Test(test) if !test.verbose);
let base_no_info = is_transient_cmd || is_test_no_verbose;
let base_level = if base_no_info { Warn } else { Info };
let preview_level = if is_test_no_verbose { Warn } else { Debug };
let diag_level = if is_test_no_verbose { Warn } else { Info };
env_logger::builder()
.filter_module("tinymist", base_level)
.filter_module("tinymist_preview", preview_level)
.filter_module("typlite", base_level)
.filter_module("reflexo", base_level)
.filter_module("sync_ls", base_level)
.filter_module("reflexo_typst2vec::pass::span2vec", Error)
.filter_module("reflexo_typst::diag::console", diag_level)
.try_init()
};
let _ = tinymist::init_log(tinymist::InitLogOpts {
is_transient_cmd: matches!(cmd, Commands::Compile(..)),
is_test_no_verbose: matches!(&cmd, Commands::Test(test) if !test.verbose),
output: None,
});
match cmd {
Commands::Probe => Ok(()),

View file

@ -77,7 +77,7 @@ impl SemanticRequest for CodeActionRequest {
type Response = Vec<CodeAction>;
fn request(self, ctx: &mut LocalContext) -> Option<Self::Response> {
log::info!("requested code action: {self:?}");
log::trace!("requested code action: {self:?}");
let source = ctx.source_by_path(&self.path).ok()?;
let range = ctx.to_typst_range(self.range, &source)?;

View file

@ -29,7 +29,7 @@ serde_json.workspace = true
serde_with.workspace = true
siphasher.workspace = true
web-time = { workspace = true, optional = true }
time.workspace = true
time = { workspace = true, features = ["formatting"] }
lsp-types.workspace = true
tempfile = { workspace = true, optional = true }
same-file = { workspace = true, optional = true }

View file

@ -33,6 +33,8 @@ pub fn now() -> Time {
Time::UNIX_EPOCH
}
pub use time::format_description::well_known::Rfc3339;
/// The trait helping convert to a [`UtcDateTime`].
pub trait ToUtcDateTime {
/// Converts to a [`UtcDateTime`].

View file

@ -2,7 +2,7 @@
name = "tinymist"
description = "An integrated language service for Typst."
categories = ["compilers", "command-line-utilities"]
keywords = ["api", "language", "typst"]
keywords = ["api", "lsp", "language", "typst"]
authors.workspace = true
version.workspace = true
license.workspace = true

View file

@ -1,6 +1,25 @@
//! Tinymist Core Library
//! # tinymist
//!
//! This crate provides a CLI that starts services for [Typst](https://typst.app/). It provides:
//! + `tinymist lsp`: A language server following the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/).
//! + `tinymist preview`: A preview server for Typst.
//!
//! ## Usage
//!
//! See [Features: Command Line Interface](https://myriad-dreamin.github.io/tinymist/feature/cli.html).
//!
//! ## Documentation
//!
//! See [Crate Docs](https://myriad-dreamin.github.io/tinymist/rs/tinymist/index.html).
//!
//! Also see [Developer Guide: Tinymist LSP](https://myriad-dreamin.github.io/tinymist/module/lsp.html).
//!
//! ## Contributing
//!
//! See [CONTRIBUTING.md](https://github.com/Myriad-Dreamin/tinymist/blob/main/CONTRIBUTING.md).
pub use config::*;
pub use log_::*;
pub use lsp::init::*;
pub use server::*;
pub use sync_ls::LspClient;
@ -23,22 +42,27 @@ pub use dap::SuperInit as DapSuperInit;
pub mod project;
pub mod tool;
pub(crate) mod config;
#[cfg(feature = "dap")]
pub(crate) mod dap;
pub(crate) mod input;
pub(crate) mod lsp;
#[cfg(feature = "lock")]
pub(crate) mod route;
#[cfg(feature = "web")]
pub mod web;
mod actor;
mod cmd;
mod config;
mod input;
#[path = "log.rs"]
mod log_;
mod lsp;
mod resource;
mod server;
mod stats;
mod task;
mod utils;
#[cfg(feature = "dap")]
mod dap;
#[cfg(feature = "lock")]
mod route;
use std::sync::LazyLock;
use lsp::query::QueryFuture;
@ -70,6 +94,3 @@ Typst Source: {}
env!("TYPST_SOURCE"),
)
});
#[cfg(feature = "web")]
pub mod web;

108
crates/tinymist/src/log.rs Normal file
View file

@ -0,0 +1,108 @@
//! Logging Functionality
use serde::{Deserialize, Serialize};
use super::*;
/// Options for initializing the logger.
pub struct InitLogOpts {
/// Whether the command is transient (e.g., compile).
pub is_transient_cmd: bool,
/// Whether the command is a test without verbose output.
pub is_test_no_verbose: bool,
/// Redirects output via LSP/DAP notification.
pub output: Option<LspClient>,
}
/// Initializes the logger for the Tinymist library.
pub fn init_log(
InitLogOpts {
is_transient_cmd,
is_test_no_verbose,
output,
}: InitLogOpts,
) -> anyhow::Result<()> {
use log::LevelFilter::*;
let base_no_info = is_transient_cmd || is_test_no_verbose;
let base_level = if base_no_info { Warn } else { Info };
let preview_level = if is_test_no_verbose { Warn } else { Debug };
let diag_level = if is_test_no_verbose { Warn } else { Info };
let mut builder = env_logger::builder();
if let Some(output) = output {
builder.target(LogNotification::create(output));
}
// In WebAssembly, we use a custom notification for logging.
#[cfg(target_arch = "wasm32")]
{
builder.format(|f, record| {
use std::io::Write;
let ts = tinymist_std::time::utc_now();
write!(f, "[")?;
ts.format_into(f, &tinymist_std::time::Rfc3339)
.map_err(std::io::Error::other)?;
writeln!(
f,
" {level:<5} {module_path} {file_path}:{line} {target}] {args}",
level = record.level(),
module_path = record.module_path().unwrap_or("unknown"),
file_path = record.file().unwrap_or("unknown"),
line = record.line().unwrap_or(0),
target = record.target(),
args = record.args()
)
});
}
Ok(builder
.filter_module("tinymist", base_level)
.filter_module("tinymist_preview", preview_level)
.filter_module("typlite", base_level)
.filter_module("reflexo", base_level)
.filter_module("sync_ls", base_level)
.filter_module("reflexo_typst2vec::pass::span2vec", Error)
.filter_module("reflexo_typst::diag::console", diag_level)
.try_init()?)
}
struct LogNotification(LspClient, Vec<u8>);
impl LogNotification {
/// Creates a new `LogNotification` with the given LSP client and an empty
/// buffer.
fn create(output: LspClient) -> env_logger::Target {
env_logger::Target::Pipe(Box::new(Self(output, vec![])))
}
}
impl std::io::Write for LogNotification {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
self.1.extend_from_slice(buf);
Ok(buf.len())
}
// todo: the from_utf8_lossy may break non-ascii characters and inefficient
fn flush(&mut self) -> std::io::Result<()> {
let data = String::from_utf8_lossy(self.1.as_slice()).to_string();
self.1.clear();
self.0.send_notification::<Log>(&Log { data });
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Log {
data: String,
}
impl lsp_types::notification::Notification for Log {
const METHOD: &'static str = "tmLog";
type Params = Self;
}