Skip to content

Smart semicolon autocorrect#3159

Merged
rgrunber merged 2 commits into
redhat-developer:masterfrom
snjeza:issue-703
Jul 24, 2023
Merged

Smart semicolon autocorrect#3159
rgrunber merged 2 commits into
redhat-developer:masterfrom
snjeza:issue-703

Conversation

@snjeza

@snjeza snjeza commented Jun 19, 2023

Copy link
Copy Markdown
Collaborator

@rgrunber

rgrunber commented Jun 19, 2023

Copy link
Copy Markdown
Member

A few things :

I noticed that for a larger project, there is a point where one can edit a source file, while the language server is still loading. Maybe we need only register the capability under ServiceReady (like we do for paste delegate command) ? What ends up happening is the delegate commands are sent but do not produce a response. If a user types ; during this time, the character is completely ignored for a few seconds.

[Info  - 14:02:19] Jun. 19, 2023, 2:02:18 p.m. >> document/foldingRange
[Trace - 14:02:19] Sending request 'workspace/executeCommand - (29)'.
Params: {
    "command": "java.action.smartSemicolonDetection",
    "arguments": [
        "{\"uri\":\"file:///home/rgrunber/git/lemminx/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java\",\"position\":{\"line\":328,\"character\":24}}"
    ]
}
...
...
(~2s later)
...
...
[Trace - 14:02:21] Received response 'workspace/executeCommand - (29)' in 2122ms.
Result: {
    "uri": "file:///home/rgrunber/git/lemminx/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java",
    "position": {
        "line": 328,
        "character": 51
    }
}

Some other odd cases. Can we handle any of these ?

text-block-smart-insert-semicolon

multi-line-smart-insert-semicolon

@snjeza

snjeza commented Jun 20, 2023

Copy link
Copy Markdown
Collaborator Author

I noticed that for a larger project, there is a point where one can edit a source file, while the language server is still loading. Maybe we need only register the capability under ServiceReady (like we do for paste delegate command) ? What ends up happening is the delegate commands are sent but do not produce a response. If a user types ; during this time, the character is completely ignored for a few seconds.

Fixed

@snjeza

snjeza commented Jun 20, 2023

Copy link
Copy Markdown
Collaborator Author

Some other odd cases. Can we handle any of these ?

It is related to eclipse-jdtls/eclipse.jdt.ls#2710. Checking...

@hopehadfield

Copy link
Copy Markdown
Member

Peek 2023-06-22 16-57

I was trying it out and noticed this case where the semicolon doesn't autocorrect. Is this something we can handle?

@snjeza

snjeza commented Jun 22, 2023

Copy link
Copy Markdown
Collaborator Author

I was trying it out and noticed this case where the semicolon doesn't autocorrect. Is this something we can handle?

I will try.
It has been fixed.

@snjeza snjeza requested a review from hopehadfield June 22, 2023 23:10
@snjeza snjeza force-pushed the issue-703 branch 3 times, most recently from 0061d14 to d4e4d21 Compare June 23, 2023 13:29
@snjeza

snjeza commented Jun 23, 2023

Copy link
Copy Markdown
Collaborator Author

Some other odd cases. Can we handle any of these ?

@rgrunber Could you, please, check it again.

I have also implemented the backspace char the same way as in Eclipse.
smart2

@snjeza

snjeza commented Jun 23, 2023

Copy link
Copy Markdown
Collaborator Author

The remaining issues:

smart3

@snjeza

snjeza commented Jun 23, 2023

Copy link
Copy Markdown
Collaborator Author

The remaining issues:

It has been fixed.
I have copied FastJavaPartitionScanner and dependent classes -eclipse-jdtls/eclipse.jdt.ls@6202173
@fbricon @rgrunber We could move them from o.e.jdt.ui/o.e.jface.text to o.e.jdt.core.manipulation/o.e.text.

@rgrunber rgrunber left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, looks good. Just some questions of style and where to place things.

I also noticed the following
inconsistent-semicolon

I'm open to merging even with this as I suspect it might be some misbehaviour in the partition logic on the server-side.

Comment thread src/settings.ts Outdated
}
}

let serverReady = false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using this variable here because you're trying to correctly handle the semicolon in the case of the text blocks also ?

I would have really like to move this into

registerPasteEventHandler(context, this.languageClient);
if there was a nice way to do so.

Would it make sense to create a helper method that sets :

oldPosition = null;
newPosition = null;

so we can move the "smart semicolon" code into the standard client's onReady callback ?

@snjeza snjeza Jul 18, 2023

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using this variable here because you're trying to correctly handle the semicolon in the case of the text blocks also ?

No, it isn't related to the text block. We are waiting for the server to be ready.

I would have really like to move this into

I have tried it, but I didn't succeed.

