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            ignore_first_sync: true,
101        },
102    );
103
104    compiler.primary.reason.by_entry_update = true;
105
106    StartProjectResult {
107        service: WatchService {
108            compiler,
109            intr_rx,
110            intr_handler,
111        },
112        intr_tx,
113        editor_rx,
114    }
115}
116
117/// A service that watches for project changes and compiles them.
118pub struct WatchService<F> {
119    /// The project compiler.
120    pub compiler: LspProjectCompiler,
121    intr_rx: tokio::sync::mpsc::UnboundedReceiver<LspInterrupt>,
122    intr_handler: F,
123}
124
125impl<F> WatchService<F>
126where
127    F: FnMut(
128            &mut LspProjectCompiler,
129            Interrupt<LspCompilerFeat>,
130            fn(&mut LspProjectCompiler, Interrupt<LspCompilerFeat>),
131        ) + Send
132        + 'static,
133{
134    /// Runs the project service.
135    pub async fn run(self) {
136        let Self {
137            mut compiler,
138            mut intr_rx,
139            mut intr_handler,
140        } = self;
141
142        let handler = compiler.handler.clone();
143        handler.on_any_compile_reason(&mut compiler);
144
145        while let Some(intr) = intr_rx.recv().await {
146            log::debug!("Project compiler received: {intr:?}");
147            intr_handler(&mut compiler, intr, ProjectState::do_interrupt);
148        }
149
150        log::info!("Project compiler exited");
151    }
152}