dev: ensure to run a single compiler instance (#119)

* build: remoe tinymist deps on tests

* dev: ensure unique running compiler

* docs: update some comments
This commit is contained in:
Myriad-Dreamin 2024-03-29 14:36:02 +08:00 committed by GitHub
parent b364160fdd
commit d53bd80d14
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 59 additions and 71 deletions

1
Cargo.lock generated
View file

@ -3603,7 +3603,6 @@ dependencies = [
"reflexo",
"serde",
"serde_json",
"tinymist",
]
[[package]]

View file

@ -58,7 +58,6 @@ use typst_ts_compiler::service::Compiler;
use typst_ts_core::{error::prelude::*, ImmutPath};
use super::lsp_init::*;
use crate::actor::render::ExportConfig;
use crate::actor::typ_client::CompileClientActor;
use crate::actor::{FormattingConfig, FormattingRequest};
use crate::compiler::{CompileServer, CompileServerArgs};
@ -187,8 +186,14 @@ pub struct TypstLanguageServer {
// State to synchronize with the client.
/// Whether the server is shutting down.
pub shutdown_requested: bool,
/// Whether the server has registered semantic tokens capabilities.
pub sema_tokens_registered: Option<bool>,
/// Whether the server has registered document formatter capabilities.
pub formatter_registered: Option<bool>,
/// Whether client is pinning a file.
pub pinning: bool,
/// The client focusing file.
pub focusing: Option<ImmutPath>,
// Configurations
/// User configuration from the editor.
@ -207,11 +212,17 @@ pub struct TypstLanguageServer {
/// Regular commands for dispatching.
pub regular_cmds: RegularCmdMap,
// Resources
/// Source synchronized with client
pub memory_changes: HashMap<Arc<Path>, MemoryFileMeta>,
pub pinning: bool,
pub primary: CompileServer,
pub main: Option<CompileClientActor>,
/// The semantic token context.
pub tokens_ctx: SemanticTokenContext,
/// The compiler for general purpose.
pub primary: CompileServer,
/// The compilers for tasks
pub dedicates: Vec<CompileServer>,
/// The formatter thread running in backend.
/// Note: The thread will exit if you drop the sender.
pub format_thread: Option<crossbeam_channel::Sender<FormattingRequest>>,
}
@ -236,6 +247,7 @@ impl TypstLanguageServer {
font: args.font,
handle: tokio::runtime::Handle::current(),
}),
dedicates: Vec::new(),
shutdown_requested: false,
sema_tokens_registered: None,
formatter_registered: None,
@ -249,20 +261,18 @@ impl TypstLanguageServer {
memory_changes: HashMap::new(),
pinning: false,
main: None,
focusing: None,
tokens_ctx,
format_thread: None,
}
}
/// Get the const configuration.
///
/// # Panics
/// Panics if the const configuration is not initialized.
pub fn const_config(&self) -> &ConstConfig {
&self.const_config
}
/// Get the primary compiler for those commands without task context.
pub fn primary(&self) -> &CompileClientActor {
self.primary.compiler.as_ref().expect("primary")
}
@ -322,7 +332,7 @@ impl InitializedLspDriver for TypstLanguageServer {
if self.const_config().sema_tokens_dynamic_registration
&& self.config.semantic_tokens == SemanticTokensMode::Enable
{
let err = self.react_sema_token_changes(true);
let err = self.enable_sema_token_caps(true);
if let Err(err) = err {
error!("could not register semantic tokens for initialization: {err}");
}
@ -331,7 +341,7 @@ impl InitializedLspDriver for TypstLanguageServer {
if self.const_config().doc_fmt_dynamic_registration
&& self.config.formatter != FormatterMode::Disable
{
let err = self.react_formatter_changes(true);
let err = self.enable_formatter_caps(true);
if let Err(err) = err {
error!("could not register formatter for initialization: {err}");
}
@ -360,6 +370,7 @@ impl InitializedLspDriver for TypstLanguageServer {
info!("server initialized");
}
/// Enters main loop after initialization.
fn main_loop(&mut self, inbox: crossbeam_channel::Receiver<Message>) -> anyhow::Result<()> {
// todo: follow what rust analyzer does
// Windows scheduler implements priority boosts: if thread waits for an
@ -398,6 +409,7 @@ impl InitializedLspDriver for TypstLanguageServer {
}
impl TypstLanguageServer {
/// Receives the next event from event sources.
fn next_event(&self, inbox: &Receiver<lsp_server::Message>) -> Option<Event> {
select! {
recv(inbox) -> msg =>
@ -405,6 +417,7 @@ impl TypstLanguageServer {
}
}
/// Handles an incoming event.
fn handle_event(&mut self, event: Event) -> anyhow::Result<()> {
let loop_start = Instant::now();
@ -485,7 +498,8 @@ impl TypstLanguageServer {
Ok(())
}
fn react_sema_token_changes(&mut self, enable: bool) -> anyhow::Result<()> {
/// Registers or unregisters semantic tokens.
fn enable_sema_token_caps(&mut self, enable: bool) -> anyhow::Result<()> {
if !self.const_config().sema_tokens_dynamic_registration {
trace!("skip register semantic by config");
return Ok(());
@ -515,7 +529,8 @@ impl TypstLanguageServer {
res
}
fn react_formatter_changes(&mut self, enable: bool) -> anyhow::Result<()> {
/// Registers or unregisters document formatter.
fn enable_formatter_caps(&mut self, enable: bool) -> anyhow::Result<()> {
if !self.const_config().doc_fmt_dynamic_registration {
trace!("skip dynamic register formatter by config");
return Ok(());
@ -670,7 +685,7 @@ impl TypstLanguageServer {
pub fn pin_document(&mut self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
let new_entry = parse_path_or_null(arguments.first())?;
let update_result = self.update_main_entry(new_entry.clone());
let update_result = self.pin_entry(new_entry.clone());
update_result.map_err(|err| internal_error(format!("could not pin file: {err}")))?;
info!("file pinned: {entry:?}", entry = new_entry);
@ -678,10 +693,10 @@ impl TypstLanguageServer {
}
/// Focus main file to some path.
pub fn focus_document(&self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
pub fn focus_document(&mut self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
let new_entry = parse_path_or_null(arguments.first())?;
let update_result = self.update_primary_entry(new_entry.clone());
let update_result = self.focus_entry(new_entry.clone());
update_result.map_err(|err| internal_error(format!("could not focus file: {err}")))?;
info!("file focused: {entry:?}", entry = new_entry);
@ -878,33 +893,17 @@ impl TypstLanguageServer {
self.primary.on_changed_configuration(values)?;
info!("new settings applied");
if config.compile.output_path != self.config.compile.output_path
|| config.compile.export_pdf != self.config.compile.export_pdf
{
let config = ExportConfig {
substitute_pattern: self.config.compile.output_path.clone(),
mode: self.config.compile.export_pdf,
..ExportConfig::default()
};
{
if let Some(main) = self.main.as_ref() {
main.change_export_pdf(config);
}
}
}
if config.semantic_tokens != self.config.semantic_tokens {
let err = self.react_sema_token_changes(
self.config.semantic_tokens == SemanticTokensMode::Enable,
);
let err = self
.enable_sema_token_caps(self.config.semantic_tokens == SemanticTokensMode::Enable);
if let Err(err) = err {
error!("could not change semantic tokens config: {err}");
}
}
if config.formatter != self.config.formatter {
let err = self.react_formatter_changes(self.config.formatter != FormatterMode::Disable);
let err = self.enable_formatter_caps(self.config.formatter != FormatterMode::Disable);
if let Err(err) = err {
error!("could not change formatter config: {err}");
}

View file

@ -380,14 +380,15 @@ impl Init {
tokio::spawn(cluster_actor.run());
// Respond to the host (LSP client)
// Register these capabilities statically if the client does not support dynamic
// registration
let semantic_tokens_provider = match service.config.semantic_tokens {
SemanticTokensMode::Enable if !cc.sema_tokens_dynamic_registration => {
Some(get_semantic_tokens_options().into())
}
_ => None,
};
// if !cc.doc_fmt_dynamic_registration
let document_formatting_provider = match service.config.formatter {
FormatterMode::Typstyle | FormatterMode::Typstfmt
if !cc.doc_fmt_dynamic_registration =>
@ -467,7 +468,6 @@ fn create_font_book(opts: CompileFontOpts) -> ZResult<SharedFontResolver> {
let res = crate::world::LspWorldBuilder::resolve_fonts(opts)?;
Ok(SharedFontResolver {
inner: Arc::new(res),
// inner: res,
})
}

View file

@ -24,44 +24,37 @@ pub struct MemoryFileMeta {
}
impl TypstLanguageServer {
/// Updates the main entry
pub fn update_main_entry(&mut self, new_entry: Option<ImmutPath>) -> Result<(), Error> {
self.pinning = new_entry.is_some();
match (new_entry, self.main.is_some()) {
(Some(new_entry), true) => {
let main = self.main.as_mut().unwrap();
main.change_entry(Some(new_entry))?;
}
(Some(new_entry), false) => {
let main_node = self.server(
"main".to_owned(),
self.config.compile.determine_entry(Some(new_entry)),
self.config.compile.determine_inputs(),
);
/// Pin the entry to the given path
pub fn pin_entry(&mut self, new_entry: Option<ImmutPath>) -> Result<(), Error> {
let pinning = new_entry.is_some();
self.primary().change_entry(new_entry)?;
self.pinning = pinning;
self.main = Some(main_node);
if !pinning {
if let Some(e) = &self.focusing {
self.primary().change_entry(Some(e.clone()))?;
}
(None, true) => {
let main = self.main.take().unwrap();
std::thread::spawn(move || main.settle());
}
(None, false) => {}
};
}
Ok(())
}
/// Updates the primary (focusing) entry
pub fn update_primary_entry(&self, new_entry: Option<ImmutPath>) -> Result<(), Error> {
pub fn focus_entry(&mut self, new_entry: Option<ImmutPath>) -> Result<(), Error> {
if self.pinning {
self.focusing = new_entry;
return Ok(());
}
self.primary().change_entry(new_entry.clone())
}
}
impl TypstLanguageServer {
fn update_source(&self, files: FileChangeSet) -> Result<(), Error> {
let main = self.main.as_ref();
let primary = Some(self.primary());
let clients_to_notify = (primary.into_iter()).chain(main);
let clients_to_notify =
(primary.into_iter()).chain(self.dedicates.iter().map(CompileServer::compiler));
for client in clients_to_notify {
client.add_memory_changes(MemoryEvent::Update(files.clone()));
@ -220,16 +213,14 @@ impl TypstLanguageServer {
SelectionRange(req) => query_source!(self, SelectionRange, req),
DocumentSymbol(req) => query_source!(self, DocumentSymbol, req),
_ => {
match self.main.as_ref() {
Some(main) if self.pinning => Self::query_on(main, query),
Some(..) | None => {
// todo: race condition, we need atomic primary query
if let Some(path) = query.associated_path() {
self.primary().change_entry(Some(path.into()))?;
}
Self::query_on(self.primary(), query)
let client = self.primary();
if !self.pinning {
// todo: race condition, we need atomic primary query
if let Some(path) = query.associated_path() {
client.change_entry(Some(path.into()))?;
}
}
Self::query_on(client, query)
}
}
}

View file

@ -9,7 +9,6 @@ name = "tinymist-e2e-tests"
path = "e2e/main.rs"
[dev-dependencies]
tinymist.workspace = true
lsp-server.workspace = true
lsp-types.workspace = true
serde.workspace = true