feat: allow to disable lint or lint on save (#1658)

* feat: allow to disable lint or lint on save

* fix: description
This commit is contained in:
Myriad-Dreamin 2025-04-16 03:15:56 +08:00 committed by GitHub
parent 39d13c83f6
commit 2709aaf429
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 140 additions and 33 deletions

View file

@ -12,7 +12,7 @@ use tinymist_analysis::stats::AllocStats;
use tinymist_analysis::ty::term_value;
use tinymist_analysis::{analyze_expr_, analyze_import_};
use tinymist_lint::LintInfo;
use tinymist_project::{LspComputeGraph, LspWorld};
use tinymist_project::{LspComputeGraph, LspWorld, TaskWhen};
use tinymist_std::hash::{hash128, FxDashMap};
use tinymist_std::typst::TypstDocument;
use tinymist_world::debug_loc::DataSource;
@ -69,6 +69,8 @@ pub struct Analysis {
pub completion_feat: CompletionFeat,
/// The editor's color theme.
pub color_theme: ColorTheme,
/// When to trigger the lint.
pub lint: TaskWhen,
/// The periscope provider.
pub periscope: Option<Arc<dyn PeriscopeProvider + Send + Sync>>,
/// The global worker resources for analysis.

View file

@ -42,21 +42,8 @@ impl ExportTimings {
timing: Option<TaskWhen>,
docs: Option<&D>,
) -> Option<bool> {
let s = snap.signal;
let when = timing.unwrap_or(TaskWhen::Never);
if !matches!(when, TaskWhen::Never) && s.by_entry_update {
return Some(true);
}
match when {
TaskWhen::Never => Some(false),
TaskWhen::OnType => Some(s.by_mem_events),
TaskWhen::OnSave => Some(s.by_fs_events),
TaskWhen::OnDocumentHasTitle if s.by_fs_events => {
docs.map(|doc| doc.info().title.is_some())
}
TaskWhen::OnDocumentHasTitle => Some(false),
}
snap.signal
.should_run_task(timing.unwrap_or_default(), docs)
}
}

View file

@ -2,7 +2,7 @@
use core::fmt;
use crate::{CompilerFeat, CompilerWorld, EntryReader, TaskInputs};
use crate::{args::TaskWhen, CompilerFeat, CompilerWorld, EntryReader, TaskInputs};
use ecow::EcoString;
use tinymist_std::typst::TypstDocument;
@ -43,6 +43,38 @@ impl ExportSignal {
self.by_fs_events |= other.by_fs_events;
self.by_entry_update |= other.by_entry_update;
}
pub fn should_run_task_dyn(
&self,
when: TaskWhen,
docs: Option<&TypstDocument>,
) -> Option<bool> {
match docs {
Some(TypstDocument::Paged(doc)) => self.should_run_task(when, Some(doc.as_ref())),
Some(TypstDocument::Html(doc)) => self.should_run_task(when, Some(doc.as_ref())),
None => self.should_run_task::<typst::layout::PagedDocument>(when, None),
}
}
pub fn should_run_task<D: typst::Document>(
&self,
when: TaskWhen,
docs: Option<&D>,
) -> Option<bool> {
if !matches!(when, TaskWhen::Never) && self.by_entry_update {
return Some(true);
}
match when {
TaskWhen::Never => Some(false),
TaskWhen::OnType => Some(self.by_mem_events),
TaskWhen::OnSave => Some(self.by_fs_events),
TaskWhen::OnDocumentHasTitle if self.by_fs_events => {
docs.map(|doc| doc.info().title.is_some())
}
TaskWhen::OnDocumentHasTitle => Some(false),
}
}
}
/// A snapshot of the project and compilation state.

View file

