Skip to content

Commit 3ad9b16

Browse files
authored
Smart semicolon autocorrect (#3159)
- Introduce java.edit.smartSemicolonDetection.enabled Signed-off-by: Snjezana Peco <snjezana.peco@redhat.com>
1 parent a60b5e4 commit 3ad9b16

9 files changed

Lines changed: 141 additions & 4 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ New in 1.21.0
240240
- `ask`: Ask to reload the sources of the open class files
241241
- `auto`: Automatically reload the sources of the open class files
242242
- `manual`: Manually reload the sources of the open class files
243+
* `java.edit.smartSemicolonDetection.enabled`: Defines the `smart semicolon` detection. Defaults to `false`.
243244

244245
Semantic Highlighting
245246
===============

package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,12 @@
11401140
"markdownDescription": "Specifies whether to recheck all open Java files for diagnostics when editing a Java file.",
11411141
"scope": "window"
11421142
},
1143+
"java.edit.smartSemicolonDetection.enabled": {
1144+
"type": "boolean",
1145+
"default": false,
1146+
"markdownDescription": "Defines the `smart semicolon` detection. Defaults to `false`.",
1147+
"scope": "window"
1148+
},
11431149
"java.editor.reloadChangedSources": {
11441150
"type": "string",
11451151
"enum": [
@@ -1302,6 +1308,16 @@
13021308
"command": "java.server.restart",
13031309
"title": "%java.server.restart%",
13041310
"category": "Java"
1311+
},
1312+
{
1313+
"command": "java.edit.smartSemicolonDetection.command",
1314+
"title": "%java.edit.smartSemicolonDetection%",
1315+
"category": "Java"
1316+
},
1317+
{
1318+
"command": "java.edit.smartSemicolonDetectionUndo",
1319+
"title": "%java.edit.smartSemicolonDetectionUndo%",
1320+
"category": "Java"
13051321
}
13061322
],
13071323
"keybindings": [
@@ -1319,6 +1335,16 @@
13191335
"key": "ctrl+shift+v",
13201336
"mac": "cmd+shift+v",
13211337
"when": "javaLSReady && editorLangId == java"
1338+
},
1339+
{
1340+
"command": "java.edit.smartSemicolonDetection.command",
1341+
"key": ";",
1342+
"when": "editorFocus && javaLSReady && editorLangId == java"
1343+
},
1344+
{
1345+
"command": "java.edit.smartSemicolonDetectionUndo",
1346+
"key": "backspace",
1347+
"when": "editorFocus && javaLSReady && editorLangId == java"
13221348
}
13231349
],
13241350
"menus": {

package.nls.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,7 @@
2424
"java.action.changeBaseType": "Base on this Type",
2525
"java.project.createModuleInfo.command": "Create module-info.java",
2626
"java.clean.sharedIndexes": "Clean Shared Indexes",
27-
"java.server.restart": "Restart Java Language Server"
27+
"java.server.restart": "Restart Java Language Server",
28+
"java.edit.smartSemicolonDetection": "Java Smart Semicolon Detection",
29+
"java.edit.smartSemicolonDetectionUndo": "Java Smart Semicolon Detection Undo"
2830
}

src/commands.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,4 +335,15 @@ export namespace Commands {
335335
*/
336336
export const GET_DECOMPILED_SOURCE = "java.decompile";
337337

338+
/**
339+
* Smart semicolon detection.
340+
*/
341+
export const SMARTSEMICOLON_DETECTION = "java.edit.smartSemicolonDetection";
342+
export const SMARTSEMICOLON_DETECTION_CMD = "java.edit.smartSemicolonDetection.command";
343+
344+
/**
345+
* Smart semicolon detection backspace.
346+
*/
347+
export const SMARTSEMICOLON_DETECTION_UNDO = "java.edit.smartSemicolonDetectionUndo";
348+
338349
}

