tinymist/tool/
project.rs

1//! Project management tools.
2
3use std::sync::Arc;
4
5use parking_lot::Mutex;
6use tinymist_query::analysis::Analysis;
7use tokio::sync::mpsc;
8
9use crate::project::*;
10use crate::{actor::editor::EditorRequest, Config};
11
12/// Options for starting a project.
13#[derive(Default)]
14pub struct ProjectOpts {
15    /// The tokio runtime handle.
16    pub handle: Option<tokio::runtime::Handle>,
17    /// The shared preview state.
18    pub analysis: Arc<Analysis>,
19    /// The shared config.
20    pub config: Config,
21    /// The shared preview state.
22    #[cfg(feature = "preview")]
23    pub preview: ProjectPreviewState,
24    /// The export target.
25    pub export_target: ExportTarget,
26}
27
28/// Result of starting a project.
29pub struct StartProjectResult<F> {
30    /// A future service that runs the project.
31    pub service: WatchService<F>,
32    /// The interrupt sender.
33    pub intr_tx: mpsc::UnboundedSender<LspInterrupt>,
34    /// The editor request receiver.
35    pub editor_rx: mpsc::UnboundedReceiver<EditorRequest>,
36}
37
38// todo: This is only extracted from the `tinymist preview` command, and we need
39// to abstract it in future.
40/// Starts a project with the given universe.
41pub fn start_project<F>(
42    verse: LspUniverse,
43    opts: Option<ProjectOpts>,
44    intr_handler: F,
45) -> StartProjectResult<F>
46where
47    F: FnMut(
48        &mut LspProjectCompiler,
49        Interrupt<LspCompilerFeat>,
50        fn(&mut LspProjectCompiler, Interrupt<LspCompilerFeat>),
51    ),
52{
53    let opts = opts.unwrap_or_default();
54    #[cfg(any(feature = "export", feature = "system"))]
55    let handle = opts.handle.unwrap_or_else(tokio::runtime::Handle::current);
56
57    let _ = opts.config;
58
59    // type EditorSender = mpsc::UnboundedSender<EditorRequest>;
60    let (editor_tx, editor_rx) = mpsc::unbounded_channel();
61    let (intr_tx, intr_rx) = tokio::sync::mpsc::unbounded_channel();
62
63    // todo: unify filesystem watcher
64    let (dep_tx, dep_rx) = mpsc::unbounded_channel();
65    // todo: notify feature?
66    #[cfg(feature = "system")]
67    {
68        let fs_intr_tx = intr_tx.clone();
69        handle.spawn(watch_deps(dep_rx, move |event| {
70            fs_intr_tx.interrupt(LspInterrupt::Fs(event));
71        }));
72    }
73    #[cfg(not(feature = "system"))]
74    {
75        let _ = dep_rx;
76        log::warn!("Project: system watcher is not enabled, file changes will not be watched");
77    }
78
79    // Create the actor
80    let compile_handle = Arc::new(CompileHandlerImpl {
81        #[cfg(feature = "preview")]
82        preview: opts.preview,
83        is_standalone: true,
84        #[cfg(feature = "export")]
85        export: crate::task::ExportTask::new(handle, Some(editor_tx.clone()), opts.config.export()),
86        editor_tx,
87        client: Arc::new(intr_tx.clone()),
88
89        analysis: opts.analysis,
90        status_revision: Mutex::default(),
91        notified_revision: Mutex::default(),
92    });
93
94    let mut compiler = ProjectCompiler::new(
95        verse,
96        dep_tx,
97        CompileServerOpts {
98            handler: compile_handle,
99            export_target: opts.export_target,
100            syntax_only: opts.config.syntax_only,
101            ignore_first_sync: true,
102        },
103    );
104
105    compiler.primary.reason.by_entry_update = true;
106
107    StartProjectResult {
108        service: WatchService {
109            compiler,
110            intr_rx,
111            intr_handler,
112        },
113        intr_tx,
114        editor_rx,
115    }
116}
117
118/// A service that watches for project changes and compiles them.
119pub struct WatchService<F> {
120    /// The project compiler.
121    pub compiler: LspProjectCompiler,
122    intr_rx: tokio::sync::mpsc::UnboundedReceiver<LspInterrupt>,
123    intr_handler: F,
124}
125
126impl<F> WatchService<F>
127where
128    F: FnMut(
129            &mut LspProjectCompiler,
130            Interrupt<LspCompilerFeat>,
131            fn(&mut LspProjectCompiler, Interrupt<LspCompilerFeat>),
132        ) + Send
133        + 'static,
134{
135    /// Runs the project service.
136    pub async fn run(self) {
137        let Self {
138            mut compiler,
139            mut intr_rx,
140            mut intr_handler,
141        } = self;
142
143        let handler = compiler.handler.clone();
144        handler.on_any_compile_reason(&mut compiler);
145
146        while let Some(intr) = intr_rx.recv().await {
147            log::debug!("Project compiler received: {intr:?}");
148            intr_handler(&mut compiler, intr, ProjectState::do_interrupt);
149        }
150
151        log::info!("Project compiler exited");
152    }
153}