Skip to content

Commit

Permalink
LS: Send project model updates through channel
Browse files Browse the repository at this point in the history
commit-id:d41ad059
  • Loading branch information
piotmag769 committed Nov 25, 2024
1 parent 97e618d commit 90bb324
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 105 deletions.
27 changes: 3 additions & 24 deletions crates/cairo-lang-language-server/src/lang/db/swapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ use cairo_lang_utils::{Intern, LookupIntern};
use lsp_types::Url;
use tracing::{error, warn};

use crate::config::Config;
use crate::lang::db::AnalysisDatabase;
use crate::lang::lsp::LsProtoGroup;
use crate::lang::proc_macros::db::ProcMacroGroup;
use crate::project::ProjectController;
use crate::server::client::Notifier;
use crate::toolchain::scarb::ScarbToolchain;
use crate::{Tricks, env_config};

Expand Down Expand Up @@ -57,9 +55,7 @@ impl AnalysisDatabaseSwapper {
&mut self,
db: &mut AnalysisDatabase,
open_files: &HashSet<Url>,
config: &Config,
tricks: &Tricks,
notifier: &Notifier,
project_controller: &mut ProjectController,
) {
let Ok(elapsed) = self.last_replace.elapsed() else {
Expand All @@ -76,7 +72,7 @@ impl AnalysisDatabaseSwapper {
return;
}

self.swap(db, open_files, config, tricks, notifier, project_controller)
self.swap(db, open_files, tricks, project_controller)
}

/// Swaps the database.
Expand All @@ -85,9 +81,7 @@ impl AnalysisDatabaseSwapper {
&mut self,
db: &mut AnalysisDatabase,
open_files: &HashSet<Url>,
config: &Config,
tricks: &Tricks,
notifier: &Notifier,
project_controller: &mut ProjectController,
) {
let Ok(new_db) = catch_unwind(AssertUnwindSafe(|| {
Expand All @@ -96,13 +90,7 @@ impl AnalysisDatabaseSwapper {
let mut new_db = AnalysisDatabase::new(tricks);
self.migrate_proc_macro_state(&mut new_db, db);
self.migrate_file_overrides(&mut new_db, db, open_files);
self.detect_crates_for_open_files(
&mut new_db,
open_files,
config,
notifier,
project_controller,
);
self.detect_crates_for_open_files(open_files, project_controller);
new_db
})) else {
error!("caught panic when preparing new db for swap");
Expand Down Expand Up @@ -156,10 +144,7 @@ impl AnalysisDatabaseSwapper {
/// Ensures all open files have their crates detected to regenerate crates state.
fn detect_crates_for_open_files(
&self,
new_db: &mut AnalysisDatabase,
open_files: &HashSet<Url>,
config: &Config,
notifier: &Notifier,
project_controller: &mut ProjectController,
) {
for uri in open_files {
Expand All @@ -168,13 +153,7 @@ impl AnalysisDatabaseSwapper {
continue;
};

project_controller.detect_crate_for(
new_db,
&self.scarb_toolchain,
config,
&file_path,
notifier,
);
project_controller.update_project_for(&self.scarb_toolchain, &file_path);
}
}
}
40 changes: 22 additions & 18 deletions crates/cairo-lang-language-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ use anyhow::Result;
use cairo_lang_filesystem::db::FilesGroup;
use cairo_lang_filesystem::ids::FileLongId;
use cairo_lang_semantic::plugin::PluginSuite;
use crossbeam::select;
use crossbeam::channel::{Receiver, select_biased};
use lsp_server::Message;
use lsp_types::RegistrationParams;
use salsa::{Database, Durability};
Expand All @@ -60,6 +60,7 @@ use crate::lsp::capabilities::server::{
collect_dynamic_registrations, collect_server_capabilities,
};
use crate::lsp::result::LSPResult;
use crate::project::{ProjectController, ProjectUpdate};
use crate::server::client::{Notifier, Requester, Responder};
use crate::server::connection::{Connection, ConnectionInitializer};
use crate::server::panic::is_cancelled;
Expand Down Expand Up @@ -273,6 +274,7 @@ impl Backend {
state.proc_macro_controller.initialize_once(&mut state.db);
let proc_macro_channels = state.proc_macro_controller.init_channels();

let project_updates_receiver = state.project_controller.init_channel();
let mut scheduler = Scheduler::new(&mut state, connection.make_sender());

Self::dispatch_setup_tasks(&mut scheduler);
Expand All @@ -287,7 +289,12 @@ impl Backend {
// we basically never hit such a case in CairoLS in happy paths.
scheduler.on_sync_task(Self::refresh_diagnostics);

let result = Self::event_loop(&connection, proc_macro_channels, scheduler);
let result = Self::event_loop(
&connection,
proc_macro_channels,
scheduler,
project_updates_receiver,
);

// Trigger cancellation in any background tasks that might still be running.
state.db.salsa_runtime_mut().synthetic_write(Durability::LOW);
Expand Down Expand Up @@ -343,11 +350,20 @@ impl Backend {
connection: &Connection,
proc_macro_channels: ProcMacroChannelsReceivers,
mut scheduler: Scheduler<'_>,
project_updates_receiver: Receiver<ProjectUpdate>,
) -> Result<()> {
let incoming = connection.incoming();

loop {
select! {
select_biased! {
// Project updates may significantly change the state, therefore
// they should be handled first in case of multiple operations being ready at once.
// To ensure it, keep project updates channel in the first arm of `select_biased!`.
recv(project_updates_receiver) -> project_update => {
let Ok(project_update) = project_update else { break };

scheduler.local(move |state, notifier, _, _| ProjectController::handle_update(state, notifier, project_update));
}
recv(incoming) -> msg => {
let Ok(msg) = msg else { break };

Expand Down Expand Up @@ -390,13 +406,11 @@ impl Backend {
}

/// Calls [`lang::db::AnalysisDatabaseSwapper::maybe_swap`] to do its work.
fn maybe_swap_database(state: &mut State, notifier: Notifier) {
fn maybe_swap_database(state: &mut State, _notifier: Notifier) {
state.db_swapper.maybe_swap(
&mut state.db,
&state.open_files,
&state.config,
&state.tricks,
&notifier,
&mut state.project_controller,
);
}
Expand All @@ -407,24 +421,14 @@ impl Backend {
}

/// Reload crate detection for all open files.
fn reload(
state: &mut State,
notifier: &Notifier,
requester: &mut Requester<'_>,
) -> LSPResult<()> {
fn reload(state: &mut State, requester: &mut Requester<'_>) -> LSPResult<()> {
state.project_controller.clear_loaded_workspaces();
state.config.reload(requester, &state.client_capabilities)?;

for uri in state.open_files.iter() {
let Some(file_id) = state.db.file_for_url(uri) else { continue };
if let FileLongId::OnDisk(file_path) = state.db.lookup_intern_file(file_id) {
state.project_controller.detect_crate_for(
&mut state.db,
&state.scarb_toolchain,
&state.config,
&file_path,
notifier,
);
state.project_controller.update_project_for(&state.scarb_toolchain, &file_path);
}
}

Expand Down
113 changes: 75 additions & 38 deletions crates/cairo-lang-language-server/src/project/mod.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,49 @@
use std::cell::OnceCell;
use std::collections::HashSet;
use std::path::{Path, PathBuf};

use anyhow::Context;
use cairo_lang_compiler::db::validate_corelib;
use cairo_lang_compiler::project::{setup_project, update_crate_roots_from_project_config};
use cairo_lang_project::ProjectConfig;
use tracing::{error, trace, warn};
use crossbeam::channel::{Receiver, Sender};
use tracing::{debug, error, trace, warn};

pub use self::crate_data::Crate;
pub use self::project_manifest_path::*;
use crate::config::Config;
use crate::lang::db::AnalysisDatabase;
use crate::lsp::ext::{CorelibVersionMismatch, ScarbMetadataFailed};
use crate::project::scarb::{get_workspace_members_manifests, update_crate_roots};
use crate::lsp::ext::CorelibVersionMismatch;
use crate::project::scarb::{extract_crates, get_workspace_members_manifests};
use crate::project::unmanaged_core_crate::try_to_init_unmanaged_core;
use crate::server::client::Notifier;
use crate::state::Owned;
use crate::state::{Owned, State};
use crate::toolchain::scarb::ScarbToolchain;

mod crate_data;
mod project_manifest_path;
// TODO(mkaput): These two are `pub` temporarily.
pub(crate) mod scarb;
pub(crate) mod unmanaged_core_crate;
mod scarb;
mod unmanaged_core_crate;

pub struct ProjectController {
loaded_scarb_manifests: Owned<HashSet<PathBuf>>,
sender: OnceCell<Sender<ProjectUpdate>>,
}

impl ProjectController {
pub fn new() -> Self {
ProjectController { loaded_scarb_manifests: Default::default() }
ProjectController { loaded_scarb_manifests: Default::default(), sender: Default::default() }
}

/// Tries to detect the crate root the config that contains a cairo file, and add it to the
/// system.
pub fn init_channel(&mut self) -> Receiver<ProjectUpdate> {
let (sender, receiver) = crossbeam::channel::unbounded();

self.sender.set(sender).expect("failed to set sender once cell");
receiver
}

/// Tries to fetch changes to the project model that are necessary for the file analysis.
#[tracing::instrument(skip_all)]
pub fn detect_crate_for(
&mut self,
db: &mut AnalysisDatabase,
scarb_toolchain: &ScarbToolchain,
config: &Config,
file_path: &Path,
notifier: &Notifier,
) {
match ProjectManifestPath::discover(file_path) {
pub fn update_project_for(&mut self, scarb_toolchain: &ScarbToolchain, file_path: &Path) {
let project_update = match ProjectManifestPath::discover(file_path) {
Some(ProjectManifestPath::Scarb(manifest_path)) => {
if self.loaded_scarb_manifests.contains(&manifest_path) {
trace!("scarb project is already loaded: {}", manifest_path.display());
Expand All @@ -57,48 +56,86 @@ impl ProjectController {
format!("failed to refresh scarb workspace: {}", manifest_path.display())
})
.inspect_err(|err| {
warn!("{err:?}");
notifier.notify::<ScarbMetadataFailed>(());
error!("{err:?}");
})
.ok();

if let Some(metadata) = metadata {
let maybe_crates = if let Some(metadata) = metadata {
self.loaded_scarb_manifests.extend(get_workspace_members_manifests(&metadata));
update_crate_roots(&metadata, db);
Some(extract_crates(&metadata))
} else {
// Try to set up a corelib at least.
try_to_init_unmanaged_core(db, config, scarb_toolchain);
}
None
};

if let Err(result) = validate_corelib(db) {
notifier.notify::<CorelibVersionMismatch>(result.to_string());
}
ProjectUpdate::Scarb(maybe_crates)
}

Some(ProjectManifestPath::CairoProject(config_path)) => {
// The base path of ProjectConfig must be absolute to ensure that all paths in Salsa
// DB will also be absolute.
assert!(config_path.is_absolute());

try_to_init_unmanaged_core(db, config, scarb_toolchain);
let maybe_project_config = ProjectConfig::from_file(&config_path)
// TODO: send failure notification
.inspect_err(|err| error!("{err:?}"))
.ok();
ProjectUpdate::CairoProjectToml(maybe_project_config)
}

None => ProjectUpdate::NoConfig(file_path.to_path_buf()),
};

if let Ok(config) = ProjectConfig::from_file(&config_path) {
update_crate_roots_from_project_config(db, &config);
};
self.sender
.get()
.expect("failed to get sender once cell")
.send(project_update)
.expect("the receiver was expected to exist in the main event loop");
}

pub fn handle_update(state: &mut State, notifier: Notifier, project_update: ProjectUpdate) {
let db = &mut state.db;
match project_update {
ProjectUpdate::Scarb(crates) => {
if let Some(crates) = crates {
debug!("updating crate roots from scarb metadata: {crates:#?}");

for cr in crates {
cr.apply(db);
}
} else {
// Try to set up a corelib at least.
try_to_init_unmanaged_core(db, &state.config, &state.scarb_toolchain);
}
}
ProjectUpdate::CairoProjectToml(maybe_project_config) => {
try_to_init_unmanaged_core(db, &state.config, &state.scarb_toolchain);

None => {
try_to_init_unmanaged_core(db, config, scarb_toolchain);
if let Some(project_config) = maybe_project_config {
update_crate_roots_from_project_config(db, &project_config);
}
}
ProjectUpdate::NoConfig(file_path) => {
try_to_init_unmanaged_core(db, &state.config, &state.scarb_toolchain);

if let Err(err) = setup_project(&mut *db, file_path) {
if let Err(err) = setup_project(&mut *db, &file_path) {
let file_path_s = file_path.to_string_lossy();
error!("error loading file {file_path_s} as a single crate: {err}");
}
}
}

if let Err(result) = validate_corelib(db) {
notifier.notify::<CorelibVersionMismatch>(result.to_string());
}
}

pub fn clear_loaded_workspaces(&mut self) {
self.loaded_scarb_manifests.clear()
}
}

pub enum ProjectUpdate {
Scarb(Option<Vec<Crate>>),
CairoProjectToml(Option<ProjectConfig>),
NoConfig(PathBuf),
}
Loading

0 comments on commit 90bb324

Please sign in to comment.