Add command for manually running flychecks

This commit is contained in:
Lukas Wirth 2022-12-16 22:43:14 +01:00
parent ba3e3282da
commit a04feb915a
7 changed files with 133 additions and 80 deletions

View file

@ -138,6 +138,19 @@ impl Request for CancelFlycheck {
const METHOD: &'static str = "rust-analyzer/cancelFlycheck"; const METHOD: &'static str = "rust-analyzer/cancelFlycheck";
} }
pub enum RunFlycheck {}
impl Notification for RunFlycheck {
type Params = RunFlycheckParams;
const METHOD: &'static str = "rust-analyzer/runFlycheck";
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RunFlycheckParams {
pub text_document: Option<TextDocumentIdentifier>,
}
pub enum MatchingBrace {} pub enum MatchingBrace {}
impl Request for MatchingBrace { impl Request for MatchingBrace {

View file

@ -703,6 +703,88 @@ impl GlobalState {
/// Handles an incoming notification. /// Handles an incoming notification.
fn on_notification(&mut self, not: Notification) -> Result<()> { fn on_notification(&mut self, not: Notification) -> Result<()> {
// FIXME: Move these implementations out into a module similar to on_request
fn run_flycheck(this: &mut GlobalState, vfs_path: VfsPath) -> bool {
let file_id = this.vfs.read().0.file_id(&vfs_path);
if let Some(file_id) = file_id {
let world = this.snapshot();
let mut updated = false;
let task = move || -> std::result::Result<(), ide::Cancelled> {
// Trigger flychecks for all workspaces that depend on the saved file
// Crates containing or depending on the saved file
let crate_ids: Vec<_> = world
.analysis
.crates_for(file_id)?
.into_iter()
.flat_map(|id| world.analysis.transitive_rev_deps(id))
.flatten()
.sorted()
.unique()
.collect();
let crate_root_paths: Vec<_> = crate_ids
.iter()
.filter_map(|&crate_id| {
world
.analysis
.crate_root(crate_id)
.map(|file_id| {
world
.file_id_to_file_path(file_id)
.as_path()
.map(ToOwned::to_owned)
})
.transpose()
})
.collect::<ide::Cancellable<_>>()?;
let crate_root_paths: Vec<_> =
crate_root_paths.iter().map(Deref::deref).collect();
// Find all workspaces that have at least one target containing the saved file
let workspace_ids =
world.workspaces.iter().enumerate().filter(|(_, ws)| match ws {
project_model::ProjectWorkspace::Cargo { cargo, .. } => {
cargo.packages().any(|pkg| {
cargo[pkg].targets.iter().any(|&it| {
crate_root_paths.contains(&cargo[it].root.as_path())
})
})
}
project_model::ProjectWorkspace::Json { project, .. } => project
.crates()
.any(|(c, _)| crate_ids.iter().any(|&crate_id| crate_id == c)),
project_model::ProjectWorkspace::DetachedFiles { .. } => false,
});
// Find and trigger corresponding flychecks
for flycheck in world.flycheck.iter() {
for (id, _) in workspace_ids.clone() {
if id == flycheck.id() {
updated = true;
flycheck.restart();
continue;
}
}
}
// No specific flycheck was triggered, so let's trigger all of them.
if !updated {
for flycheck in world.flycheck.iter() {
flycheck.restart();
}
}
Ok(())
};
this.task_pool.handle.spawn_with_sender(move |_| {
if let Err(e) = std::panic::catch_unwind(task) {
tracing::error!("flycheck task panicked: {e:?}")
}
});
true
} else {
false
}
}
NotificationDispatcher { not: Some(not), global_state: self } NotificationDispatcher { not: Some(not), global_state: self }
.on::<lsp_types::notification::Cancel>(|this, params| { .on::<lsp_types::notification::Cancel>(|this, params| {
let id: lsp_server::RequestId = match params.id { let id: lsp_server::RequestId = match params.id {
@ -782,6 +864,20 @@ impl GlobalState {
} }
Ok(()) Ok(())
})? })?
.on::<lsp_ext::RunFlycheck>(|this, params| {
if let Some(text_document) = params.text_document {
if let Ok(vfs_path) = from_proto::vfs_path(&text_document.uri) {
if run_flycheck(this, vfs_path) {
return Ok(());
}
}
}
// No specific flycheck was triggered, so let's trigger all of them.
for flycheck in this.flycheck.iter() {
flycheck.restart();
}
Ok(())
})?
.on::<lsp_types::notification::DidSaveTextDocument>(|this, params| { .on::<lsp_types::notification::DidSaveTextDocument>(|this, params| {
if let Ok(vfs_path) = from_proto::vfs_path(&params.text_document.uri) { if let Ok(vfs_path) = from_proto::vfs_path(&params.text_document.uri) {
// Re-fetch workspaces if a workspace related file has changed // Re-fetch workspaces if a workspace related file has changed
@ -792,82 +888,7 @@ impl GlobalState {
} }
} }
let file_id = this.vfs.read().0.file_id(&vfs_path); if run_flycheck(this, vfs_path) {
if let Some(file_id) = file_id {
let world = this.snapshot();
let mut updated = false;
let task = move || -> std::result::Result<(), ide::Cancelled> {
// Trigger flychecks for all workspaces that depend on the saved file
// Crates containing or depending on the saved file
let crate_ids: Vec<_> = world
.analysis
.crates_for(file_id)?
.into_iter()
.flat_map(|id| world.analysis.transitive_rev_deps(id))
.flatten()
.sorted()
.unique()
.collect();
let crate_root_paths: Vec<_> = crate_ids
.iter()
.filter_map(|&crate_id| {
world
.analysis
.crate_root(crate_id)
.map(|file_id| {
world
.file_id_to_file_path(file_id)
.as_path()
.map(ToOwned::to_owned)
})
.transpose()
})
.collect::<ide::Cancellable<_>>()?;
let crate_root_paths: Vec<_> =
crate_root_paths.iter().map(Deref::deref).collect();
// Find all workspaces that have at least one target containing the saved file
let workspace_ids =
world.workspaces.iter().enumerate().filter(|(_, ws)| match ws {
project_model::ProjectWorkspace::Cargo { cargo, .. } => {
cargo.packages().any(|pkg| {
cargo[pkg].targets.iter().any(|&it| {
crate_root_paths.contains(&cargo[it].root.as_path())
})
})
}
project_model::ProjectWorkspace::Json { project, .. } => {
project.crates().any(|(c, _)| {
crate_ids.iter().any(|&crate_id| crate_id == c)
})
}
project_model::ProjectWorkspace::DetachedFiles { .. } => false,
});
// Find and trigger corresponding flychecks
for flycheck in world.flycheck.iter() {
for (id, _) in workspace_ids.clone() {
if id == flycheck.id() {
updated = true;
flycheck.restart();
continue;
}
}
}
// No specific flycheck was triggered, so let's trigger all of them.
if !updated {
for flycheck in world.flycheck.iter() {
flycheck.restart();
}
}
Ok(())
};
this.task_pool.handle.spawn_with_sender(move |_| {
if let Err(e) = std::panic::catch_unwind(task) {
tracing::error!("DidSaveTextDocument flycheck task panicked: {e:?}")
}
});
return Ok(()); return Ok(());
} }
} }

View file

@ -1,5 +1,5 @@
<!--- <!---
lsp_ext.rs hash: 61fe425627f9baaa lsp_ext.rs hash: 1cb29d3afa36e743
If you need to change the above hash to make the test pass, please check if you If you need to change the above hash to make the test pass, please check if you
need to adjust this doc as well and ping this issue: need to adjust this doc as well and ping this issue:

View file

@ -246,6 +246,11 @@
"command": "rust-analyzer.cancelFlycheck", "command": "rust-analyzer.cancelFlycheck",
"title": "Cancel running flychecks", "title": "Cancel running flychecks",
"category": "rust-analyzer" "category": "rust-analyzer"
},
{
"command": "rust-analyzer.runFlycheck",
"title": "Run flycheck",
"category": "rust-analyzer"
} }
], ],
"keybindings": [ "keybindings": [

View file

@ -788,8 +788,17 @@ export function openDocs(ctx: CtxInit): Cmd {
export function cancelFlycheck(ctx: CtxInit): Cmd { export function cancelFlycheck(ctx: CtxInit): Cmd {
return async () => { return async () => {
await ctx.client.sendRequest(ra.cancelFlycheck);
};
}
export function runFlycheck(ctx: CtxInit): Cmd {
return async () => {
const editor = ctx.activeRustEditor;
const client = ctx.client; const client = ctx.client;
await client.sendRequest(ra.cancelFlycheck); const params = editor ? { uri: editor.document.uri.toString() } : null;
await client.sendNotification(ra.runFlycheck, { textDocument: params });
}; };
} }
@ -797,12 +806,12 @@ export function resolveCodeAction(ctx: CtxInit): Cmd {
return async (params: lc.CodeAction) => { return async (params: lc.CodeAction) => {
const client = ctx.client; const client = ctx.client;
params.command = undefined; params.command = undefined;
const item = await client?.sendRequest(lc.CodeActionResolveRequest.type, params); const item = await client.sendRequest(lc.CodeActionResolveRequest.type, params);
if (!item?.edit) { if (!item?.edit) {
return; return;
} }
const itemEdit = item.edit; const itemEdit = item.edit;
const edit = await client?.protocol2CodeConverter.asWorkspaceEdit(itemEdit); const edit = await client.protocol2CodeConverter.asWorkspaceEdit(itemEdit);
// filter out all text edits and recreate the WorkspaceEdit without them so we can apply // filter out all text edits and recreate the WorkspaceEdit without them so we can apply
// snippet edits on our own // snippet edits on our own
const lcFileSystemEdit = { const lcFileSystemEdit = {

View file

@ -81,6 +81,10 @@ export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, Te
export const cancelFlycheck = new lc.RequestType0<void, void>("rust-analyzer/cancelFlycheck"); export const cancelFlycheck = new lc.RequestType0<void, void>("rust-analyzer/cancelFlycheck");
export const runFlycheck = new lc.NotificationType<{
textDocument: lc.TextDocumentIdentifier | null;
}>("rust-analyzer/runFlycheck");
// Experimental extensions // Experimental extensions
export interface SsrParams { export interface SsrParams {

View file

@ -150,6 +150,7 @@ function createCommands(): Record<string, CommandFactory> {
moveItemUp: { enabled: commands.moveItemUp }, moveItemUp: { enabled: commands.moveItemUp },
moveItemDown: { enabled: commands.moveItemDown }, moveItemDown: { enabled: commands.moveItemDown },
cancelFlycheck: { enabled: commands.cancelFlycheck }, cancelFlycheck: { enabled: commands.cancelFlycheck },
runFlycheck: { enabled: commands.runFlycheck },
ssr: { enabled: commands.ssr }, ssr: { enabled: commands.ssr },
serverVersion: { enabled: commands.serverVersion }, serverVersion: { enabled: commands.serverVersion },
// Internal commands which are invoked by the server. // Internal commands which are invoked by the server.