src/extension.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as fs from 'fs';
55
import * as fse from 'fs-extra';
66
import * as os from 'os';
77
import * as path from 'path';
8-
import { CodeActionContext, commands, ConfigurationTarget, Diagnostic, env, EventEmitter, ExtensionContext, extensions, IndentAction, InputBoxOptions, languages, RelativePattern, TextDocument, UIKind, Uri, ViewColumn, window, workspace, WorkspaceConfiguration, ProgressLocation } from 'vscode';
8+
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';
99
import { CancellationToken, CodeActionParams, CodeActionRequest, Command, DidChangeConfigurationNotification, ExecuteCommandParams, ExecuteCommandRequest, LanguageClientOptions, RevealOutputChannelOn } from 'vscode-languageclient';
1010
import { LanguageClient } from 'vscode-languageclient/node';
1111
import { apiManager } from './apiManager';
@@ -268,7 +268,6 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
268268
// the promise is resolved
269269
// no need to pass `resolve` into any code past this point,
270270
// since `resolve` is a no-op from now on
271-
272271
const serverOptions = prepareExecutable(requirements, syntaxServerWorkspacePath, getJavaConfig(requirements.java_home), context, true);
273272
if (requireSyntaxServer) {
274273
if (process.env['SYNTAXLS_CLIENT_PORT']) {

src/settings.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import * as fs from 'fs';
44
import * as path from 'path';
5-
import { commands, ConfigurationTarget, env, ExtensionContext, Position, Range, SnippetString, TextDocument, Uri, window, workspace, WorkspaceConfiguration, WorkspaceFolder } from 'vscode';
5+
import { commands, ConfigurationTarget, env, ExtensionContext, Position, Range, Selection, SnippetString, TextDocument, Uri, window, workspace, WorkspaceConfiguration, WorkspaceFolder } from 'vscode';
66
import { Commands } from './commands';
77
import { cleanupLombokCache } from './lombokSupport';
88
import { ensureExists, getJavaConfiguration } from './utils';
9+
import { apiManager } from './apiManager';
10+
import { setSmartSemiColonDetectionState } from './smartSemicolonDetection';
911

1012
const DEFAULT_HIDDEN_FILES: string[] = ['**/.classpath', '**/.project', '**/.settings', '**/.factorypath'];
1113
const IS_WORKSPACE_JDK_ALLOWED = "java.ls.isJdkAllowed";
@@ -329,6 +331,9 @@ export function handleTextBlockClosing(document: TextDocument, changes: readonly
329331
return;
330332
}
331333
if (lastChange.text !== '"""";') {
334+
if (lastChange.text !== ';') {
335+
setSmartSemiColonDetectionState(null, null);
336+
}
332337
return;
333338
}
334339
const selection = activeTextEditor.selection.active;
@@ -349,3 +354,5 @@ export function handleTextBlockClosing(document: TextDocument, changes: readonly
349354
}
350355
}
351356
}
357+
358+

src/smartSemicolonDetection.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use strict';
2+
3+
import { commands, ExtensionContext, Position, Range, Selection, window } from 'vscode';
4+
import { Commands } from './commands';
5+
import { getJavaConfiguration } from './utils';
6+
7+
let oldPosition: Position = null;
8+
let newPosition: Position = null;
9+
10+
export function registerSmartSemicolonDetection(context: ExtensionContext) {
11+
context.subscriptions.push(commands.registerCommand(Commands.SMARTSEMICOLON_DETECTION_CMD, async () => {
12+
if (!didSmartSemicolonInsertion() && enabled() && window.activeTextEditor!.document.fileName.endsWith(".java")) {
13+
const params: SmartDetectionParams = {
14+
uri: window.activeTextEditor.document.uri.toString(),
15+
position: window.activeTextEditor!.selection.active,
16+
};
17+
const response: SmartDetectionParams = await commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.SMARTSEMICOLON_DETECTION, JSON.stringify(params));
18+
if (response !== null) {
19+
window.activeTextEditor!.edit(editBuilder => {
20+
oldPosition = window.activeTextEditor!.selection.active;
21+
editBuilder.insert(response.position, ";");
22+
window.activeTextEditor.selections = [new Selection(response.position, response.position)];
23+
newPosition = window.activeTextEditor!.selection.active;
24+
});
25+
return;
26+
}
27+
}
28+
window.activeTextEditor!.edit(editBuilder => {
29+
editBuilder.insert(window.activeTextEditor!.selection.active, ";");
30+
});
31+
newPosition = null;
32+
oldPosition = null;
33+
}));
34+
context.subscriptions.push(commands.registerCommand(Commands.SMARTSEMICOLON_DETECTION_UNDO, async () => {
35+
if (didSmartSemicolonInsertion() && enabled()) {
36+
window.activeTextEditor!.edit(editBuilder => {
37+
editBuilder.insert(oldPosition, ";");
38+
const delRange = new Range(newPosition, new Position(newPosition.line, newPosition.character + 1));
39+
editBuilder.delete(delRange);
40+
window.activeTextEditor.selections = [new Selection(oldPosition, oldPosition)];
41+
oldPosition = null;
42+
newPosition = null;
43+
});
44+
return;
45+
}
46+
window.activeTextEditor!.edit(() => {
47+
commands.executeCommand("deleteLeft");
48+
});
49+
oldPosition = null;
50+
newPosition = null;
51+
}));
52+
}
53+
54+
interface SmartDetectionParams {
55+
uri: String;
56+
position: Position;
57+
}
58+
59+
function didSmartSemicolonInsertion() {
60+
const smartSemicolonInsertion = window.activeTextEditor.selections.length === 1 && enabled() && oldPosition !== null && newPosition !== null;
61+
if (smartSemicolonInsertion) {
62+
const active = window.activeTextEditor!.selection.active;
63+
const prev = new Position(active.line, active.character === 0 ? 0 : active.character - 1);
64+
return newPosition.isEqual(prev);
65+
}
66+
return smartSemicolonInsertion;
67+
}
68+
69+
function enabled() {
70+
return getJavaConfiguration().get<boolean>("edit.smartSemicolonDetection.enabled");
71+
}
72+
73+
export function setSmartSemiColonDetectionState(oldPos: Position, newPos: Position) {
74+
oldPosition = oldPos;
75+
newPos = newPos;
76+
}

