diff --git a/README.md b/README.md index d5490c54b..a069f0343 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,7 @@ New in 1.21.0 - `ask`: Ask to reload the sources of the open class files - `auto`: Automatically reload the sources of the open class files - `manual`: Manually reload the sources of the open class files +* `java.edit.smartSemicolonDetection.enabled`: Defines the `smart semicolon` detection. Defaults to `false`. Semantic Highlighting =============== diff --git a/package.json b/package.json index fd9f0828b..62edf78fc 100644 --- a/package.json +++ b/package.json @@ -1140,6 +1140,12 @@ "markdownDescription": "Specifies whether to recheck all open Java files for diagnostics when editing a Java file.", "scope": "window" }, + "java.edit.smartSemicolonDetection.enabled": { + "type": "boolean", + "default": false, + "markdownDescription": "Defines the `smart semicolon` detection. Defaults to `false`.", + "scope": "window" + }, "java.editor.reloadChangedSources": { "type": "string", "enum": [ @@ -1302,6 +1308,16 @@ "command": "java.server.restart", "title": "%java.server.restart%", "category": "Java" + }, + { + "command": "java.edit.smartSemicolonDetection.command", + "title": "%java.edit.smartSemicolonDetection%", + "category": "Java" + }, + { + "command": "java.edit.smartSemicolonDetectionUndo", + "title": "%java.edit.smartSemicolonDetectionUndo%", + "category": "Java" } ], "keybindings": [ @@ -1319,6 +1335,16 @@ "key": "ctrl+shift+v", "mac": "cmd+shift+v", "when": "javaLSReady && editorLangId == java" + }, + { + "command": "java.edit.smartSemicolonDetection.command", + "key": ";", + "when": "editorFocus && javaLSReady && editorLangId == java" + }, + { + "command": "java.edit.smartSemicolonDetectionUndo", + "key": "backspace", + "when": "editorFocus && javaLSReady && editorLangId == java" } ], "menus": { diff --git a/package.nls.json b/package.nls.json index 0d8099ad7..38b94f77a 100644 --- a/package.nls.json +++ b/package.nls.json @@ -24,5 +24,7 @@ "java.action.changeBaseType": "Base on this Type", "java.project.createModuleInfo.command": "Create module-info.java", "java.clean.sharedIndexes": "Clean Shared Indexes", - "java.server.restart": "Restart Java Language Server" + "java.server.restart": "Restart Java Language Server", + "java.edit.smartSemicolonDetection": "Java Smart Semicolon Detection", + "java.edit.smartSemicolonDetectionUndo": "Java Smart Semicolon Detection Undo" } diff --git a/src/commands.ts b/src/commands.ts index 51d4cbdf9..2f185f9f1 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -335,4 +335,15 @@ export namespace Commands { */ export const GET_DECOMPILED_SOURCE = "java.decompile"; + /** + * Smart semicolon detection. + */ + export const SMARTSEMICOLON_DETECTION = "java.edit.smartSemicolonDetection"; + export const SMARTSEMICOLON_DETECTION_CMD = "java.edit.smartSemicolonDetection.command"; + + /** + * Smart semicolon detection backspace. + */ + export const SMARTSEMICOLON_DETECTION_UNDO = "java.edit.smartSemicolonDetectionUndo"; + } diff --git a/src/extension.ts b/src/extension.ts index 1cdf1458c..e7da495d0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import * as fse from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; -import { CodeActionContext, commands, ConfigurationTarget, Diagnostic, env, EventEmitter, ExtensionContext, extensions, IndentAction, InputBoxOptions, languages, RelativePattern, TextDocument, UIKind, Uri, ViewColumn, window, workspace, WorkspaceConfiguration, ProgressLocation } from 'vscode'; +import { CodeActionContext, commands, ConfigurationTarget, Diagnostic, env, EventEmitter, ExtensionContext, extensions, IndentAction, InputBoxOptions, languages, RelativePattern, TextDocument, UIKind, Uri, ViewColumn, window, workspace, WorkspaceConfiguration, ProgressLocation, Position, Selection, Range } from 'vscode'; import { CancellationToken, CodeActionParams, CodeActionRequest, Command, DidChangeConfigurationNotification, ExecuteCommandParams, ExecuteCommandRequest, LanguageClientOptions, RevealOutputChannelOn } from 'vscode-languageclient'; import { LanguageClient } from 'vscode-languageclient/node'; import { apiManager } from './apiManager'; @@ -268,7 +268,6 @@ export function activate(context: ExtensionContext): Promise { // the promise is resolved // no need to pass `resolve` into any code past this point, // since `resolve` is a no-op from now on - const serverOptions = prepareExecutable(requirements, syntaxServerWorkspacePath, getJavaConfig(requirements.java_home), context, true); if (requireSyntaxServer) { if (process.env['SYNTAXLS_CLIENT_PORT']) { diff --git a/src/settings.ts b/src/settings.ts index 56f1089cf..2959dbc18 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -2,10 +2,12 @@ import * as fs from 'fs'; import * as path from 'path'; -import { commands, ConfigurationTarget, env, ExtensionContext, Position, Range, SnippetString, TextDocument, Uri, window, workspace, WorkspaceConfiguration, WorkspaceFolder } from 'vscode'; +import { commands, ConfigurationTarget, env, ExtensionContext, Position, Range, Selection, SnippetString, TextDocument, Uri, window, workspace, WorkspaceConfiguration, WorkspaceFolder } from 'vscode'; import { Commands } from './commands'; import { cleanupLombokCache } from './lombokSupport'; import { ensureExists, getJavaConfiguration } from './utils'; +import { apiManager } from './apiManager'; +import { setSmartSemiColonDetectionState } from './smartSemicolonDetection'; const DEFAULT_HIDDEN_FILES: string[] = ['**/.classpath', '**/.project', '**/.settings', '**/.factorypath']; const IS_WORKSPACE_JDK_ALLOWED = "java.ls.isJdkAllowed"; @@ -329,6 +331,9 @@ export function handleTextBlockClosing(document: TextDocument, changes: readonly return; } if (lastChange.text !== '"""";') { + if (lastChange.text !== ';') { + setSmartSemiColonDetectionState(null, null); + } return; } const selection = activeTextEditor.selection.active; @@ -349,3 +354,5 @@ export function handleTextBlockClosing(document: TextDocument, changes: readonly } } } + + diff --git a/src/smartSemicolonDetection.ts b/src/smartSemicolonDetection.ts new file mode 100644 index 000000000..cecf2600e --- /dev/null +++ b/src/smartSemicolonDetection.ts @@ -0,0 +1,76 @@ +'use strict'; + +import { commands, ExtensionContext, Position, Range, Selection, window } from 'vscode'; +import { Commands } from './commands'; +import { getJavaConfiguration } from './utils'; + +let oldPosition: Position = null; +let newPosition: Position = null; + +export function registerSmartSemicolonDetection(context: ExtensionContext) { + context.subscriptions.push(commands.registerCommand(Commands.SMARTSEMICOLON_DETECTION_CMD, async () => { + if (!didSmartSemicolonInsertion() && enabled() && window.activeTextEditor!.document.fileName.endsWith(".java")) { + const params: SmartDetectionParams = { + uri: window.activeTextEditor.document.uri.toString(), + position: window.activeTextEditor!.selection.active, + }; + const response: SmartDetectionParams = await commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.SMARTSEMICOLON_DETECTION, JSON.stringify(params)); + if (response !== null) { + window.activeTextEditor!.edit(editBuilder => { + oldPosition = window.activeTextEditor!.selection.active; + editBuilder.insert(response.position, ";"); + window.activeTextEditor.selections = [new Selection(response.position, response.position)]; + newPosition = window.activeTextEditor!.selection.active; + }); + return; + } + } + window.activeTextEditor!.edit(editBuilder => { + editBuilder.insert(window.activeTextEditor!.selection.active, ";"); + }); + newPosition = null; + oldPosition = null; + })); + context.subscriptions.push(commands.registerCommand(Commands.SMARTSEMICOLON_DETECTION_UNDO, async () => { + if (didSmartSemicolonInsertion() && enabled()) { + window.activeTextEditor!.edit(editBuilder => { + editBuilder.insert(oldPosition, ";"); + const delRange = new Range(newPosition, new Position(newPosition.line, newPosition.character + 1)); + editBuilder.delete(delRange); + window.activeTextEditor.selections = [new Selection(oldPosition, oldPosition)]; + oldPosition = null; + newPosition = null; + }); + return; + } + window.activeTextEditor!.edit(() => { + commands.executeCommand("deleteLeft"); + }); + oldPosition = null; + newPosition = null; + })); +} + +interface SmartDetectionParams { + uri: String; + position: Position; +} + +function didSmartSemicolonInsertion() { + const smartSemicolonInsertion = window.activeTextEditor.selections.length === 1 && enabled() && oldPosition !== null && newPosition !== null; + if (smartSemicolonInsertion) { + const active = window.activeTextEditor!.selection.active; + const prev = new Position(active.line, active.character === 0 ? 0 : active.character - 1); + return newPosition.isEqual(prev); + } + return smartSemicolonInsertion; +} + +function enabled() { + return getJavaConfiguration().get("edit.smartSemicolonDetection.enabled"); +} + +export function setSmartSemiColonDetectionState(oldPos: Position, newPos: Position) { + oldPosition = oldPos; + newPos = newPos; +} \ No newline at end of file diff --git a/src/standardLanguageClient.ts b/src/standardLanguageClient.ts index 99ea44a1f..a4506166b 100644 --- a/src/standardLanguageClient.ts +++ b/src/standardLanguageClient.ts @@ -40,6 +40,7 @@ import { getAllJavaProjects, getJavaConfig, getJavaConfiguration } from "./utils import { Telemetry } from "./telemetry"; import { TelemetryEvent } from "@redhat-developer/vscode-redhat-telemetry/lib"; import { registerDocumentValidationListener } from './diagnostic'; +import { registerSmartSemicolonDetection } from './smartSemicolonDetection'; const extensionName = 'Language Support for Java'; const GRADLE_CHECKSUM = "gradle/checksum/prompt"; @@ -137,6 +138,11 @@ export class StandardLanguageClient { // clients may not have properly configured documentPaste logger.error(error); } + try { + registerSmartSemicolonDetection(context); + } catch (error) { + logger.error(error); + } activationProgressNotification.hide(); if (!hasImported) { showImportFinishNotification(context); diff --git a/test/standard-mode-suite/extension.test.ts b/test/standard-mode-suite/extension.test.ts index 284b97c04..98506f547 100644 --- a/test/standard-mode-suite/extension.test.ts +++ b/test/standard-mode-suite/extension.test.ts @@ -42,6 +42,7 @@ suite('Java Language Extension - Standard', () => { return vscode.commands.getCommands(true).then((commands) => { const JAVA_COMMANDS = [ + Commands.ADD_TO_SOURCEPATH, Commands.ADD_TO_SOURCEPATH_CMD, Commands.APPLY_REFACTORING_COMMAND, Commands.APPLY_WORKSPACE_EDIT, @@ -53,6 +54,7 @@ suite('Java Language Extension - Standard', () => { Commands.CLIPBOARD_ONPASTE, Commands.COMPILE_WORKSPACE, Commands.CONFIGURATION_UPDATE, + Commands.CREATE_MODULE_INFO, Commands.CREATE_MODULE_INFO_COMMAND, Commands.EXECUTE_WORKSPACE_COMMAND, Commands.GENERATE_ACCESSORS_PROMPT, @@ -102,13 +104,20 @@ suite('Java Language Extension - Standard', () => { Commands.SHOW_SERVER_TASK_STATUS, Commands.SWITCH_SERVER_MODE, "java.edit.stringFormatting", + "java.completion.onDidSelect", + "java.decompile", + "java.protobuf.generateSources", Commands.SHOW_TYPE_HIERARCHY, Commands.SHOW_SUBTYPE_HIERARCHY, Commands.SHOW_SUPERTYPE_HIERARCHY, Commands.SHOW_CLASS_HIERARCHY, Commands.UPGRADE_GRADLE_WRAPPER, + Commands.UPGRADE_GRADLE_WRAPPER_CMD, Commands.UPDATE_SOURCE_ATTACHMENT, Commands.UPDATE_SOURCE_ATTACHMENT_CMD, + Commands.SMARTSEMICOLON_DETECTION_CMD, + Commands.SMARTSEMICOLON_DETECTION_UNDO, + Commands.RESOLVE_SOURCE_ATTACHMENT, ].sort(); const foundJavaCommands = commands.filter((value) => { return JAVA_COMMANDS.indexOf(value)>=0 || value.startsWith('java.');