Skip to content

Commit

Permalink
Support change signature refactoring
Browse files Browse the repository at this point in the history
Signed-off-by: Shi Chen <chenshi@microsoft.com>
  • Loading branch information
CsCherrYY committed Feb 27, 2023
1 parent 6572351 commit 81e3c08
Show file tree
Hide file tree
Showing 26 changed files with 14,430 additions and 8 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Expand Up @@ -106,5 +106,6 @@
"prefer-arrow-callback": "off"
}
}
]
],
"ignorePatterns": ["webview-ui/**"]
}
18 changes: 16 additions & 2 deletions .vscode/launch.json
Expand Up @@ -7,14 +7,21 @@
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"debugWebviews": true,
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
"env": {
"DEBUG_VSCODE_JAVA":"true"
},
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [ "${workspaceRoot}/dist/**/*.js" ],
"preLaunchTask": "npm: watch"
"preLaunchTask": "npm: watch",
"rendererDebugOptions": {
"webRoot": "${workspaceFolder}/webview-ui/build/static/js",
"urlFilter": "*redhat.java*",
"sourceMaps": true,
"pauseForSourceMap": true,
}
},
{
"name": "Launch Extension - Remote Server",
Expand All @@ -36,6 +43,7 @@
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"debugWebviews": true,
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
"stopOnEntry": false,
"sourceMaps": true,
Expand All @@ -44,7 +52,13 @@
"JDTLS_CLIENT_PORT": "5036",
"DEBUG_VSCODE_JAVA":"true"
},
"preLaunchTask": "npm: watch"
"preLaunchTask": "npm: watch",
"rendererDebugOptions": {
"webRoot": "${workspaceFolder}/webview-ui/build/static/js",
"urlFilter": "*redhat.java*",
"sourceMaps": true,
"pauseForSourceMap": true,
}
},
{
"name": "Launch Extension - SyntaxLS Client",
Expand Down
4 changes: 3 additions & 1 deletion .vscodeignore
Expand Up @@ -23,4 +23,6 @@ Jenkinsfile
.eslintrc.json
.eslintignore
webpack.config.js
.DS_Store
.DS_Store
webview-ui/**
!webview-ui/build/static/**
5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -1382,8 +1382,9 @@
}
},
"scripts": {
"vscode:prepublish": "webpack --mode production",
"compile": "tsc -p ./&webpack --mode development",
"postinstall": "cd webview-ui && npm install",
"vscode:prepublish": "webpack --mode production && cd webview-ui && npm run build",
"compile": "tsc -p ./&webpack --mode development && cd webview-ui && npm run build",
"watch": "webpack --mode development --watch",
"pretest": "npm run compile",
"test": "node ./out/test/runtest.js",
Expand Down
5 changes: 5 additions & 0 deletions src/refactorAction.ts
Expand Up @@ -7,6 +7,7 @@ import { FormattingOptions, WorkspaceEdit, RenameFile, DeleteFile, TextDocumentE
import { LanguageClient } from 'vscode-languageclient/node';
import { Commands as javaCommands } from './commands';
import { GetRefactorEditRequest, MoveRequest, RefactorWorkspaceEdit, RenamePosition, GetMoveDestinationsRequest, SearchSymbols, SelectionInfo, InferSelectionRequest } from './protocol';
import { ChangeSignaturePanel } from './refactoring/changeSignaturePanel';
import { getExtractInterfaceArguments, revealExtractedInterface } from './refactoring/extractInterface';

export function registerCommands(languageClient: LanguageClient, context: ExtensionContext) {
Expand Down Expand Up @@ -40,6 +41,7 @@ function registerApplyRefactorCommand(languageClient: LanguageClient, context: E
|| command === 'extractMethod'
|| command === 'extractField'
|| command === 'extractInterface'
|| command === 'changeSignature'
|| command === 'assignField'
|| command === 'convertVariableToField'
|| command === 'invertVariable'
Expand Down Expand Up @@ -109,6 +111,9 @@ function registerApplyRefactorCommand(languageClient: LanguageClient, context: E
return;
}
commandArguments.push(...args);
} else if (command === 'changeSignature') {
ChangeSignaturePanel.render(context.extensionUri, languageClient, command, params, formattingOptions, commandInfo);
return;
}

const result: RefactorWorkspaceEdit = await languageClient.sendRequest(GetRefactorEditRequest.type, {
Expand Down
187 changes: 187 additions & 0 deletions src/refactoring/changeSignaturePanel.ts
@@ -0,0 +1,187 @@
import { Disposable, Webview, WebviewPanel, window, Uri, ViewColumn, workspace, WorkspaceEdit, Position } from "vscode";
import { LanguageClient } from "vscode-languageclient/node";
import { GetRefactorEditRequest, RefactorWorkspaceEdit } from "../protocol";
import { getNonce, getUri } from "../webview/utils";

interface MethodParameter {
type: string;
name: string;
defaultValue: string;
originalIndex: number;
}

interface MethodException {
type: string;
typeHandleIdentifier: string;
}

export class ChangeSignaturePanel {
public static type = "java.refactor.changeSignature";
public static title = "Refactor: Change Method Signature";
public static currentPanel: ChangeSignaturePanel | undefined;
private readonly panel: WebviewPanel;
private disposables: Disposable[] = [];

// method matadata
private methodIdentifier: string | undefined;
private methodName: string | undefined;
private accessType: string | undefined;
private returnType: string | undefined;
private parameters: MethodParameter[] | undefined;
private exceptions: MethodException[] | undefined;

// refactor metadata
private languageClient: LanguageClient;
private params: any;
private formattingOptions: any;
private command: any;

private constructor(panel: WebviewPanel, extensionUri: Uri) {
this.panel = panel;
this.panel.onDidDispose(() => this.dispose(), null, this.disposables);
this.panel.webview.html = this.getWebviewContent(this.panel.webview, extensionUri);
this.setWebviewMessageListener(this.panel.webview);
}

public static render(extensionUri: Uri, languageClient: LanguageClient, command: any, params: any, formattingOptions: any, commandInfo: any) {
if (ChangeSignaturePanel.currentPanel) {
ChangeSignaturePanel.currentPanel.panel.reveal(ViewColumn.Beside);
} else {
const panel = window.createWebviewPanel(
ChangeSignaturePanel.type,
ChangeSignaturePanel.title,
ViewColumn.Beside,
{
enableCommandUris: true,
enableScripts: true,
localResourceRoots: [Uri.joinPath(extensionUri, "dist"), Uri.joinPath(extensionUri, "webview-ui/build")],
}
);
ChangeSignaturePanel.currentPanel = new ChangeSignaturePanel(panel, extensionUri);
ChangeSignaturePanel.currentPanel.setMetadata(languageClient, command, params, formattingOptions, commandInfo);
}
}

public setMetadata(languageClient: LanguageClient, command: any, params: any, formattingOptions: any, commandInfo: any) {
this.languageClient = languageClient;
this.command = command;
this.params = params;
this.formattingOptions = formattingOptions;
this.methodIdentifier = commandInfo.methodIdentifier;
this.methodName = commandInfo.methodName as string;
this.accessType = commandInfo.accessType as string;
this.returnType = commandInfo.returnType as string;
this.parameters = commandInfo.parameters as MethodParameter[];
this.exceptions = commandInfo.exceptions as MethodException[];
}

public dispose() {
ChangeSignaturePanel.currentPanel = undefined;
this.panel.dispose();
while (this.disposables.length) {
const disposable = this.disposables.pop();
if (disposable) {
disposable.dispose();
}
}
}

private getWebviewContent(webview: Webview, extensionUri: Uri) {
const stylesUri = getUri(webview, extensionUri, [
"webview-ui",
"build",
"static",
"css",
"main.css",
]);
const scriptUri = getUri(webview, extensionUri, [
"webview-ui",
"build",
"static",
"js",
"main.js",
]);

const nonce = getNonce();

return /* html*/ `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="stylesheet" type="text/css" href="${stylesUri}">
<title>Change Signature</title>
</head>
<body>
<div id="root"></div>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>
`;
}

private setWebviewMessageListener(webview: Webview) {
webview.onDidReceiveMessage(
async (message: any) => {
const command = message.command;
switch (command) {
case "webviewReady":
await this.panel.webview.postMessage({
command: "setInitialState",
methodIdentifier: this.methodIdentifier,
methodName: this.methodName,
accessType: this.accessType,
returnType: this.returnType,
parameters: this.parameters,
exceptions: this.exceptions
});
break;
case "doRefactor":
await this.doRefactor(message.methodIdentifier, message.isDelegate, message.methodName, message.accessType, message.returnType, message.parameters, message.exceptions);
this.dispose();
break;
}
},
undefined,
this.disposables
);
}

private async doRefactor(methodIdentifier: string, isDelegate: boolean, methodName: string, accessType: string, returnType: string, parameters: MethodParameter[], exceptions: MethodException[]) {
const clientWorkspaceEdit: RefactorWorkspaceEdit = await this.languageClient.sendRequest(GetRefactorEditRequest.type, {
command: this.command,
context: this.params,
options: this.formattingOptions,
commandArguments: [methodIdentifier, isDelegate, methodName, accessType, returnType, parameters, exceptions]
});
if (!clientWorkspaceEdit) {
return;
}
if (clientWorkspaceEdit.edit) {
const codeEdit: WorkspaceEdit = await this.languageClient.protocol2CodeConverter.asWorkspaceEdit(clientWorkspaceEdit.edit);

/**
* See the issue https://github.com/microsoft/vscode/issues/94650.
* The current vscode doesn't provide a way for the extension to pre-select all changes.
*
* As a workaround, this extension would append a dummy text edit that needs a confirm,
* and then make all others text edits not need a confirm. This will ensure that
* the REFACTOR PREVIEW panel can be triggered and all valid changes pre-selected.
*/
const textEditEntries = codeEdit.entries();
if (textEditEntries && textEditEntries.length) {
const dummyNodeUri: Uri = textEditEntries[textEditEntries.length - 1][0];
codeEdit.insert(dummyNodeUri, new Position(0, 0), "", {
needsConfirmation: true,
label: "Dummy node used to enable preview"
});
}

if (codeEdit) {
await workspace.applyEdit(codeEdit);
}
}
}
}
14 changes: 14 additions & 0 deletions src/webview/utils.ts
@@ -0,0 +1,14 @@
import { Uri, Webview } from "vscode";

export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) {
return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList));
}

export function getNonce() {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
3 changes: 2 additions & 1 deletion tsconfig.json
Expand Up @@ -13,6 +13,7 @@
"exclude": [
"node_modules",
"server",
".vscode-test"
".vscode-test",
"webview-ui"
]
}
2 changes: 1 addition & 1 deletion webpack.config.js
Expand Up @@ -8,7 +8,7 @@ const path = require('path');
/**@type {import('webpack').Configuration}*/
const config = {
watchOptions: {
ignored: /node_modules/
ignored: ["/node_modules/", "/webview-ui/"]
},
target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
node: {
Expand Down
25 changes: 25 additions & 0 deletions webview-ui/.gitignore
@@ -0,0 +1,25 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*

*.7z
15 changes: 15 additions & 0 deletions webview-ui/README.md
@@ -0,0 +1,15 @@
# `webview-ui` Directory

This directory contains all of the code that will be executed within the webview context. It can be thought of as the place where all the "frontend" code of a webview is contained.

Types of content that can be contained here:

- Frontend framework code (i.e. React, Svelte, Vue, etc.)
- JavaScript files
- CSS files
- Assets / resources (i.e. images, illustrations, etc.)

# How to debug the webview

- `npm i` in the directory
- Go to `./node_modules/react-scripts/scripts/build.js`, change all string constant "production" to "development"

0 comments on commit 81e3c08

Please sign in to comment.