src/standardLanguageClient.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { getAllJavaProjects, getJavaConfig, getJavaConfiguration } from "./utils
4040
import { Telemetry } from "./telemetry";
4141
import { TelemetryEvent } from "@redhat-developer/vscode-redhat-telemetry/lib";
4242
import { registerDocumentValidationListener } from './diagnostic';
43+
import { registerSmartSemicolonDetection } from './smartSemicolonDetection';
4344

4445
const extensionName = 'Language Support for Java';
4546
const GRADLE_CHECKSUM = "gradle/checksum/prompt";
@@ -137,6 +138,11 @@ export class StandardLanguageClient {
137138
// clients may not have properly configured documentPaste
138139
logger.error(error);
139140
}
141+
try {
142+
registerSmartSemicolonDetection(context);
143+
} catch (error) {
144+
logger.error(error);
145+
}
140146
activationProgressNotification.hide();
141147
if (!hasImported) {
142148
showImportFinishNotification(context);

test/standard-mode-suite/extension.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ suite('Java Language Extension - Standard', () => {
4242
return vscode.commands.getCommands(true).then((commands) =>
4343
{
4444
const JAVA_COMMANDS = [
45+
Commands.ADD_TO_SOURCEPATH,
4546
Commands.ADD_TO_SOURCEPATH_CMD,
4647
Commands.APPLY_REFACTORING_COMMAND,
4748
Commands.APPLY_WORKSPACE_EDIT,
@@ -53,6 +54,7 @@ suite('Java Language Extension - Standard', () => {
5354
Commands.CLIPBOARD_ONPASTE,
5455
Commands.COMPILE_WORKSPACE,
5556
Commands.CONFIGURATION_UPDATE,
57+
Commands.CREATE_MODULE_INFO,
5658
Commands.CREATE_MODULE_INFO_COMMAND,
5759
Commands.EXECUTE_WORKSPACE_COMMAND,
5860
Commands.GENERATE_ACCESSORS_PROMPT,
@@ -102,13 +104,20 @@ suite('Java Language Extension - Standard', () => {
102104
Commands.SHOW_SERVER_TASK_STATUS,
103105
Commands.SWITCH_SERVER_MODE,
104106
"java.edit.stringFormatting",
107+
"java.completion.onDidSelect",
108+
"java.decompile",
109+
"java.protobuf.generateSources",
105110
Commands.SHOW_TYPE_HIERARCHY,
106111
Commands.SHOW_SUBTYPE_HIERARCHY,
107112
Commands.SHOW_SUPERTYPE_HIERARCHY,
108113
Commands.SHOW_CLASS_HIERARCHY,
109114
Commands.UPGRADE_GRADLE_WRAPPER,
115+
Commands.UPGRADE_GRADLE_WRAPPER_CMD,
110116
Commands.UPDATE_SOURCE_ATTACHMENT,
111117
Commands.UPDATE_SOURCE_ATTACHMENT_CMD,
118+
Commands.SMARTSEMICOLON_DETECTION_CMD,
119+
Commands.SMARTSEMICOLON_DETECTION_UNDO,
120+
Commands.RESOLVE_SOURCE_ATTACHMENT,
112121
].sort();
113122
const foundJavaCommands = commands.filter((value) => {
114123
return JAVA_COMMANDS.indexOf(value)>=0 || value.startsWith('java.');

0 commit comments

Comments
 (0)