@ -90,6 +90,8 @@ pub struct Config {
pub completion: CompletionFeat,
/// Tinymist's preview features.
pub preview: PreviewFeat,
/// When to trigger the lint checks.
pub lint: LintFeat,
/// Specifies the cli font options
pub font_opts: CompileFontArgs,
@ -318,6 +320,7 @@ impl Config {
assign_config!(formatter_indent_size := "formatterIndentSize"?: Option<u32>);
assign_config!(output_path := "outputPath"?: PathPattern);
assign_config!(preview := "preview"?: PreviewFeat);
assign_config!(lint := "preview"?: LintFeat);
assign_config!(semantic_tokens := "semanticTokens"?: SemanticTokensMode);
assign_config!(support_html_in_markdown := "supportHtmlInMarkdown"?: bool);
assign_config!(system_fonts := "systemFonts"?: Option<bool>);
@ -788,6 +791,26 @@ pub struct PreviewFeat {
pub background: BackgroundPreviewOpts,
}
/// The lint features.
#[derive(Debug, Default, Clone, Deserialize)]
pub struct LintFeat {
/// Whether to enable linting.
pub enabled: Option<bool>,
/// When to trigger the lint checks.
pub when: Option<TaskWhen>,
}
impl LintFeat {
/// When to trigger the lint checks.
pub fn when(&self) -> TaskWhen {
if matches!(self.enabled, Some(false)) {
return TaskWhen::Never;
}
self.when.unwrap_or(TaskWhen::OnSave)
}
}
/// Options for browsing preview.
#[derive(Debug, Default, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]

View file

@ -161,6 +161,7 @@ impl ServerState {
Some("dark") => tinymist_query::ColorTheme::Dark,
_ => tinymist_query::ColorTheme::Light,
},
lint: config.lint.when(),
periscope: periscope_args.map(|args| {
let r = TypstPeriscopeProvider(PeriscopeRenderer::new(args));
Arc::new(r) as Arc<dyn PeriscopeProvider + Send + Sync>
@ -429,36 +430,54 @@ impl CompileHandlerImpl {
.log_error("failed to send diagnostics");
}
fn notify_diagnostics(&self, snap: &LspCompiledArtifact) {
fn notify_diagnostics(&self, art: &LspCompiledArtifact) {
let dv = ProjVersion {
id: snap.id().clone(),
revision: snap.world().revision().get(),
id: art.id().clone(),
revision: art.world().revision().get(),
};
// todo: better way to remove diagnostics
let valid = !snap.world().entry_state().is_inactive();
let valid = !art.world().entry_state().is_inactive();
if !valid {
self.push_diagnostics(dv, None);
return;
}
let snap = snap.clone();
let editor_tx = self.editor_tx.clone();
let analysis = self.analysis.clone();
rayon::spawn(move || {
let world = snap.world().clone();
let mut ctx = analysis.enter(world);
let should_lint = art
.snap
.signal
.should_run_task_dyn(self.analysis.lint, art.doc.as_ref())
.unwrap_or_default();
// todo: check all errors in this file
let Some(diagnostics) = CheckRequest { snap }.request(&mut ctx) else {
return;
};
if !should_lint {
let enc = self.analysis.position_encoding;
let diagnostics =
tinymist_query::convert_diagnostics(art.world(), art.diagnostics(), enc);
log::trace!("notify diagnostics({dv:?}): {diagnostics:#?}");
editor_tx
self.editor_tx
.send(EditorRequest::Diag(dv, Some(diagnostics)))
.log_error("failed to send diagnostics");
});
} else {
let snap = art.clone();
let editor_tx = self.editor_tx.clone();
let analysis = self.analysis.clone();
rayon::spawn(move || {
let world = snap.world().clone();
let mut ctx = analysis.enter(world);
// todo: check all errors in this file
let Some(diagnostics) = CheckRequest { snap }.request(&mut ctx) else {
return;
};
log::trace!("notify diagnostics({dv:?}): {diagnostics:#?}");
editor_tx
.send(EditorRequest::Diag(dv, Some(diagnostics)))
.log_error("failed to send diagnostics");
});
}
}
}