Add debug/toolbar button to reload changed classes (#586)
* Add debug/toolbar button to reload changed classes Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Make tslint happy Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Show a warning when autobuild is disabled Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Address review comments Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
|
@ -7,7 +7,7 @@ src/**
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
gulpfile.js
|
gulpfile.js
|
||||||
.gitignore
|
.gitignore
|
||||||
images/**
|
images/docs/**
|
||||||
testprojects/**
|
testprojects/**
|
||||||
TestPlan.md
|
TestPlan.md
|
||||||
.github/**
|
.github/**
|
||||||
|
|
|
@ -95,7 +95,10 @@ Please also check the documentation of [Language Support for Java by Red Hat](ht
|
||||||
- `java.debug.settings.showQualifiedNames`: show fully qualified class names in "Variables" viewlet, defaults to `false`.
|
- `java.debug.settings.showQualifiedNames`: show fully qualified class names in "Variables" viewlet, defaults to `false`.
|
||||||
- `java.debug.settings.showLogicalStructure`: show the logical structure for the Collection and Map classes in "Variables" viewlet, defaults to `true`.
|
- `java.debug.settings.showLogicalStructure`: show the logical structure for the Collection and Map classes in "Variables" viewlet, defaults to `true`.
|
||||||
- `java.debug.settings.maxStringLength`: the maximum length of string displayed in "Variables" or "Debug Console" viewlet, the string longer than this length will be trimmed, defaults to `0` which means no trim is performed.
|
- `java.debug.settings.maxStringLength`: the maximum length of string displayed in "Variables" or "Debug Console" viewlet, the string longer than this length will be trimmed, defaults to `0` which means no trim is performed.
|
||||||
- `java.debug.settings.enableHotCodeReplace`: enable hot code replace for Java code. Make sure the auto build is not disabled for [VSCode Java](https://github.com/redhat-developer/vscode-java). See the [wiki page](https://github.com/Microsoft/vscode-java-debug/wiki/Hot-Code-Replace) for more information about usages and limitations.
|
- `java.debug.settings.hotCodeReplace`: Reload the changed Java classes during debugging, defaults to `manual`. Make sure `java.autobuild.enabled` is not disabled for [VSCode Java](https://github.com/redhat-developer/vscode-java). See the [wiki page](https://github.com/Microsoft/vscode-java-debug/wiki/Hot-Code-Replace) for more information about usages and limitations.
|
||||||
|
- manual - Click the toolbar to apply the changes.
|
||||||
|
- auto - Automatically apply the changes after compilation.
|
||||||
|
- never - Never apply the changes.
|
||||||
- `java.debug.settings.enableRunDebugCodeLens`: enable the code lens provider for the run and debug buttons over main entry points, defaults to `true`.
|
- `java.debug.settings.enableRunDebugCodeLens`: enable the code lens provider for the run and debug buttons over main entry points, defaults to `true`.
|
||||||
- `java.debug.settings.forceBuildBeforeLaunch`: force building the workspace before launching java program, defaults to `true`.
|
- `java.debug.settings.forceBuildBeforeLaunch`: force building the workspace before launching java program, defaults to `true`.
|
||||||
|
|
||||||
|
|
35
TestPlan.md
|
@ -299,20 +299,33 @@ anonymous
|
||||||
|
|
||||||
1. Open project `19.java9-app` in vscode.
|
1. Open project `19.java9-app` in vscode.
|
||||||
2. Follow gif to verify step filters feature.
|
2. Follow gif to verify step filters feature.
|
||||||

|

|
||||||
|
|
||||||
The new gif:
|
The new gif:
|
||||||

|

|
||||||
|
|
||||||
## Hot Code Replace
|
## Hot Code Replace
|
||||||
|
- Manually trigger hot code replace
|
||||||
1. Open project `24.hotCodeReplace` in vscode.
|
1. Open project `24.hotCodeReplace` in vscode.
|
||||||
2. Set breakpoints: NameProvider.java line 12; Person.java line 13
|
2. Set breakpoints: NameProvider.java line 12; Person.java line 13.
|
||||||
3. Press `F5` to start debug.
|
3. Press `F5` to start debug.
|
||||||
4. The program stopped at the Person.java line 13
|
4. The program stopped at the Person.java line 13.
|
||||||
5. Change the value of the line "old" to "new"
|
5. Change the value of the line "old" to "new", and save the document.
|
||||||
5. Save the document to trigger HCR. Check the breakpoint will stop at line 12
|
6. Click the "Hot Code Replace" icon in the debug toolbar to trigger HCR. Check the breakpoint will stop at line 12 .
|
||||||
6. Click F10 to step over, check the value of `res` on the debug view of local variable which should be `new`
|
7. Click F10 to step over, check the value of `res` on the debug view of local variable which should be `new`.
|
||||||
|
|
||||||
|
- Automatically trigger hot code replace
|
||||||
|
1. Repeat step 1 ~ 4 above.
|
||||||
|
2. Change `java.debug.settings.hotCodeReplace` to `auto`.
|
||||||
|
3. Change the value of the line "old" to "new", and save the document.
|
||||||
|
4. HCR will be automatically triggered. Check the breakpoint will stop at line 12 .
|
||||||
|
5. Click F10 to step over, check the value of `res` on the debug view of local variable which should be `new`.
|
||||||
|
|
||||||
|
- Disable hot code replace
|
||||||
|
1. Repeat step 1 ~ 4 above.
|
||||||
|
2. Change `java.debug.settings.hotCodeReplace` to `never`.
|
||||||
|
3. Change the value of the line "old" to "new", and save the document.
|
||||||
|
4. Click F10 to step over, check the value of `res` on the debug view of local variable which should be `old`.
|
||||||
|
|
||||||
## Conditional Breakpoints
|
## Conditional Breakpoints
|
||||||
|
|
||||||
|
@ -342,7 +355,7 @@ public class App
|
||||||
|
|
||||||
2. set conditional breakpoint on line 13 with condition `i ==1000`, F5 and wait the breakpoint to be hit
|
2. set conditional breakpoint on line 13 with condition `i ==1000`, F5 and wait the breakpoint to be hit
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
3. verify i equals 1000 in variable window.
|
3. verify i equals 1000 in variable window.
|
||||||
4. F5 and wait for program to exit.
|
4. F5 and wait for program to exit.
|
||||||
|
@ -406,7 +419,7 @@ Exception in thread "main" java.lang.IllegalStateException
|
||||||
2. Launch java debugger and continue your program.
|
2. Launch java debugger and continue your program.
|
||||||
3. When the logpoint code branch is hit, it just log the message to the console and doesn't stop your program.
|
3. When the logpoint code branch is hit, it just log the message to the console and doesn't stop your program.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Start without debugging
|
## Start without debugging
|
||||||
|
@ -472,7 +485,7 @@ Exception in thread "main" java.lang.IllegalStateException
|
||||||
```
|
```
|
||||||
4. Press F5 to verify the variables should be like this:
|
4. Press F5 to verify the variables should be like this:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Classpath shortener for long classpath project
|
## Classpath shortener for long classpath project
|
||||||
1. Open `longclasspath` project in VS Code.
|
1. Open `longclasspath` project in VS Code.
|
||||||
|
|
|
@ -45,7 +45,7 @@ This error indicates you are doing `Hot Code Replace`. The `Hot Code Replace` fe
|
||||||
|
|
||||||
### Try:
|
### Try:
|
||||||
1. Restart your application to apply the new changes. Or ignore the message, and continue to debug.
|
1. Restart your application to apply the new changes. Or ignore the message, and continue to debug.
|
||||||
2. You can disable the hot code replace feature by changing the user setting `"java.debug.settings.enableHotCodeReplace": false`.
|
2. You can disable the hot code replace feature by changing the user setting `"java.debug.settings.hotCodeReplace": "never"`.
|
||||||
|
|
||||||
## Please specify the host name and the port of the remote debuggee in the launch.json.
|
## Please specify the host name and the port of the remote debuggee in the launch.json.
|
||||||
### Reason:
|
### Reason:
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#F3A600;}
|
||||||
|
</style>
|
||||||
|
<title>lightning</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="lightning">
|
||||||
|
<path id="Combined-Shape" class="st0" d="M15,12l9,1l-9,19V20l-9-1l9-19V12z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 568 B |
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 281 KiB After Width: | Height: | Size: 281 KiB |
Before Width: | Height: | Size: 4.5 MiB After Width: | Height: | Size: 4.5 MiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
41
package.json
|
@ -14,7 +14,7 @@
|
||||||
"debugger"
|
"debugger"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.22.0"
|
"vscode": "^1.32.0"
|
||||||
},
|
},
|
||||||
"license": "SEE LICENSE IN LICENSE.txt",
|
"license": "SEE LICENSE IN LICENSE.txt",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -41,7 +41,31 @@
|
||||||
"javaExtensions": [
|
"javaExtensions": [
|
||||||
"./server/com.microsoft.java.debug.plugin-0.18.0.jar"
|
"./server/com.microsoft.java.debug.plugin-0.18.0.jar"
|
||||||
],
|
],
|
||||||
"commands": [],
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "java.debug.hotCodeReplace",
|
||||||
|
"title": "Hot Code Replace",
|
||||||
|
"icon": {
|
||||||
|
"dark": "images/commands/hot_code_replace.svg",
|
||||||
|
"light": "images/commands/hot_code_replace.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"menus": {
|
||||||
|
"debug/toolBar": [
|
||||||
|
{
|
||||||
|
"command": "java.debug.hotCodeReplace",
|
||||||
|
"group": "navigation@100",
|
||||||
|
"when": "inDebugMode && debugType == java && javaHotReload == 'manual'"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"commandPalette": [
|
||||||
|
{
|
||||||
|
"command": "java.debug.hotCodeReplace",
|
||||||
|
"when": "false"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"debuggers": [
|
"debuggers": [
|
||||||
{
|
{
|
||||||
"type": "java",
|
"type": "java",
|
||||||
|
@ -409,10 +433,15 @@
|
||||||
"description": "%java.debugger.configuration.maxStringLength.description%",
|
"description": "%java.debugger.configuration.maxStringLength.description%",
|
||||||
"default": 0
|
"default": 0
|
||||||
},
|
},
|
||||||
"java.debug.settings.enableHotCodeReplace": {
|
"java.debug.settings.hotCodeReplace": {
|
||||||
"type": "boolean",
|
"type": "string",
|
||||||
"description": "%java.debugger.configuration.enableHotCodeReplace.description%",
|
"default": "manual",
|
||||||
"default": true
|
"description": "%java.debugger.configuration.hotCodeReplace.description%",
|
||||||
|
"enum": [
|
||||||
|
"auto",
|
||||||
|
"manual",
|
||||||
|
"never"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"java.debug.settings.enableRunDebugCodeLens": {
|
"java.debug.settings.enableRunDebugCodeLens": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|
|
@ -5,6 +5,5 @@
|
||||||
"java.debugger.configuration.showStaticVariables.description": "Mostra variabili statiche nella scheda \"variabili\".",
|
"java.debugger.configuration.showStaticVariables.description": "Mostra variabili statiche nella scheda \"variabili\".",
|
||||||
"java.debugger.configuration.showQualifiedNames.description": "Mostra nome completo delle classi nella scheda \"variabili\".",
|
"java.debugger.configuration.showQualifiedNames.description": "Mostra nome completo delle classi nella scheda \"variabili\".",
|
||||||
"java.debugger.configuration.maxStringLength.description": "Lunghezza massima delle stringhe visualizzate nella scheda \"Variabili\" o \"Console di Debug\", stringhe più lunghe di questo numero verranno tagliate, se 0 nessun taglio viene eseguito.",
|
"java.debugger.configuration.maxStringLength.description": "Lunghezza massima delle stringhe visualizzate nella scheda \"Variabili\" o \"Console di Debug\", stringhe più lunghe di questo numero verranno tagliate, se 0 nessun taglio viene eseguito.",
|
||||||
"java.debugger.configuration.enableHotCodeReplace.description": "Attiva sostituzione hotcode per codice Java.",
|
|
||||||
"java.debugger.configuration.enableRunDebugCodeLens.description": "Abilitare i provider di lenti di codice run e debug sui metodi principali."
|
"java.debugger.configuration.enableRunDebugCodeLens.description": "Abilitare i provider di lenti di codice run e debug sui metodi principali."
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
"java.debugger.configuration.showQualifiedNames.description": "Show fully qualified class names in \"Variables\" viewlet.",
|
"java.debugger.configuration.showQualifiedNames.description": "Show fully qualified class names in \"Variables\" viewlet.",
|
||||||
"java.debugger.configuration.showLogicalStructure.description": "Show the logical structure for the Collection and Map classes in \"Variables\" viewlet.",
|
"java.debugger.configuration.showLogicalStructure.description": "Show the logical structure for the Collection and Map classes in \"Variables\" viewlet.",
|
||||||
"java.debugger.configuration.maxStringLength.description": "The maximum length of strings displayed in \"Variables\" or \"Debug Console\" viewlet, strings longer than this length will be trimmed, if 0 no trim is performed.",
|
"java.debugger.configuration.maxStringLength.description": "The maximum length of strings displayed in \"Variables\" or \"Debug Console\" viewlet, strings longer than this length will be trimmed, if 0 no trim is performed.",
|
||||||
"java.debugger.configuration.enableHotCodeReplace.description": "Enable hot code replace for Java code.",
|
"java.debugger.configuration.hotCodeReplace.description": "Reload the changed Java classes during debugging. Make sure 'java.autobuild.enabled' is not disabled.",
|
||||||
"java.debugger.configuration.enableRunDebugCodeLens.description": "Enable the run and debug code lens providers over main methods.",
|
"java.debugger.configuration.enableRunDebugCodeLens.description": "Enable the run and debug code lens providers over main methods.",
|
||||||
"java.debugger.configuration.forceBuildBeforeLaunch": "Force building the workspace before launching java program."
|
"java.debugger.configuration.forceBuildBeforeLaunch": "Force building the workspace before launching java program."
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
"java.debugger.configuration.showQualifiedNames.description": "在“变量”视图中显示类的全名。",
|
"java.debugger.configuration.showQualifiedNames.description": "在“变量”视图中显示类的全名。",
|
||||||
"java.debugger.configuration.showLogicalStructure.description": "在“变量”视图中显示Collection和Map类的逻辑结构。",
|
"java.debugger.configuration.showLogicalStructure.description": "在“变量”视图中显示Collection和Map类的逻辑结构。",
|
||||||
"java.debugger.configuration.maxStringLength.description": "设定“变量”或“调试控制台”视图中显示的字符串最大长度,长度超过部分将被剪掉。如果值为0,则不执行修剪。",
|
"java.debugger.configuration.maxStringLength.description": "设定“变量”或“调试控制台”视图中显示的字符串最大长度,长度超过部分将被剪掉。如果值为0,则不执行修剪。",
|
||||||
"java.debugger.configuration.enableHotCodeReplace.description": "为Java代码启用热代码替换。",
|
"java.debugger.configuration.hotCodeReplace.description": "在调试期间重新加载已更改的Java类。确保未禁用'java.autobuild.enabled'。",
|
||||||
"java.debugger.configuration.enableRunDebugCodeLens.description": "在main方法上启用CodeLens标记。",
|
"java.debugger.configuration.enableRunDebugCodeLens.description": "在main方法上启用CodeLens标记。",
|
||||||
"java.debugger.configuration.forceBuildBeforeLaunch": "在启动java程序之前强制编译整个工作空间。"
|
"java.debugger.configuration.forceBuildBeforeLaunch": "在启动java程序之前强制编译整个工作空间。"
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
import * as path from "path";
|
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentOperation } from "vscode-extension-telemetry-wrapper";
|
import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentOperation } from "vscode-extension-telemetry-wrapper";
|
||||||
import * as commands from "./commands";
|
import * as commands from "./commands";
|
||||||
|
@ -49,6 +48,29 @@ function initializeExtension(operationId: string, context: vscode.ExtensionConte
|
||||||
context.subscriptions.push(instrumentAndRegisterCommand("JavaDebug.SpecifyProgramArgs", async () => {
|
context.subscriptions.push(instrumentAndRegisterCommand("JavaDebug.SpecifyProgramArgs", async () => {
|
||||||
return specifyProgramArguments(context);
|
return specifyProgramArguments(context);
|
||||||
}));
|
}));
|
||||||
|
context.subscriptions.push(instrumentAndRegisterCommand("java.debug.hotCodeReplace", async (args: any) => {
|
||||||
|
const autobuildConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("java.autobuild");
|
||||||
|
if (!autobuildConfig.enabled) {
|
||||||
|
const ans = await vscode.window.showWarningMessage(
|
||||||
|
"The hot code replace feature requires you to enable the autobuild flag, do you want to enable it?",
|
||||||
|
"Yes", "No");
|
||||||
|
if (ans === "Yes") {
|
||||||
|
await autobuildConfig.update("enabled", true);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const debugSession: vscode.DebugSession = vscode.debug.activeDebugSession;
|
||||||
|
if (!debugSession) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vscode.window.withProgress({ location: vscode.ProgressLocation.Window }, async (progress) => {
|
||||||
|
progress.report({ message: "Applying code changes..." });
|
||||||
|
return await debugSession.customRequest("redefineClasses");
|
||||||
|
});
|
||||||
|
}));
|
||||||
initializeHotCodeReplace(context);
|
initializeHotCodeReplace(context);
|
||||||
context.subscriptions.push(vscode.debug.onDidReceiveDebugSessionCustomEvent((customEvent) => {
|
context.subscriptions.push(vscode.debug.onDidReceiveDebugSessionCustomEvent((customEvent) => {
|
||||||
const t = customEvent.session ? customEvent.session.type : undefined;
|
const t = customEvent.session ? customEvent.session.type : undefined;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
|
|
||||||
import * as anchor from "./anchor";
|
import * as anchor from "./anchor";
|
||||||
import { HCR_EVENT, JAVA_LANGID } from "./constants";
|
import { JAVA_LANGID } from "./constants";
|
||||||
import * as utility from "./utility";
|
import * as utility from "./utility";
|
||||||
|
|
||||||
const suppressedReasons: Set<string> = new Set();
|
const suppressedReasons: Set<string> = new Set();
|
||||||
|
@ -24,6 +24,12 @@ enum HcrChangeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initializeHotCodeReplace(context: vscode.ExtensionContext) {
|
export function initializeHotCodeReplace(context: vscode.ExtensionContext) {
|
||||||
|
vscode.commands.executeCommand("setContext", "javaHotReload", getHotReloadFlag());
|
||||||
|
vscode.workspace.onDidChangeConfiguration((event) => {
|
||||||
|
if (event.affectsConfiguration("java.debug.settings.hotCodeReplace")) {
|
||||||
|
vscode.commands.executeCommand("setContext", "javaHotReload", getHotReloadFlag());
|
||||||
|
}
|
||||||
|
});
|
||||||
context.subscriptions.push(vscode.debug.onDidTerminateDebugSession((session) => {
|
context.subscriptions.push(vscode.debug.onDidTerminateDebugSession((session) => {
|
||||||
const t = session ? session.type : undefined;
|
const t = session ? session.type : undefined;
|
||||||
if (t === JAVA_LANGID) {
|
if (t === JAVA_LANGID) {
|
||||||
|
@ -34,11 +40,13 @@ export function initializeHotCodeReplace(context: vscode.ExtensionContext) {
|
||||||
|
|
||||||
export function handleHotCodeReplaceCustomEvent(hcrEvent) {
|
export function handleHotCodeReplaceCustomEvent(hcrEvent) {
|
||||||
if (hcrEvent.body.changeType === HcrChangeType.BUILD_COMPLETE) {
|
if (hcrEvent.body.changeType === HcrChangeType.BUILD_COMPLETE) {
|
||||||
|
if (getHotReloadFlag() === "auto") {
|
||||||
return vscode.window.withProgress({ location: vscode.ProgressLocation.Window }, (progress) => {
|
return vscode.window.withProgress({ location: vscode.ProgressLocation.Window }, (progress) => {
|
||||||
progress.report({ message: "Applying code changes..." });
|
progress.report({ message: "Applying code changes..." });
|
||||||
return hcrEvent.session.customRequest("redefineClasses");
|
return hcrEvent.session.customRequest("redefineClasses");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hcrEvent.body.changeType === HcrChangeType.ERROR || hcrEvent.body.changeType === HcrChangeType.WARNING) {
|
if (hcrEvent.body.changeType === HcrChangeType.ERROR || hcrEvent.body.changeType === HcrChangeType.WARNING) {
|
||||||
if (!suppressedReasons.has(hcrEvent.body.message)) {
|
if (!suppressedReasons.has(hcrEvent.body.message)) {
|
||||||
|
@ -55,3 +63,7 @@ export function handleHotCodeReplaceCustomEvent(hcrEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getHotReloadFlag(): string {
|
||||||
|
return vscode.workspace.getConfiguration("java.debug.settings").get("hotCodeReplace") || "manual";
|
||||||
|
}
|
||||||
|
|