@rgrunber rgrunber Jul 20, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an issue with the following ? Basically I just moved all the smart semicolon logic into a new file, smartSemicolonDetection.ts. I made the oldPostion/newPosition variables global in there as well and created a helper function (setSmartSemiColonDetectionState(old, new)) that can be used to access them from the text block logic. I also called registerSmartSemicolonDetection(..) directly in the standardLanguageClient.ts code where we listen for the ServiceReady notification.

patch
diff --git a/src/extension.ts b/src/extension.ts
index 90fc982..d0de894 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -24,7 +24,7 @@ import { initialize as initializeRecommendation } from './recommendation';
 import * as requirements from './requirements';
 import { runtimeStatusBarProvider } from './runtimeStatusBarProvider';
 import { serverStatusBarProvider } from './serverStatusBarProvider';
-import { ACTIVE_BUILD_TOOL_STATE, cleanWorkspaceFileName, getJavaServerMode, handleTextBlockClosing, onConfigurationChange, registerSmartSemicolonDetection, ServerMode } from './settings';
+import { ACTIVE_BUILD_TOOL_STATE, cleanWorkspaceFileName, getJavaServerMode, handleTextBlockClosing, onConfigurationChange, ServerMode } from './settings';
 import { snippetCompletionProvider } from './snippetCompletionProvider';
 import { JavaClassEditorProvider } from './javaClassEditor';
 import { StandardLanguageClient } from './standardLanguageClient';
@@ -35,6 +35,7 @@ import { Telemetry } from './telemetry';
 import { getMessage } from './errorUtils';
 import { TelemetryService } from '@redhat-developer/vscode-redhat-telemetry/lib';
 import { activationProgressNotification } from "./serverTaskPresenter";
+import { registerSmartSemicolonDetection } from './smartSemicolonDetection';
 
 const syntaxClient: SyntaxLanguageClient = new SyntaxLanguageClient();
 const standardClient: StandardLanguageClient = new StandardLanguageClient();
diff --git a/src/settings.ts b/src/settings.ts
index 8cd580f..091c8ee 100644
--- a/src/settings.ts
+++ b/src/settings.ts
@@ -7,6 +7,7 @@ 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";
@@ -29,9 +30,6 @@ export const ORGANIZE_IMPORTS_ON_PASTE = 'actionsOnPaste.organizeImports'; // ja
 let oldConfig: WorkspaceConfiguration = getJavaConfiguration();
 const gradleWrapperPromptDialogs = [];
 
