mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-26 13:53:01 +00:00
refactor: rearrange state methods (#1252)
This commit is contained in:
parent
4ce405a89b
commit
5069a89d5d
9 changed files with 445 additions and 453 deletions
|
|
@ -330,7 +330,7 @@ pub mod prelude {
|
|||
use crate::Error;
|
||||
|
||||
pub use super::{IgnoreLogging, WithContext, WithContextUntyped};
|
||||
pub use crate::Result;
|
||||
pub use crate::{bail, Result};
|
||||
|
||||
pub fn map_string_err<T: ToString>(loc: &'static str) -> impl Fn(T) -> Error {
|
||||
move |e| Error::new(loc, e.to_string().to_error_kind(), None)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use world::TaskInputs;
|
|||
|
||||
use super::state::*;
|
||||
use super::*;
|
||||
use crate::state::query::{run_query, LspClientExt};
|
||||
use crate::state::lsp_query::{run_query, LspClientExt};
|
||||
use crate::tool::package::InitTask;
|
||||
|
||||
/// See [`ProjectTask`].
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ pub use world::{CompileFontArgs, CompileOnceArgs, CompilePackageArgs};
|
|||
|
||||
use lsp_server::ResponseError;
|
||||
use serde_json::from_value;
|
||||
use state::query::QueryFuture;
|
||||
use state::lsp_query::QueryFuture;
|
||||
use sync_lsp::*;
|
||||
use tinymist_std::error::Result;
|
||||
use utils::*;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ mod server;
|
|||
pub use server::*;
|
||||
|
||||
pub(crate) mod input;
|
||||
pub(crate) mod lsp;
|
||||
pub(crate) mod lsp_query;
|
||||
pub mod project;
|
||||
pub(crate) mod protocol;
|
||||
pub(crate) mod query;
|
||||
pub(crate) mod route;
|
||||
|
|
|
|||
|
|
@ -1,145 +1,17 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use lsp_types::request::WorkspaceConfiguration;
|
||||
use lsp_types::*;
|
||||
use once_cell::sync::OnceCell;
|
||||
use reflexo_typst::Bytes;
|
||||
use serde_json::{Map, Value as JsonValue};
|
||||
use sync_lsp::*;
|
||||
use tinymist_project::{Interrupt, ProjectResolutionKind};
|
||||
use tinymist_query::{to_typst_range, PositionEncoding};
|
||||
use tinymist_std::error::{prelude::*, IgnoreLogging};
|
||||
use tinymist_std::error::prelude::*;
|
||||
use tinymist_std::ImmutPath;
|
||||
use typst::{diag::FileResult, syntax::Source};
|
||||
|
||||
use crate::route::ProjectResolution;
|
||||
use crate::task::FormatterConfig;
|
||||
use crate::world::vfs::{notify::MemoryEvent, FileChangeSet};
|
||||
use crate::world::TaskInputs;
|
||||
use crate::{init::*, *};
|
||||
|
||||
/// LSP Document Synchronization
|
||||
impl ServerState {
|
||||
pub(crate) fn did_open(&mut self, params: DidOpenTextDocumentParams) -> LspResult<()> {
|
||||
log::info!("did open {:?}", params.text_document.uri);
|
||||
let path = as_path_(params.text_document.uri);
|
||||
let text = params.text_document.text;
|
||||
|
||||
self.create_source(path.clone(), text)
|
||||
.map_err(|e| invalid_params(e.to_string()))?;
|
||||
|
||||
// Focus after opening
|
||||
self.implicit_focus_entry(|| Some(path.as_path().into()), 'o');
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn did_close(&mut self, params: DidCloseTextDocumentParams) -> LspResult<()> {
|
||||
let path = as_path_(params.text_document.uri);
|
||||
|
||||
self.remove_source(path.clone())
|
||||
.map_err(|e| invalid_params(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn did_change(&mut self, params: DidChangeTextDocumentParams) -> LspResult<()> {
|
||||
let path = as_path_(params.text_document.uri);
|
||||
let changes = params.content_changes;
|
||||
|
||||
self.edit_source(path.clone(), changes, self.const_config().position_encoding)
|
||||
.map_err(|e| invalid_params(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn did_save(&mut self, _params: DidSaveTextDocumentParams) -> LspResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// LSP Configuration Synchronization
|
||||
impl ServerState {
|
||||
pub(crate) fn on_changed_configuration(
|
||||
&mut self,
|
||||
values: Map<String, JsonValue>,
|
||||
) -> LspResult<()> {
|
||||
let old_config = self.config.clone();
|
||||
match self.config.update_by_map(&values) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
self.config = old_config;
|
||||
log::error!("error applying new settings: {err}");
|
||||
return Err(invalid_params(format!(
|
||||
"error applying new settings: {err}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let new_export_config = self.config.export();
|
||||
if old_config.export() != new_export_config {
|
||||
self.change_export_config(new_export_config);
|
||||
}
|
||||
|
||||
if old_config.compile.primary_opts() != self.config.compile.primary_opts() {
|
||||
self.config.compile.fonts = OnceCell::new(); // todo: don't reload fonts if not changed
|
||||
self.restart_primary()
|
||||
.log_error("could not restart primary");
|
||||
}
|
||||
|
||||
if old_config.semantic_tokens != self.config.semantic_tokens {
|
||||
self.enable_sema_token_caps(self.config.semantic_tokens == SemanticTokensMode::Enable)
|
||||
.log_error("could not change semantic tokens config");
|
||||
}
|
||||
|
||||
let new_formatter_config = self.config.formatter();
|
||||
if !old_config.formatter().eq(&new_formatter_config) {
|
||||
let enabled = !matches!(new_formatter_config.config, FormatterConfig::Disable);
|
||||
self.enable_formatter_caps(enabled)
|
||||
.log_error("could not change formatter config");
|
||||
|
||||
self.formatter.change_config(new_formatter_config);
|
||||
}
|
||||
|
||||
log::info!("new settings applied");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn did_change_configuration(
|
||||
&mut self,
|
||||
params: DidChangeConfigurationParams,
|
||||
) -> LspResult<()> {
|
||||
// For some clients, we don't get the actual changed configuration and need to
|
||||
// poll for it https://github.com/microsoft/language-server-protocol/issues/676
|
||||
if let JsonValue::Object(settings) = params.settings {
|
||||
return self.on_changed_configuration(settings);
|
||||
};
|
||||
|
||||
self.client.send_request::<WorkspaceConfiguration>(
|
||||
ConfigurationParams {
|
||||
items: Config::get_items(),
|
||||
},
|
||||
Self::workspace_configuration_callback,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn workspace_configuration_callback(this: &mut ServerState, resp: lsp_server::Response) {
|
||||
if let Some(err) = resp.error {
|
||||
log::error!("failed to request configuration: {err:?}");
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(result) = resp.result else {
|
||||
log::error!("no configuration returned");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(resp) = serde_json::from_value::<Vec<JsonValue>>(result)
|
||||
.log_error("could not parse configuration")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let _ = this.on_changed_configuration(Config::values_to_map(resp));
|
||||
}
|
||||
}
|
||||
use crate::*;
|
||||
|
||||
/// In memory source file management.
|
||||
impl ServerState {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,204 @@
|
|||
use lsp_types::request::WorkspaceConfiguration;
|
||||
use lsp_types::*;
|
||||
use once_cell::sync::OnceCell;
|
||||
use request::{RegisterCapability, UnregisterCapability};
|
||||
use serde_json::{Map, Value as JsonValue};
|
||||
use sync_lsp::*;
|
||||
use tinymist_std::error::{prelude::*, IgnoreLogging};
|
||||
|
||||
use crate::task::FormatterConfig;
|
||||
use crate::{init::*, *};
|
||||
|
||||
/// Trait implemented by language server backends.
|
||||
///
|
||||
/// This interface allows servers adhering to the [Language Server Protocol] to
|
||||
/// be implemented in a safe and easily testable way without exposing the
|
||||
/// low-level implementation details.
|
||||
///
|
||||
/// [Language Server Protocol]: https://microsoft.github.io/language-server-protocol/
|
||||
impl ServerState {
|
||||
/// The [`initialized`] notification is sent from the client to the server
|
||||
/// after the client received the result of the initialize request but
|
||||
/// before the client sends anything else.
|
||||
///
|
||||
/// [`initialized`]: https://microsoft.github.io/language-server-protocol/specification#initialized
|
||||
///
|
||||
/// The server can use the `initialized` notification, for example, to
|
||||
/// dynamically register capabilities with the client.
|
||||
pub(crate) fn initialized(&mut self, _params: InitializedParams) -> LspResult<()> {
|
||||
if self.const_config().tokens_dynamic_registration
|
||||
&& self.config.semantic_tokens == SemanticTokensMode::Enable
|
||||
{
|
||||
self.enable_sema_token_caps(true)
|
||||
.log_error("could not register semantic tokens for initialization");
|
||||
}
|
||||
|
||||
if self.const_config().doc_fmt_dynamic_registration
|
||||
&& self.config.formatter_mode != FormatterMode::Disable
|
||||
{
|
||||
self.enable_formatter_caps(true)
|
||||
.log_error("could not register formatter for initialization");
|
||||
}
|
||||
|
||||
if self.const_config().cfg_change_registration {
|
||||
log::trace!("setting up to request config change notifications");
|
||||
|
||||
const CONFIG_REGISTRATION_ID: &str = "config";
|
||||
const CONFIG_METHOD_ID: &str = "workspace/didChangeConfiguration";
|
||||
|
||||
self.register_capability(vec![Registration {
|
||||
id: CONFIG_REGISTRATION_ID.to_owned(),
|
||||
method: CONFIG_METHOD_ID.to_owned(),
|
||||
register_options: None,
|
||||
}])
|
||||
.log_error("could not register to watch config changes");
|
||||
}
|
||||
|
||||
log::info!("server initialized");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The [`shutdown`] request asks the server to gracefully shut down, but to
|
||||
/// not exit.
|
||||
///
|
||||
/// [`shutdown`]: https://microsoft.github.io/language-server-protocol/specification#shutdown
|
||||
///
|
||||
/// This request is often later followed by an [`exit`] notification, which
|
||||
/// will cause the server to exit immediately.
|
||||
///
|
||||
/// [`exit`]: https://microsoft.github.io/language-server-protocol/specification#exit
|
||||
///
|
||||
/// This method is guaranteed to only execute once. If the client sends this
|
||||
/// request to the server again, the server will respond with JSON-RPC
|
||||
/// error code `-32600` (invalid request).
|
||||
pub(crate) fn shutdown(&mut self, _params: ()) -> SchedulableResponse<()> {
|
||||
just_ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// LSP Document Synchronization
|
||||
impl ServerState {
|
||||
pub(crate) fn did_open(&mut self, params: DidOpenTextDocumentParams) -> LspResult<()> {
|
||||
log::info!("did open {:?}", params.text_document.uri);
|
||||
let path = as_path_(params.text_document.uri);
|
||||
let text = params.text_document.text;
|
||||
|
||||
self.create_source(path.clone(), text)
|
||||
.map_err(|e| invalid_params(e.to_string()))?;
|
||||
|
||||
// Focus after opening
|
||||
self.implicit_focus_entry(|| Some(path.as_path().into()), 'o');
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn did_close(&mut self, params: DidCloseTextDocumentParams) -> LspResult<()> {
|
||||
let path = as_path_(params.text_document.uri);
|
||||
|
||||
self.remove_source(path.clone())
|
||||
.map_err(|e| invalid_params(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn did_change(&mut self, params: DidChangeTextDocumentParams) -> LspResult<()> {
|
||||
let path = as_path_(params.text_document.uri);
|
||||
let changes = params.content_changes;
|
||||
|
||||
self.edit_source(path.clone(), changes, self.const_config().position_encoding)
|
||||
.map_err(|e| invalid_params(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn did_save(&mut self, _params: DidSaveTextDocumentParams) -> LspResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// LSP Configuration Synchronization
|
||||
impl ServerState {
|
||||
pub(crate) fn on_changed_configuration(
|
||||
&mut self,
|
||||
values: Map<String, JsonValue>,
|
||||
) -> LspResult<()> {
|
||||
let old_config = self.config.clone();
|
||||
match self.config.update_by_map(&values) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
self.config = old_config;
|
||||
log::error!("error applying new settings: {err}");
|
||||
return Err(invalid_params(format!(
|
||||
"error applying new settings: {err}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let new_export_config = self.config.export();
|
||||
if old_config.export() != new_export_config {
|
||||
self.change_export_config(new_export_config);
|
||||
}
|
||||
|
||||
if old_config.compile.primary_opts() != self.config.compile.primary_opts() {
|
||||
self.config.compile.fonts = OnceCell::new(); // todo: don't reload fonts if not changed
|
||||
self.restart_primary()
|
||||
.log_error("could not restart primary");
|
||||
}
|
||||
|
||||
if old_config.semantic_tokens != self.config.semantic_tokens {
|
||||
self.enable_sema_token_caps(self.config.semantic_tokens == SemanticTokensMode::Enable)
|
||||
.log_error("could not change semantic tokens config");
|
||||
}
|
||||
|
||||
let new_formatter_config = self.config.formatter();
|
||||
if !old_config.formatter().eq(&new_formatter_config) {
|
||||
let enabled = !matches!(new_formatter_config.config, FormatterConfig::Disable);
|
||||
self.enable_formatter_caps(enabled)
|
||||
.log_error("could not change formatter config");
|
||||
|
||||
self.formatter.change_config(new_formatter_config);
|
||||
}
|
||||
|
||||
log::info!("new settings applied");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn did_change_configuration(
|
||||
&mut self,
|
||||
params: DidChangeConfigurationParams,
|
||||
) -> LspResult<()> {
|
||||
// For some clients, we don't get the actual changed configuration and need to
|
||||
// poll for it https://github.com/microsoft/language-server-protocol/issues/676
|
||||
if let JsonValue::Object(settings) = params.settings {
|
||||
return self.on_changed_configuration(settings);
|
||||
};
|
||||
|
||||
self.client.send_request::<WorkspaceConfiguration>(
|
||||
ConfigurationParams {
|
||||
items: Config::get_items(),
|
||||
},
|
||||
Self::workspace_configuration_callback,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn workspace_configuration_callback(this: &mut ServerState, resp: lsp_server::Response) {
|
||||
if let Some(err) = resp.error {
|
||||
log::error!("failed to request configuration: {err:?}");
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(result) = resp.result else {
|
||||
log::error!("no configuration returned");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(resp) = serde_json::from_value::<Vec<JsonValue>>(result)
|
||||
.log_error("could not parse configuration")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let _ = this.on_changed_configuration(Config::values_to_map(resp));
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerState {
|
||||
// todo: handle error
|
||||
pub(crate) fn register_capability(&self, registrations: Vec<Registration>) -> Result<()> {
|
||||
|
|
@ -122,70 +316,3 @@ impl ServerState {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait implemented by language server backends.
|
||||
///
|
||||
/// This interface allows servers adhering to the [Language Server Protocol] to
|
||||
/// be implemented in a safe and easily testable way without exposing the
|
||||
/// low-level implementation details.
|
||||
///
|
||||
/// [Language Server Protocol]: https://microsoft.github.io/language-server-protocol/
|
||||
impl ServerState {
|
||||
/// The [`initialized`] notification is sent from the client to the server
|
||||
/// after the client received the result of the initialize request but
|
||||
/// before the client sends anything else.
|
||||
///
|
||||
/// [`initialized`]: https://microsoft.github.io/language-server-protocol/specification#initialized
|
||||
///
|
||||
/// The server can use the `initialized` notification, for example, to
|
||||
/// dynamically register capabilities with the client.
|
||||
pub(crate) fn initialized(&mut self, _params: InitializedParams) -> LspResult<()> {
|
||||
if self.const_config().tokens_dynamic_registration
|
||||
&& self.config.semantic_tokens == SemanticTokensMode::Enable
|
||||
{
|
||||
self.enable_sema_token_caps(true)
|
||||
.log_error("could not register semantic tokens for initialization");
|
||||
}
|
||||
|
||||
if self.const_config().doc_fmt_dynamic_registration
|
||||
&& self.config.formatter_mode != FormatterMode::Disable
|
||||
{
|
||||
self.enable_formatter_caps(true)
|
||||
.log_error("could not register formatter for initialization");
|
||||
}
|
||||
|
||||
if self.const_config().cfg_change_registration {
|
||||
log::trace!("setting up to request config change notifications");
|
||||
|
||||
const CONFIG_REGISTRATION_ID: &str = "config";
|
||||
const CONFIG_METHOD_ID: &str = "workspace/didChangeConfiguration";
|
||||
|
||||
self.register_capability(vec![Registration {
|
||||
id: CONFIG_REGISTRATION_ID.to_owned(),
|
||||
method: CONFIG_METHOD_ID.to_owned(),
|
||||
register_options: None,
|
||||
}])
|
||||
.log_error("could not register to watch config changes");
|
||||
}
|
||||
|
||||
log::info!("server initialized");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The [`shutdown`] request asks the server to gracefully shut down, but to
|
||||
/// not exit.
|
||||
///
|
||||
/// [`shutdown`]: https://microsoft.github.io/language-server-protocol/specification#shutdown
|
||||
///
|
||||
/// This request is often later followed by an [`exit`] notification, which
|
||||
/// will cause the server to exit immediately.
|
||||
///
|
||||
/// [`exit`]: https://microsoft.github.io/language-server-protocol/specification#exit
|
||||
///
|
||||
/// This method is guaranteed to only execute once. If the client sends this
|
||||
/// request to the server again, the server will respond with JSON-RPC
|
||||
/// error code `-32600` (invalid request).
|
||||
pub(crate) fn shutdown(&mut self, _params: ()) -> SchedulableResponse<()> {
|
||||
just_ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ macro_rules! run_query {
|
|||
}
|
||||
pub(crate) use run_query;
|
||||
|
||||
/// Standard Language Features
|
||||
/// LSP Standard Language Features
|
||||
impl ServerState {
|
||||
pub(crate) fn goto_definition(
|
||||
&mut self,
|
||||
|
|
@ -26,26 +26,212 @@ use std::sync::Arc;
|
|||
use parking_lot::Mutex;
|
||||
use reflexo::{hash::FxHashMap, path::unix_slash};
|
||||
use reflexo_typst::CompileReport;
|
||||
use sync_lsp::LspClient;
|
||||
use sync_lsp::{LspClient, TypedLspClient};
|
||||
use tinymist_project::vfs::{FileChangeSet, MemoryEvent};
|
||||
use tinymist_query::{
|
||||
analysis::{Analysis, AnalysisRevLock, LocalContextGuard},
|
||||
CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, SemanticRequest, StatefulRequest,
|
||||
VersionedDocument,
|
||||
};
|
||||
use tinymist_std::{
|
||||
bail,
|
||||
error::{prelude::*, IgnoreLogging},
|
||||
analysis::{Analysis, AnalysisRevLock, LocalContextGuard, PeriscopeProvider},
|
||||
CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, LocalContext, SemanticRequest,
|
||||
StatefulRequest, VersionedDocument,
|
||||
};
|
||||
use tinymist_render::PeriscopeRenderer;
|
||||
use tinymist_std::{error::prelude::*, ImmutPath};
|
||||
use tokio::sync::mpsc;
|
||||
use typst::{diag::FileResult, foundations::Bytes, layout::Position as TypstPosition};
|
||||
|
||||
use super::ServerState;
|
||||
use crate::actor::editor::{CompileStatus, CompileStatusEnum, EditorRequest, ProjVersion};
|
||||
use crate::stats::{CompilerQueryStats, QueryStatGuard};
|
||||
use crate::{task::ExportUserConfig, Config};
|
||||
|
||||
type EditorSender = mpsc::UnboundedSender<EditorRequest>;
|
||||
|
||||
/// LSP project compiler.
|
||||
pub type LspProjectCompiler = ProjectCompiler<LspCompilerFeat, ProjectInsStateExt>;
|
||||
|
||||
/// Getters and the main loop.
|
||||
impl ServerState {
|
||||
/// Changes the export configuration.
|
||||
pub fn change_export_config(&mut self, config: ExportUserConfig) {
|
||||
self.project.export.change_config(config);
|
||||
}
|
||||
|
||||
/// Snapshot the compiler thread for tasks
|
||||
pub fn snapshot(&mut self) -> Result<LspCompileSnapshot> {
|
||||
self.project.snapshot()
|
||||
}
|
||||
|
||||
/// Snapshot the compiler thread for language queries
|
||||
pub fn query_snapshot(&mut self) -> Result<LspQuerySnapshot> {
|
||||
self.project.query_snapshot(None)
|
||||
}
|
||||
|
||||
/// Snapshot the compiler thread for language queries
|
||||
pub fn query_snapshot_with_stat(
|
||||
&mut self,
|
||||
q: &CompilerQueryRequest,
|
||||
) -> Result<QuerySnapWithStat> {
|
||||
let name: &'static str = q.into();
|
||||
let path = q.associated_path();
|
||||
let stat = self.project.stats.query_stat(path, name);
|
||||
let snap = self.project.query_snapshot(Some(q))?;
|
||||
Ok((snap, stat))
|
||||
}
|
||||
|
||||
/// Restart the primary server.
|
||||
pub fn restart_primary(&mut self) -> Result<ProjectInsId> {
|
||||
// todo: hot replacement
|
||||
#[cfg(feature = "preview")]
|
||||
self.preview.stop_all();
|
||||
|
||||
let watchers = self.preview.watchers.clone();
|
||||
let editor_tx = self.editor_tx.clone();
|
||||
|
||||
let new_project = Self::project(&self.config, editor_tx, self.client.clone(), watchers);
|
||||
|
||||
let mut old_project = std::mem::replace(&mut self.project, new_project);
|
||||
|
||||
let snapshot = FileChangeSet::new_inserts(
|
||||
self.memory_changes
|
||||
.iter()
|
||||
.map(|(path, content)| {
|
||||
let content = Bytes::from(content.clone().text().as_bytes());
|
||||
(path.clone(), FileResult::Ok(content).into())
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
|
||||
self.project
|
||||
.interrupt(Interrupt::Memory(MemoryEvent::Update(snapshot)));
|
||||
|
||||
rayon::spawn(move || {
|
||||
old_project.stop();
|
||||
});
|
||||
|
||||
Ok(self.project.primary_id().clone())
|
||||
}
|
||||
|
||||
/// Restart the server with the given group.
|
||||
pub fn restart_dedicate(
|
||||
&mut self,
|
||||
dedicate: &str,
|
||||
entry: Option<ImmutPath>,
|
||||
) -> Result<ProjectInsId> {
|
||||
let entry = self.config.compile.entry_resolver.resolve(entry);
|
||||
self.project.restart_dedicate(dedicate, entry)
|
||||
}
|
||||
|
||||
// pub async fn settle(&mut self) {
|
||||
// let _ = self.change_entry(None);
|
||||
// log::info!("TypstActor({}): settle requested", self.handle.diag_group);
|
||||
// match self.handle.settle().await {
|
||||
// Ok(()) => log::info!("TypstActor({}): settled",
|
||||
// self.handle.diag_group), Err(err) => error!(
|
||||
// "TypstActor({}): failed to settle: {err:#}",
|
||||
// self.handle.diag_group
|
||||
// ),
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Create a fresh [`ProjectState`].
|
||||
pub fn project(
|
||||
config: &Config,
|
||||
editor_tx: tokio::sync::mpsc::UnboundedSender<EditorRequest>,
|
||||
client: TypedLspClient<ServerState>,
|
||||
preview: ProjectPreviewState,
|
||||
) -> ProjectState {
|
||||
let const_config = &config.const_config;
|
||||
|
||||
// Run Export actors before preparing cluster to avoid loss of events
|
||||
let export = crate::task::ExportTask::new(
|
||||
client.handle.clone(),
|
||||
Some(editor_tx.clone()),
|
||||
config.export(),
|
||||
);
|
||||
|
||||
// Create the compile handler for client consuming results.
|
||||
let periscope_args = config.compile.periscope_args.clone();
|
||||
let handle = Arc::new(CompileHandlerImpl {
|
||||
#[cfg(feature = "preview")]
|
||||
preview,
|
||||
export: export.clone(),
|
||||
editor_tx: editor_tx.clone(),
|
||||
client: Box::new(client.clone().to_untyped()),
|
||||
analysis: Arc::new(Analysis {
|
||||
position_encoding: const_config.position_encoding,
|
||||
allow_overlapping_token: const_config.tokens_overlapping_token_support,
|
||||
allow_multiline_token: const_config.tokens_multiline_token_support,
|
||||
remove_html: !config.support_html_in_markdown,
|
||||
completion_feat: config.completion.clone(),
|
||||
color_theme: match config.compile.color_theme.as_deref() {
|
||||
Some("dark") => tinymist_query::ColorTheme::Dark,
|
||||
_ => tinymist_query::ColorTheme::Light,
|
||||
},
|
||||
periscope: periscope_args.map(|args| {
|
||||
let r = TypstPeriscopeProvider(PeriscopeRenderer::new(args));
|
||||
Arc::new(r) as Arc<dyn PeriscopeProvider + Send + Sync>
|
||||
}),
|
||||
tokens_caches: Arc::default(),
|
||||
workers: Default::default(),
|
||||
caches: Default::default(),
|
||||
analysis_rev_cache: Arc::default(),
|
||||
stats: Arc::default(),
|
||||
}),
|
||||
|
||||
notified_revision: Mutex::default(),
|
||||
});
|
||||
|
||||
let default_path = config.compile.entry_resolver.resolve_default();
|
||||
let entry = config.compile.entry_resolver.resolve(default_path);
|
||||
let inputs = config.compile.determine_inputs();
|
||||
let cert_path = config.compile.determine_certification_path();
|
||||
let package = config.compile.determine_package_opts();
|
||||
|
||||
log::info!("ServerState: creating ProjectState, entry: {entry:?}, inputs: {inputs:?}");
|
||||
|
||||
// todo: never fail?
|
||||
let embedded_fonts = Arc::new(LspUniverseBuilder::only_embedded_fonts().unwrap());
|
||||
let package_registry =
|
||||
LspUniverseBuilder::resolve_package(cert_path.clone(), Some(&package));
|
||||
let verse = LspUniverseBuilder::build(entry, inputs, embedded_fonts, package_registry);
|
||||
|
||||
// todo: unify filesystem watcher
|
||||
let (dep_tx, dep_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let fs_client = client.clone().to_untyped();
|
||||
let async_handle = client.handle.clone();
|
||||
async_handle.spawn(watch_deps(dep_rx, move |event| {
|
||||
fs_client.send_event(LspInterrupt::Fs(event));
|
||||
}));
|
||||
|
||||
// Create the actor
|
||||
let compile_handle = handle.clone();
|
||||
let compiler = ProjectCompiler::new(
|
||||
verse,
|
||||
dep_tx,
|
||||
CompileServerOpts {
|
||||
handler: compile_handle,
|
||||
enable_watch: true,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
// Delayed Loads fonts
|
||||
let font_client = client.clone();
|
||||
let font_resolver = config.compile.determine_fonts();
|
||||
client.handle.spawn_blocking(move || {
|
||||
// Refresh fonts
|
||||
font_client.send_event(LspInterrupt::Font(font_resolver.wait().clone()));
|
||||
});
|
||||
|
||||
ProjectState {
|
||||
compiler,
|
||||
preview: Default::default(),
|
||||
analysis: handle.analysis.clone(),
|
||||
stats: CompilerQueryStats::default(),
|
||||
export: handle.export.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ProjectInsStateExt {
|
||||
pub is_compiling: bool,
|
||||
|
|
@ -109,6 +295,20 @@ impl ProjectState {
|
|||
}
|
||||
}
|
||||
|
||||
struct TypstPeriscopeProvider(PeriscopeRenderer);
|
||||
|
||||
impl PeriscopeProvider for TypstPeriscopeProvider {
|
||||
/// Resolve periscope image at the given position.
|
||||
fn periscope_at(
|
||||
&self,
|
||||
ctx: &mut LocalContext,
|
||||
doc: VersionedDocument,
|
||||
pos: TypstPosition,
|
||||
) -> Option<String> {
|
||||
self.0.render_marked(ctx, doc, pos)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ProjectPreviewState {
|
||||
#[cfg(feature = "preview")]
|
||||
|
|
|
|||
|
|
@ -4,35 +4,23 @@ use std::path::{Path, PathBuf};
|
|||
use std::sync::Arc;
|
||||
|
||||
use lsp_types::*;
|
||||
use parking_lot::Mutex;
|
||||
use sync_lsp::*;
|
||||
use task::ExportUserConfig;
|
||||
use tinymist_project::{EntryResolver, Interrupt, LspCompileSnapshot, ProjectInsId};
|
||||
use tinymist_query::analysis::{Analysis, PeriscopeProvider};
|
||||
use tinymist_query::{
|
||||
CompilerQueryRequest, LocalContext, LspWorldExt, OnExportRequest, ServerInfoResponse,
|
||||
VersionedDocument,
|
||||
};
|
||||
use tinymist_render::PeriscopeRenderer;
|
||||
use tinymist_project::{EntryResolver, LspCompileSnapshot, ProjectInsId};
|
||||
use tinymist_query::{LspWorldExt, OnExportRequest, ServerInfoResponse};
|
||||
use tinymist_std::error::prelude::*;
|
||||
use tinymist_std::ImmutPath;
|
||||
use tokio::sync::mpsc;
|
||||
use typst::diag::FileResult;
|
||||
use typst::layout::Position as TypstPosition;
|
||||
use typst::syntax::Source;
|
||||
|
||||
use crate::actor::editor::{EditorActor, EditorRequest};
|
||||
use crate::project::{
|
||||
update_lock, watch_deps, CompileHandlerImpl, CompileServerOpts, LspInterrupt, LspQuerySnapshot,
|
||||
ProjectCompiler, ProjectPreviewState, ProjectState, QuerySnapWithStat,
|
||||
update_lock, LspInterrupt, ProjectPreviewState, ProjectState,
|
||||
PROJECT_ROUTE_USER_ACTION_PRIORITY,
|
||||
};
|
||||
use crate::route::ProjectRouteState;
|
||||
use crate::state::query::OnEnter;
|
||||
use crate::stats::CompilerQueryStats;
|
||||
use crate::state::lsp_query::OnEnter;
|
||||
use crate::task::{ExportTask, FormatTask, UserActionTask};
|
||||
use crate::vfs::{Bytes, FileChangeSet, MemoryEvent};
|
||||
use crate::world::{LspUniverseBuilder, TaskInputs};
|
||||
use crate::world::TaskInputs;
|
||||
use crate::{init::*, *};
|
||||
|
||||
pub(crate) use futures::Future;
|
||||
|
|
@ -53,10 +41,21 @@ pub(crate) fn as_path_pos(inp: TextDocumentPositionParams) -> (PathBuf, Position
|
|||
pub struct ServerState {
|
||||
/// The lsp client
|
||||
pub client: TypedLspClient<Self>,
|
||||
|
||||
// State
|
||||
/// The project route state.
|
||||
pub route: ProjectRouteState,
|
||||
/// The project state.
|
||||
pub project: ProjectState,
|
||||
/// The preview state.
|
||||
#[cfg(feature = "preview")]
|
||||
pub preview: tool::preview::PreviewState,
|
||||
/// The formatter tasks running in backend, which will be scheduled by async
|
||||
/// runtime.
|
||||
pub formatter: FormatTask,
|
||||
/// The user action tasks running in backend, which will be scheduled by
|
||||
/// async runtime.
|
||||
pub user_action: UserActionTask,
|
||||
|
||||
// State to synchronize with the client.
|
||||
/// Whether the server has registered semantic tokens capabilities.
|
||||
|
|
@ -75,21 +74,10 @@ pub struct ServerState {
|
|||
// Configurations
|
||||
/// User configuration from the editor.
|
||||
pub config: Config,
|
||||
|
||||
// Resources
|
||||
/// Source synchronized with client
|
||||
pub memory_changes: HashMap<Arc<Path>, Source>,
|
||||
/// The preview state.
|
||||
#[cfg(feature = "preview")]
|
||||
pub preview: tool::preview::PreviewState,
|
||||
/// The diagnostics sender to send diagnostics to `crate::actor::cluster`.
|
||||
pub editor_tx: mpsc::UnboundedSender<EditorRequest>,
|
||||
/// The formatter tasks running in backend, which will be scheduled by async
|
||||
/// runtime.
|
||||
pub formatter: FormatTask,
|
||||
/// The user action tasks running in backend, which will be scheduled by
|
||||
/// async runtime.
|
||||
pub user_action: UserActionTask,
|
||||
}
|
||||
|
||||
/// Getters and the main loop.
|
||||
|
|
@ -126,6 +114,21 @@ impl ServerState {
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets the const configuration.
|
||||
pub fn const_config(&self) -> &ConstConfig {
|
||||
&self.config.const_config
|
||||
}
|
||||
|
||||
/// Gets the compile configuration.
|
||||
pub fn compile_config(&self) -> &CompileConfig {
|
||||
&self.config.compile
|
||||
}
|
||||
|
||||
/// Gets the entry resolver.
|
||||
pub fn entry_resolver(&self) -> &EntryResolver {
|
||||
&self.compile_config().entry_resolver
|
||||
}
|
||||
|
||||
/// The entry point for the language server.
|
||||
pub fn main(client: TypedLspClient<Self>, config: Config, start: bool) -> Self {
|
||||
log::info!("LanguageState: initialized with config {config:?}");
|
||||
|
|
@ -153,44 +156,7 @@ impl ServerState {
|
|||
service
|
||||
}
|
||||
|
||||
/// Get the const configuration.
|
||||
pub fn const_config(&self) -> &ConstConfig {
|
||||
&self.config.const_config
|
||||
}
|
||||
|
||||
/// Get the compile configuration.
|
||||
pub fn compile_config(&self) -> &CompileConfig {
|
||||
&self.config.compile
|
||||
}
|
||||
|
||||
/// Get the entry resolver.
|
||||
pub fn entry_resolver(&self) -> &EntryResolver {
|
||||
&self.compile_config().entry_resolver
|
||||
}
|
||||
|
||||
/// Snapshot the compiler thread for tasks
|
||||
pub fn snapshot(&mut self) -> Result<LspCompileSnapshot> {
|
||||
self.project.snapshot()
|
||||
}
|
||||
|
||||
/// Snapshot the compiler thread for language queries
|
||||
pub fn query_snapshot(&mut self) -> Result<LspQuerySnapshot> {
|
||||
self.project.query_snapshot(None)
|
||||
}
|
||||
|
||||
/// Snapshot the compiler thread for language queries
|
||||
pub fn query_snapshot_with_stat(
|
||||
&mut self,
|
||||
q: &CompilerQueryRequest,
|
||||
) -> Result<QuerySnapWithStat> {
|
||||
let name: &'static str = q.into();
|
||||
let path = q.associated_path();
|
||||
let stat = self.project.stats.query_stat(path, name);
|
||||
let snap = self.project.query_snapshot(Some(q))?;
|
||||
Ok((snap, stat))
|
||||
}
|
||||
|
||||
/// Install handlers to the language server.
|
||||
/// Installs handlers to the language server.
|
||||
pub fn install<T: Initializer<S = Self> + AddCommands + 'static>(
|
||||
provider: LspBuilder<T>,
|
||||
) -> LspBuilder<T> {
|
||||
|
|
@ -288,6 +254,7 @@ impl ServerState {
|
|||
provider
|
||||
}
|
||||
|
||||
/// Handles the project interrupts.
|
||||
fn compile_interrupt<T: Initializer<S = Self>>(
|
||||
mut state: ServiceState<T, T::S>,
|
||||
params: LspInterrupt,
|
||||
|
|
@ -306,7 +273,7 @@ impl ServerState {
|
|||
}
|
||||
|
||||
impl ServerState {
|
||||
/// Get the current server info.
|
||||
/// Gets the current server info.
|
||||
pub fn collect_server_info(&mut self) -> QueryFuture {
|
||||
let dg = self.project.primary_id().to_string();
|
||||
let api_stats = self.project.stats.report();
|
||||
|
|
@ -333,167 +300,7 @@ impl ServerState {
|
|||
})
|
||||
}
|
||||
|
||||
/// Restart the primary server.
|
||||
pub fn restart_primary(&mut self) -> Result<ProjectInsId> {
|
||||
// todo: hot replacement
|
||||
#[cfg(feature = "preview")]
|
||||
self.preview.stop_all();
|
||||
|
||||
let watchers = self.preview.watchers.clone();
|
||||
let editor_tx = self.editor_tx.clone();
|
||||
|
||||
let new_project = Self::project(&self.config, editor_tx, self.client.clone(), watchers);
|
||||
|
||||
let mut old_project = std::mem::replace(&mut self.project, new_project);
|
||||
|
||||
let snapshot = FileChangeSet::new_inserts(
|
||||
self.memory_changes
|
||||
.iter()
|
||||
.map(|(path, content)| {
|
||||
let content = Bytes::from(content.clone().text().as_bytes());
|
||||
(path.clone(), FileResult::Ok(content).into())
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
|
||||
self.project
|
||||
.interrupt(Interrupt::Memory(MemoryEvent::Update(snapshot)));
|
||||
|
||||
rayon::spawn(move || {
|
||||
old_project.stop();
|
||||
});
|
||||
|
||||
Ok(self.project.primary_id().clone())
|
||||
}
|
||||
|
||||
/// Restart the server with the given group.
|
||||
pub fn restart_dedicate(
|
||||
&mut self,
|
||||
dedicate: &str,
|
||||
entry: Option<ImmutPath>,
|
||||
) -> Result<ProjectInsId> {
|
||||
let entry = self.config.compile.entry_resolver.resolve(entry);
|
||||
self.project.restart_dedicate(dedicate, entry)
|
||||
}
|
||||
|
||||
// pub async fn settle(&mut self) {
|
||||
// let _ = self.change_entry(None);
|
||||
// log::info!("TypstActor({}): settle requested", self.handle.diag_group);
|
||||
// match self.handle.settle().await {
|
||||
// Ok(()) => log::info!("TypstActor({}): settled",
|
||||
// self.handle.diag_group), Err(err) => error!(
|
||||
// "TypstActor({}): failed to settle: {err:#}",
|
||||
// self.handle.diag_group
|
||||
// ),
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Create a fresh [`ProjectState`].
|
||||
pub fn project(
|
||||
config: &Config,
|
||||
editor_tx: tokio::sync::mpsc::UnboundedSender<EditorRequest>,
|
||||
client: TypedLspClient<ServerState>,
|
||||
preview: project::ProjectPreviewState,
|
||||
) -> ProjectState {
|
||||
let const_config = &config.const_config;
|
||||
|
||||
// Run Export actors before preparing cluster to avoid loss of events
|
||||
let export = ExportTask::new(
|
||||
client.handle.clone(),
|
||||
Some(editor_tx.clone()),
|
||||
config.export(),
|
||||
);
|
||||
|
||||
// Create the compile handler for client consuming results.
|
||||
let periscope_args = config.compile.periscope_args.clone();
|
||||
let handle = Arc::new(CompileHandlerImpl {
|
||||
#[cfg(feature = "preview")]
|
||||
preview,
|
||||
export: export.clone(),
|
||||
editor_tx: editor_tx.clone(),
|
||||
client: Box::new(client.clone().to_untyped()),
|
||||
analysis: Arc::new(Analysis {
|
||||
position_encoding: const_config.position_encoding,
|
||||
allow_overlapping_token: const_config.tokens_overlapping_token_support,
|
||||
allow_multiline_token: const_config.tokens_multiline_token_support,
|
||||
remove_html: !config.support_html_in_markdown,
|
||||
completion_feat: config.completion.clone(),
|
||||
color_theme: match config.compile.color_theme.as_deref() {
|
||||
Some("dark") => tinymist_query::ColorTheme::Dark,
|
||||
_ => tinymist_query::ColorTheme::Light,
|
||||
},
|
||||
periscope: periscope_args.map(|args| {
|
||||
let r = TypstPeriscopeProvider(PeriscopeRenderer::new(args));
|
||||
Arc::new(r) as Arc<dyn PeriscopeProvider + Send + Sync>
|
||||
}),
|
||||
tokens_caches: Arc::default(),
|
||||
workers: Default::default(),
|
||||
caches: Default::default(),
|
||||
analysis_rev_cache: Arc::default(),
|
||||
stats: Arc::default(),
|
||||
}),
|
||||
|
||||
notified_revision: Mutex::default(),
|
||||
});
|
||||
|
||||
let default_path = config.compile.entry_resolver.resolve_default();
|
||||
let entry = config.compile.entry_resolver.resolve(default_path);
|
||||
let inputs = config.compile.determine_inputs();
|
||||
let cert_path = config.compile.determine_certification_path();
|
||||
let package = config.compile.determine_package_opts();
|
||||
|
||||
log::info!("ServerState: creating ProjectState, entry: {entry:?}, inputs: {inputs:?}");
|
||||
|
||||
// todo: never fail?
|
||||
let embedded_fonts = Arc::new(LspUniverseBuilder::only_embedded_fonts().unwrap());
|
||||
let package_registry =
|
||||
LspUniverseBuilder::resolve_package(cert_path.clone(), Some(&package));
|
||||
let verse = LspUniverseBuilder::build(entry, inputs, embedded_fonts, package_registry);
|
||||
|
||||
// todo: unify filesystem watcher
|
||||
let (dep_tx, dep_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let fs_client = client.clone().to_untyped();
|
||||
let async_handle = client.handle.clone();
|
||||
async_handle.spawn(watch_deps(dep_rx, move |event| {
|
||||
fs_client.send_event(LspInterrupt::Fs(event));
|
||||
}));
|
||||
|
||||
// Create the actor
|
||||
let compile_handle = handle.clone();
|
||||
let compiler = ProjectCompiler::new(
|
||||
verse,
|
||||
dep_tx,
|
||||
CompileServerOpts {
|
||||
handler: compile_handle,
|
||||
enable_watch: true,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
// Delayed Loads fonts
|
||||
let font_client = client.clone();
|
||||
let font_resolver = config.compile.determine_fonts();
|
||||
client.handle.spawn_blocking(move || {
|
||||
// Refresh fonts
|
||||
font_client.send_event(LspInterrupt::Font(font_resolver.wait().clone()));
|
||||
});
|
||||
|
||||
ProjectState {
|
||||
compiler,
|
||||
preview: Default::default(),
|
||||
analysis: handle.analysis.clone(),
|
||||
stats: CompilerQueryStats::default(),
|
||||
export: handle.export.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerState {
|
||||
pub(crate) fn change_export_config(&mut self, config: ExportUserConfig) {
|
||||
self.project.export.change_config(config);
|
||||
}
|
||||
|
||||
/// Export the current document.
|
||||
/// Exports the current document.
|
||||
pub fn on_export(&mut self, req: OnExportRequest) -> QueryFuture {
|
||||
let OnExportRequest { path, task, open } = req;
|
||||
let entry = self.entry_resolver().resolve(Some(path.as_path().into()));
|
||||
|
|
@ -547,20 +354,6 @@ impl ServerState {
|
|||
}
|
||||
}
|
||||
|
||||
struct TypstPeriscopeProvider(PeriscopeRenderer);
|
||||
|
||||
impl PeriscopeProvider for TypstPeriscopeProvider {
|
||||
/// Resolve periscope image at the given position.
|
||||
fn periscope_at(
|
||||
&self,
|
||||
ctx: &mut LocalContext,
|
||||
doc: VersionedDocument,
|
||||
pos: TypstPosition,
|
||||
) -> Option<String> {
|
||||
self.0.render_marked(ctx, doc, pos)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_path() {
|
||||
use reflexo::path::PathClean;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue