Skip to content

LSP: Support multiple workspaces #7214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 33 commits into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ca0a514
Refactored workspace management to support multiple workspaces and fi…
JoshuaBatty Jun 1, 2025
f2cefda
rebase
JoshuaBatty Jun 2, 2025
633830a
fmt
JoshuaBatty Jun 2, 2025
c8a1470
Fix condition checking for empty sync workspaces list in test case.
JoshuaBatty Jun 4, 2025
4ad021f
all tests pass
JoshuaBatty Jun 11, 2025
66014b2
rebase and fmt
JoshuaBatty Jun 11, 2025
e523607
benchmarks pass again
JoshuaBatty Jun 11, 2025
e803ad4
Add Tokio runtime support and refactor async block execution in sourc…
JoshuaBatty Jun 12, 2025
7b13ad0
Refactor `block_on_any_runtime` to always use a new Tokio runtime whe…
JoshuaBatty Jun 12, 2025
dcf910d
add document symbols for TypeAliasDecl and SideEffect
JoshuaBatty Jun 13, 2025
e9ef34a
Refactor code to eliminate unnecessary namespace cloning and improve …
JoshuaBatty Jun 13, 2025
726a8e4
add compiled_programs to server state
JoshuaBatty Jun 13, 2025
48191ee
wip making pure functions. move compiled program to server state and …
JoshuaBatty Jun 16, 2025
92c0b9f
udpate test
JoshuaBatty Jun 16, 2025
37570c1
update code_lens test to ensure runnables are created for all files a…
JoshuaBatty Jun 16, 2025
d45edd5
move runnables map to server state
JoshuaBatty Jun 16, 2025
ac806aa
Refactor Sway LSP session handling and improve compilation metrics re…
JoshuaBatty Jun 16, 2025
6dddd7f
fmt
JoshuaBatty Jun 16, 2025
a6dc1cb
benchmarks work again
JoshuaBatty Jun 16, 2025
3ba5a6d
rebase
JoshuaBatty Jun 16, 2025
48b73e4
uri_from_workspace_uri
JoshuaBatty Jun 17, 2025
624bedd
clippy
JoshuaBatty Jun 17, 2025
5763564
clean up
JoshuaBatty Jun 17, 2025
cf62fd4
fix failing test
JoshuaBatty Jun 17, 2025
00b8791
metrics work again
JoshuaBatty Jun 18, 2025
60a6edd
clippy
JoshuaBatty Jun 18, 2025
97630ea
rebase
JoshuaBatty Jun 18, 2025
bf2d556
add Cargo.lock
JoshuaBatty Jun 18, 2025
87c3aa2
remove unnecessary Options
JoshuaBatty Jun 18, 2025
2696793
use saturating sub for safety
JoshuaBatty Jun 18, 2025
2b301e0
feedback
JoshuaBatty Jun 18, 2025
04b8835
Refactor workspace sync initialization for better readability and eff…
JoshuaBatty Jun 18, 2025
260d180
Fix workspace lookup logic to use canonicalized root paths.
JoshuaBatty Jun 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions forc-pkg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ sway-features.workspace = true
sway-types.workspace = true
sway-utils.workspace = true
tar.workspace = true
tokio.workspace = true
toml = { workspace = true, features = ["parse"] }
toml_edit.workspace = true
tracing.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion forc-pkg/src/source/ipfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl source::Fetch for Pinned {
let cid = &self.0;
let ipfs_client = ipfs_client();
let dest = cache_dir();
futures::executor::block_on(async {
crate::source::reg::block_on_any_runtime(async {
match ctx.ipfs_node() {
source::IPFSNode::Local => {
println_action_green("Fetching", "with local IPFS node");
Expand Down
21 changes: 19 additions & 2 deletions forc-pkg/src/source/reg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ impl source::Pin for Source {
type Pinned = Pinned;
fn pin(&self, ctx: source::PinCtx) -> anyhow::Result<(Self::Pinned, PathBuf)> {
let pkg_name = ctx.name;
let cid = futures::executor::block_on(async {
let cid = block_on_any_runtime(async {
with_tmp_fetch_index(ctx.fetch_id(), pkg_name, self, |index_file| async move {
let version = &self.version;
let pkg_entry = index_file
Expand Down Expand Up @@ -357,7 +357,7 @@ impl source::Fetch for Pinned {
self.source.version
),
);
futures::executor::block_on(async {
block_on_any_runtime(async {
// If the user is trying to use public IPFS node with
// registry sources. Use fuel operated ipfs node
// instead.
Expand Down Expand Up @@ -526,6 +526,23 @@ where
Ok(res)
}

/// Execute an async block on the current Tokio runtime if available.
/// If not in a runtime context, a new one is created to run the future.
pub(crate) fn block_on_any_runtime<F>(future: F) -> F::Output
where
F: std::future::Future,
{
if let Ok(handle) = tokio::runtime::Handle::try_current() {
handle.block_on(future)
} else {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(future)
}
}

#[cfg(test)]
mod tests {
use super::{file_location::Namespace, resolve_to_cid, Pinned, Source};
Expand Down
6 changes: 2 additions & 4 deletions sway-lsp/benches/lsp_benchmarks/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ use tokio::runtime::Runtime;
const NUM_DID_CHANGE_ITERATIONS: usize = 10;

fn benchmarks(c: &mut Criterion) {
let (uri, session, state, _) = Runtime::new()
let (uri, session, state, _, sync) = Runtime::new()
.unwrap()
.block_on(async { black_box(super::compile_test_project().await) });

let sync = state.sync_workspace.get().unwrap();
let build_plan = session
.build_plan_cache
.get_or_update(&sync.workspace_manifest_path(), || {
Expand Down Expand Up @@ -42,7 +41,6 @@ fn benchmarks(c: &mut Criterion) {
let results = black_box(
session::compile(&build_plan, &engines_clone, None, lsp_mode.as_ref()).unwrap(),
);
let session = Arc::new(session::Session::new());
let member_path = sync.member_path(&uri).unwrap();
let modified_file = sway_lsp::server_state::modified_file(lsp_mode.as_ref());

Expand All @@ -53,8 +51,8 @@ fn benchmarks(c: &mut Criterion) {
results.clone(),
engines_original.clone(),
&engines_clone,
session.clone(),
&state.token_map,
&state.compiled_programs,
modified_file,
)
.unwrap(),
Expand Down
20 changes: 13 additions & 7 deletions sway-lsp/benches/lsp_benchmarks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,34 @@ use std::{path::PathBuf, sync::Arc};
use sway_core::{Engines, LspConfig};
use sway_lsp::{
config::GarbageCollectionConfig,
core::session::{self, Session},
core::{
session::{self, Session},
sync::SyncWorkspace,
},
server_state::{CompilationContext, ServerState},
};

pub async fn compile_test_project() -> (Url, Arc<Session>, ServerState, Engines) {
pub async fn compile_test_project() -> (Url, Arc<Session>, ServerState, Engines, Arc<SyncWorkspace>)
{
// Load the test project
let uri = Url::from_file_path(benchmark_dir().join("src/main.sw")).unwrap();
let state = ServerState::default();
let engines_clone = state.engines.read().clone();
let session = Arc::new(Session::new());
let sync = state.get_or_init_global_sync_workspace(&uri).await.unwrap();
let sync = state.get_or_init_sync_workspace(&uri).await.unwrap();
let temp_uri = sync.workspace_to_temp_url(&uri).unwrap();

state.documents.handle_open_file(&temp_uri).await;
let ctx = CompilationContext {
session: Some(session.clone()),
sync: Some(sync.clone()),
session: session.clone(),
sync: sync.clone(),
token_map: state.token_map.clone(),
engines: state.engines.clone(),
compiled_programs: state.compiled_programs.clone(),
runnables: state.runnables.clone(),
optimized_build: false,
file_versions: Default::default(),
uri: Some(uri.clone()),
uri: uri.clone(),
version: None,
gc_options: GarbageCollectionConfig::default(),
};
Expand All @@ -39,7 +45,7 @@ pub async fn compile_test_project() -> (Url, Arc<Session>, ServerState, Engines)

// Compile the project
session::parse_project(&temp_uri, &engines_clone, None, &ctx, lsp_mode.as_ref()).unwrap();
(temp_uri, session, state, engines_clone)
(temp_uri, session, state, engines_clone, sync)
}

pub fn sway_workspace_dir() -> PathBuf {
Expand Down
44 changes: 20 additions & 24 deletions sway-lsp/benches/lsp_benchmarks/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ use lsp_types::{
CompletionResponse, DocumentSymbolResponse, Position, Range, TextDocumentContentChangeEvent,
TextDocumentIdentifier,
};
use sway_lsp::{capabilities, lsp_ext::OnEnterParams};
use sway_lsp::{capabilities, core::session, lsp_ext::OnEnterParams};
use tokio::runtime::Runtime;

fn benchmarks(c: &mut Criterion) {
let (uri, session, state, engines) = Runtime::new()
let (uri, _, state, engines, sync) = Runtime::new()
.unwrap()
.block_on(async { black_box(super::compile_test_project().await) });
let sync = state.sync_workspace.get().unwrap();
let config = sway_lsp::config::Config::default();
let position = Position::new(1717, 24);
let range = Range::new(Position::new(1628, 0), Position::new(1728, 0));
Expand All @@ -21,46 +20,43 @@ fn benchmarks(c: &mut Criterion) {

c.bench_function("document_symbol", |b| {
b.iter(|| {
session
.document_symbols(&uri, &state.token_map, &engines)
session::document_symbols(&uri, &state.token_map, &engines, &state.compiled_programs)
.map(DocumentSymbolResponse::Nested)
})
});

c.bench_function("completion", |b| {
let position = Position::new(1698, 28);
b.iter(|| {
session
.completion_items(&uri, position, ".", &state.token_map, &engines)
.map(CompletionResponse::Array)
session::completion_items(
&uri,
position,
".",
&state.token_map,
&engines,
&state.compiled_programs,
)
.map(CompletionResponse::Array)
})
});

c.bench_function("hover", |b| {
b.iter(|| {
capabilities::hover::hover_data(&state, &engines, session.clone(), &uri, position)
})
b.iter(|| capabilities::hover::hover_data(&state, sync.clone(), &engines, &uri, position))
});

c.bench_function("highlight", |b| {
b.iter(|| {
capabilities::highlight::get_highlights(
session.clone(),
&engines,
&state.token_map,
&uri,
position,
)
capabilities::highlight::get_highlights(&engines, &state.token_map, &uri, position)
})
});

c.bench_function("find_all_references", |b| {
b.iter(|| session.token_references(&uri, position, &state.token_map, &engines, sync))
b.iter(|| session::token_references(&uri, position, &state.token_map, &engines, &sync))
});

c.bench_function("goto_definition", |b| {
b.iter(|| {
session.token_definition_response(&uri, position, &engines, &state.token_map, sync)
session::token_definition_response(&uri, position, &engines, &state.token_map, &sync)
})
});

Expand All @@ -78,7 +74,7 @@ fn benchmarks(c: &mut Criterion) {

c.bench_function("prepare_rename", |b| {
b.iter(|| {
capabilities::rename::prepare_rename(&engines, &state.token_map, &uri, position, sync)
capabilities::rename::prepare_rename(&engines, &state.token_map, &uri, position, &sync)
})
});

Expand All @@ -90,7 +86,7 @@ fn benchmarks(c: &mut Criterion) {
"new_token_name".to_string(),
&uri,
position,
sync,
&sync,
)
})
});
Expand All @@ -99,19 +95,19 @@ fn benchmarks(c: &mut Criterion) {
let range = Range::new(Position::new(4, 10), Position::new(4, 10));
b.iter(|| {
capabilities::code_actions::code_actions(
session.clone(),
&engines,
&state.token_map,
&range,
&uri,
&uri,
&vec![],
&state.compiled_programs,
)
})
});

c.bench_function("code_lens", |b| {
b.iter(|| capabilities::code_lens::code_lens(&session, &uri.clone()))
b.iter(|| capabilities::code_lens::code_lens(&state.runnables, &uri.clone()))
});

c.bench_function("on_enter", |b| {
Expand Down
3 changes: 1 addition & 2 deletions sway-lsp/benches/lsp_benchmarks/token_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ use lsp_types::Position;
use tokio::runtime::Runtime;

fn benchmarks(c: &mut Criterion) {
let (uri, _, state, engines) = Runtime::new()
let (uri, _, state, engines, sync) = Runtime::new()
.unwrap()
.block_on(async { black_box(super::compile_test_project().await) });
let sync = state.sync_workspace.get().unwrap();
let position = Position::new(1716, 24);
let path = uri.to_file_path().unwrap();
let program_id = sway_lsp::core::session::program_id_from_path(&path, &engines).unwrap();
Expand Down
22 changes: 12 additions & 10 deletions sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ pub(crate) fn get_call_paths_for_name<'s>(
ctx: &'s CodeActionContext,
symbol_name: &'s String,
) -> Option<impl 's + Iterator<Item = CallPath>> {
let namespace = ctx.namespace.to_owned()?;
let mut call_paths = ctx
.tokens
.tokens_for_name(symbol_name)
Expand All @@ -103,47 +102,50 @@ pub(crate) fn get_call_paths_for_name<'s>(
let struct_decl = ctx.engines.de().get_struct(&decl.decl_id);
let call_path = struct_decl
.call_path
.to_import_path(ctx.engines, &namespace);
.to_import_path(ctx.engines, ctx.namespace);
Some(call_path)
}
TyDecl::EnumDecl(decl) => {
let enum_decl = ctx.engines.de().get_enum(&decl.decl_id);
let call_path = enum_decl.call_path.to_import_path(ctx.engines, &namespace);
let call_path = enum_decl
.call_path
.to_import_path(ctx.engines, ctx.namespace);
Some(call_path)
}
TyDecl::TraitDecl(decl) => {
let trait_decl = ctx.engines.de().get_trait(&decl.decl_id);
let call_path =
trait_decl.call_path.to_import_path(ctx.engines, &namespace);
let call_path = trait_decl
.call_path
.to_import_path(ctx.engines, ctx.namespace);
Some(call_path)
}
TyDecl::FunctionDecl(decl) => {
let function_decl = ctx.engines.de().get_function(&decl.decl_id);
let call_path = function_decl
.call_path
.to_import_path(ctx.engines, &namespace);
.to_import_path(ctx.engines, ctx.namespace);
Some(call_path)
}
TyDecl::ConstantDecl(decl) => {
let constant_decl = ctx.engines.de().get_constant(&decl.decl_id);
let call_path = constant_decl
.call_path
.to_import_path(ctx.engines, &namespace);
.to_import_path(ctx.engines, ctx.namespace);
Some(call_path)
}
TyDecl::TypeAliasDecl(decl) => {
let type_alias_decl = ctx.engines.de().get_type_alias(&decl.decl_id);
let call_path = type_alias_decl
.call_path
.to_import_path(ctx.engines, &namespace);
.to_import_path(ctx.engines, ctx.namespace);
Some(call_path)
}
_ => None,
},
Some(TypedAstToken::TypedFunctionDeclaration(TyFunctionDecl {
call_path, ..
})) => {
let call_path = call_path.to_import_path(ctx.engines, &namespace);
let call_path = call_path.to_import_path(ctx.engines, ctx.namespace);
Some(call_path)
}
Some(TypedAstToken::TypedConstantDeclaration(TyConstantDecl {
Expand All @@ -153,7 +155,7 @@ pub(crate) fn get_call_paths_for_name<'s>(
call_path,
..
})) => {
let call_path = call_path.to_import_path(ctx.engines, &namespace);
let call_path = call_path.to_import_path(ctx.engines, ctx.namespace);
Some(call_path)
}
_ => None,
Expand Down
Loading
Loading