-let oldPosition: Position = null;
-let newPosition: Position = null;
-
 export function onConfigurationChange(workspacePath: string, context: ExtensionContext) {
 	return workspace.onDidChangeConfiguration(params => {
 		if (!params.affectsConfiguration('java')) {
@@ -334,8 +332,7 @@ export function handleTextBlockClosing(document: TextDocument, changes: readonly
 	}
 	if (lastChange.text !== '"""";') {
 		if (lastChange.text !== ';') {
-			oldPosition = null;
-			newPosition = null;
+			setSmartSemiColonDetectionState(null, null);
 		}
 		return;
 	}
@@ -358,68 +355,3 @@ export function handleTextBlockClosing(document: TextDocument, changes: readonly
 	}
 }
 
-let serverReady = false;
-
-export function registerSmartSemicolonDetection(context: ExtensionContext) {
-	apiManager.getApiInstance().serverReady().then(() => {
-		serverReady = true;
-	});
-	context.subscriptions.push(commands.registerCommand(Commands.SMARTSEMICOLON_DETECTION_CMD, async () => {
-		if (!isSemichar() && 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_SEMICHAR, async () => {
-		if (isSemichar()) {
-			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 isSemichar() {
-	const enabled = getJavaConfiguration().get<boolean>("edit.smartSemicolonDetection");
-	const isSemichar = serverReady && window.activeTextEditor.selections.length === 1 && enabled && oldPosition !== null && newPosition !== null;
-	if (isSemichar) {
-		const active = window.activeTextEditor!.selection.active;
-		const prev = new Position(active.line, active.character === 0 ? 0 : active.character - 1);
-		return newPosition.isEqual(prev);
-	}
-	return isSemichar;
-}
-
diff --git a/src/smartSemicolonDetection.ts b/src/smartSemicolonDetection.ts
new file mode 100644
index 0000000..fceb270
--- /dev/null
+++ b/src/smartSemicolonDetection.ts
@@ -0,0 +1,73 @@
+'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 (!isSemichar() && 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_SEMICHAR, async () => {
+		if (isSemichar()) {
+			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 isSemichar() {
+	const enabled = getJavaConfiguration().get<boolean>("edit.smartSemicolonDetection");
+	const isSemichar = window.activeTextEditor.selections.length === 1 && enabled && oldPosition !== null && newPosition !== null;
+	if (isSemichar) {
+		const active = window.activeTextEditor!.selection.active;
+		const prev = new Position(active.line, active.character === 0 ? 0 : active.character - 1);
+		return newPosition.isEqual(prev);
+	}
+	return isSemichar;
+}
+
+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 6b07f11..2b0f6a6 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,7 @@ export class StandardLanguageClient {
 						// clients may not have properly configured documentPaste
 						logger.error(error);
 					}
+					registerSmartSemicolonDetection(context);
 					activationProgressNotification.hide();
 					if (!hasImported) {
 						showImportFinishNotification(context);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works fine. I have updated the PR.

Comment thread src/settings.ts Outdated
Comment thread package.json
Comment thread README.md Outdated
Comment thread package.nls.json Outdated
Comment thread src/commands.ts Outdated
Comment thread src/settings.ts Outdated
@snjeza snjeza changed the title Smart semicolon autocorrect [WIP] Smart semicolon autocorrect Jul 18, 2023
@snjeza snjeza force-pushed the issue-703 branch 2 times, most recently from 9d99d16 to 70ed480 Compare July 18, 2023 23:41
@snjeza snjeza changed the title [WIP] Smart semicolon autocorrect Smart semicolon autocorrect Jul 18, 2023
@snjeza

snjeza commented Jul 18, 2023

Copy link
Copy Markdown
Collaborator Author

@rgrunber I have updated this PR and eclipse-jdtls/eclipse.jdt.ls#2710

Signed-off-by: Snjezana Peco <snjezana.peco@redhat.com>
@snjeza snjeza force-pushed the issue-703 branch 3 times, most recently from b63ef9e to a23bdc3 Compare July 21, 2023 18:54

@rgrunber rgrunber left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this looks good. Just some very minor things to handle. Then we can merge and address any other issues.

There's another small thing I noticed but I'm open to handling it in a separate PR to avoid complicating this one further.

We set javaLSReadyin Started at

case 'Started':
this.status = ClientStatus.started;
serverStatus.updateServerStatus(ServerStatusKind.ready);
commands.executeCommand('setContext', 'javaLSReady', true);
. This is important because after it's set, the commands can be triggered.

However, we only register the semicolon detection commands in ServiceReady . For large projects there can be a gap between Started and ServiceReady so there is a moment where the commands may be activated (after Started) but before we have registerd the command (at ServiceReady). The solution is probably to create a separate context state that's triggered within ServiceReady. Update: According to the logs started+serviceready happen not even a second apart so maybe I've just been lucky reproducing the warning. Let's say it's lower priority and we can address this part later.

Comment thread src/commands.ts Outdated
Comment thread src/smartSemicolonDetection.ts Outdated
Comment thread package.json Outdated
@snjeza snjeza changed the title Smart semicolon autocorrect [WIP] Smart semicolon autocorrect Jul 21, 2023
@snjeza snjeza changed the title [WIP] Smart semicolon autocorrect Smart semicolon autocorrect Jul 21, 2023
@snjeza

snjeza commented Jul 21, 2023

Copy link
Copy Markdown
Collaborator Author

@rgrunber I have updated the PR.

@rgrunber

rgrunber commented Jul 22, 2023

Copy link
Copy Markdown
Member

There's another issue. Even when java.edit.smartSemicolonDetection.enabled is false, the delegate command still runs because !isSemichar() is still false. I think we need an additional variable to check if it's disabled and skip that delegate command if block. See https://github.com/redhat-developer/vscode-java/pull/3159/files#diff-364a380511abddd995a22cf86f0a35cc9acedbcf311367d0432c7ca9995fdfa3R12 .

Also it looks to me like the purpose of isSemichar() is to detect whether we applied the smart semicolon insertion, so that we know that the next backspace should undo the smart insertion. If so, I think it would be better to call it didSmartSemicolonInsertion

@snjeza

snjeza commented Jul 22, 2023

Copy link
Copy Markdown
Collaborator Author

There's another issue. Even when java.edit.smartSemicolonDetection.enabled is false, the delegate command still runs because !isSemichar() is still false. I think we need an additional variable to check if it's disabled and skip that delegate command if block.

Fixed.

Also it looks to me like the purpose of isSemichar() is to detect whether we applied the smart semicolon insertion, so that we know that the next backspace should undo the smart insertion. If so, I think it would be better to call it didSmartSemicolonInsertion

Fixed.

@rgrunber rgrunber left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change looks fine to me. We can address some of the issues in later contributions. The important part is it's disabled by default for now and doesn't introduce any overhead to regular semicolon/backspace usage. Just need to squash + merge.

Contributed by Roland Grunberg <rgrunber@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Smart semicolon autocorrect?

3 participants