diff --git a/packages/metals-vscode/icons/hot_code_replace.svg b/packages/metals-vscode/icons/hot_code_replace.svg new file mode 100644 index 000000000..3e0704763 --- /dev/null +++ b/packages/metals-vscode/icons/hot_code_replace.svg @@ -0,0 +1,13 @@ + + + + +lightning +Created with Sketch. + + + + diff --git a/packages/metals-vscode/package.json b/packages/metals-vscode/package.json index 119eeb897..8768c11c5 100644 --- a/packages/metals-vscode/package.json +++ b/packages/metals-vscode/package.json @@ -422,6 +422,14 @@ } }, "commands": [ + { + "command": "metals.debug.hotCodeReplace", + "title": "Hot Code Replace", + "icon": { + "light": "icons/hot_code_replace.svg", + "dark": "icons/hot_code_replace.svg" + } + }, { "command": "metals.reveal-active-file", "category": "Metals", @@ -695,6 +703,13 @@ "when": "view == metalsPackages" } ], + "debug/toolBar": [ + { + "command": "metals.debug.hotCodeReplace", + "group": "navigation@100", + "when": "scalaHotReloadOn" + } + ], "commandPalette": [ { "command": "metals.show-tasty", diff --git a/packages/metals-vscode/src/debugger/hotCodeReplace.ts b/packages/metals-vscode/src/debugger/hotCodeReplace.ts new file mode 100644 index 000000000..fb346cfea --- /dev/null +++ b/packages/metals-vscode/src/debugger/hotCodeReplace.ts @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +// Adapted from https://github.com/microsoft/vscode-java-debug/blob/main/src/hotCodeReplace.ts + +import * as vscode from "vscode"; + +import { DebugSession, commands } from "vscode"; + +const HCR_ACTIVE = "scalaHotReloadOn"; + +export function initializeHotCodeReplace() { + vscode.debug.onDidStartDebugSession((session) => { + if (session?.configuration.noDebug && !vscode.debug.activeDebugSession) { + vscode.commands.executeCommand("setContext", HCR_ACTIVE, false); + } + }); + vscode.debug.onDidChangeActiveDebugSession((session) => { + vscode.commands.executeCommand( + "setContext", + HCR_ACTIVE, + session && !session.configuration.noDebug + ); + }); +} + +export async function applyHCR() { + const debugSession: DebugSession | undefined = + vscode.debug.activeDebugSession; + if (!debugSession) { + return; + } + + if (debugSession.configuration.noDebug) { + vscode.window + .showWarningMessage( + "Failed to apply the changes because hot code replace is not supported by run mode, " + + "would you like to restart the program?" + ) + .then((res) => { + if (res === "Yes") { + vscode.commands.executeCommand("workbench.action.debug.restart"); + } + }); + + return; + } + + await commands.executeCommand("workbench.action.files.save"); + const redefineRequest = debugSession.customRequest("redefineClasses"); + vscode.window.setStatusBarMessage( + "$(sync~spin) Applying code changes...", + redefineRequest + ); + const response = await redefineRequest; + + if (response?.errorMessage) { + vscode.window.showErrorMessage(response.errorMessage); + return; + } + + if (!response?.changedClasses?.length) { + vscode.window.showWarningMessage( + "No classes were reloaded, please check the logs" + ); + return; + } + + const changed = response.changedClasses.length; + vscode.window.setStatusBarMessage( + `$(check) ${changed} Class${changed > 1 ? "es" : ""} reloaded`, + 5 * 1000 + ); +} diff --git a/packages/metals-vscode/src/extension.ts b/packages/metals-vscode/src/extension.ts index 9f5788520..0ae86e169 100644 --- a/packages/metals-vscode/src/extension.ts +++ b/packages/metals-vscode/src/extension.ts @@ -105,6 +105,7 @@ import { SCALA_LANGID, } from "./consts"; import { ScalaCodeLensesParams } from "./debugger/types"; +import { applyHCR, initializeHotCodeReplace } from "./debugger/hotCodeReplace"; const outputChannel = window.createOutputChannel("Metals"); const downloadJava = "Download Java"; @@ -1188,6 +1189,10 @@ function launchMetals( } ); context.subscriptions.push(decorationsRangesDidChangeDispoasable); + registerCommand("metals.debug.hotCodeReplace", () => { + applyHCR(); + }); + initializeHotCodeReplace(); }, (reason) => { if (reason instanceof Error) {