interface implementation

This commit is contained in:
NeonXP 2023-08-07 21:08:48 +03:00
parent 0eaa18df4b
commit 80815c107e
No known key found for this signature in database
GPG key ID: B0DA6283C40CB2CB
8 changed files with 2191 additions and 2030 deletions

View file

@ -9,7 +9,8 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
node-version: 14
- run: npm install
- name: Publish to Open VSX Registry
uses: HaaLeo/publish-vscode-extension@v0
with:
@ -20,7 +21,8 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
node-version: 14
- run: npm install
- name: Publish to Visual Studio Marketplace
uses: HaaLeo/publish-vscode-extension@v0
with:

View file

@ -20,3 +20,20 @@ Extension in active development! Your contribution is always welcome :)
`Add error checking` - adds stub error checking to current line:
<img src="/wraperror.gif" width="500" />
# Interface implementation
Command "Go: Implement Interface Methods" based on https://github.com/ricardoerikson/vscode-go-impl-methods/ extension.
Install the impl package as follows:
```
go get -u github.com/josharian/impl
```
## Usage
1. At command pallete select command "Go: Implement Interface Methods"
2. Write receiver for methods. Example: "f *File", "m MyType", "c CustomType"
3. Select interface to implement

19
package-lock.json generated
View file

@ -1,25 +1,30 @@
{
"name": "gotools",
"version": "0.0.1",
"version": "0.1.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gotools",
"version": "0.0.1",
"version": "0.1.4",
"license": "GPL-3.0-or-later",
"dependencies": {
"lodash.debounce": "^4.0.8"
},
"devDependencies": {
"@types/glob": "^8.1.0",
"@types/mocha": "^10.0.1",
"@types/node": "20.2.5",
"@types/vscode": "^1.81.0",
"@types/vscode": "^1.80.0",
"@vscode/test-electron": "^2.3.2",
"eslint": "^8.41.0",
"glob": "^8.1.0",
"lodash.debounce": "^4.0.8",
"mocha": "^10.2.0",
"typescript": "^5.1.3"
},
"engines": {
"vscode": "^1.81.0"
"vscode": "^1.80.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@ -1176,6 +1181,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"dev": true
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",

View file

@ -2,9 +2,9 @@
"name": "gotools",
"displayName": "Golang Tools",
"description": "Tools for productive work",
"version": "0.1.2",
"version": "0.1.4",
"engines": {
"vscode": "^1.81.0"
"vscode": "^1.80.0"
},
"publisher": "neonxp",
"license": "GPL-3.0-or-later",
@ -28,7 +28,8 @@
"golang"
],
"activationEvents": [
"onLanguage:go"
"onLanguage:go",
"onCommand:gotools.implement"
],
"main": "./src/extension.js",
"contributes": {
@ -50,6 +51,11 @@
{
"command": "gotools.wrap-error",
"title": "if err≠nil {...}"
},
{
"command": "gotools.implement",
"title": "Implement Interface Methods",
"category": "Go"
}
]
},
@ -59,14 +65,18 @@
"test": "node ./test/runTest.js"
},
"devDependencies": {
"@types/vscode": "^1.81.0",
"@types/glob": "^8.1.0",
"@types/mocha": "^10.0.1",
"@types/node": "20.2.5",
"@types/vscode": "^1.80.0",
"@vscode/test-electron": "^2.3.2",
"eslint": "^8.41.0",
"glob": "^8.1.0",
"mocha": "^10.2.0",
"typescript": "^5.1.3",
"@vscode/test-electron": "^2.3.2"
"lodash.debounce": "^4.0.8"
},
"dependencies": {
"lodash.debounce": "^4.0.8"
}
}

60
src/errors.js Normal file
View file

@ -0,0 +1,60 @@
const vscode = require('vscode');
const fnRegex = /^(\t*)(.*)err\s?:?=.+?$/;
class ErrorsWrapper {
provideCodeActions(document, range) {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return undefined;
}
const line = document.lineAt(editor.selection.start.line);
if (!fnRegex.test(line.text)) {
vscode.commands.executeCommand('setContext', 'allowWrapIferr', false);
return undefined;
}
vscode.commands.executeCommand('setContext', 'allowWrapIferr', true);
const action = new vscode.CodeAction('Add error checking', vscode.CodeActionKind.RefactorRewrite);
action.command = { command: 'gotools.wrap-error', title: 'Add error checking block', tooltip: '' };
return [
action,
];
}
}
const wrapError = () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const document = editor.document;
const line = document.lineAt(editor.selection.start.line);
const matches = line.text.match(fnRegex);
if (matches === null || matches.length === 0) {
return;
}
const extravars = matches[2].split(',').map(x => x.trim()).filter(x => x);
if (extravars.filter(x => x !== "_").length > 0) {
editor.insertSnippet(
new vscode.SnippetString(`\nif err != nil {\n\treturn \${1:nil, }\${2:err}\n}\n`),
new vscode.Position(line.range.end.line, line.range.end.character + line.firstNonWhitespaceCharacterIndex),
);
} else {
const tabs = matches[1];
const original = matches[0].trimStart()
editor.insertSnippet(
new vscode.SnippetString(`${tabs}if ${original}; err != nil {\n${tabs}\treturn \${2:nil, }\${3:err}\n${tabs}}\n`),
line.range,
{
undoStopBefore: true,
undoStopAfter: true
}
);
}
};
module.exports = {
ErrorsWrapper, wrapError
}

View file

@ -1,11 +1,6 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode');
const fnRegex = /^(\t*)(.*)err\s?:?=.+?$/;
// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
const { ErrorsWrapper, wrapError } = require('./errors');
const selectReceiver = require('./implement-interface');
/**
* @param {vscode.ExtensionContext} context
*/
@ -23,66 +18,20 @@ function activate(context) {
)
);
context.subscriptions.push(wrapErrorCommand);
vscode.commands.registerCommand("gotools.imports", function () {
// TODO
})
context.subscriptions.push(vscode.commands.registerCommand("gotools.implement", selectReceiver));
}
// This method is called when your extension is deactivated
function deactivate() { }
class ErrorsWrapper {
provideCodeActions(document, range) {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return undefined;
}
const line = document.lineAt(editor.selection.start.line);
if (!fnRegex.test(line.text)) {
vscode.commands.executeCommand('setContext', 'allowWrapIferr', false);
return undefined;
}
vscode.commands.executeCommand('setContext', 'allowWrapIferr', true);
const action = new vscode.CodeAction('Add error checking', vscode.CodeActionKind.RefactorRewrite);
action.command = { command: 'gotools.wrap-error', title: 'Add error checking block', tooltip: '' };
return [
action,
];
}
}
module.exports = {
activate,
deactivate
}
const wrapError = () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const document = editor.document;
const line = document.lineAt(editor.selection.start.line);
const matches = line.text.match(fnRegex);
if (matches === null || matches.length === 0) {
return;
}
const extravars = matches[2].split(',').map(x => x.trim()).filter(x => x);
if (extravars.filter(x => x !== "_").length > 0) {
editor.insertSnippet(
new vscode.SnippetString(`\nif err != nil {\n\treturn \${1:nil, }\${2:err}\n}\n`),
new vscode.Position(line.range.end.line, line.range.end.character + line.firstNonWhitespaceCharacterIndex),
);
} else {
const tabs = matches[1];
const original = matches[0].trimStart()
editor.insertSnippet(
new vscode.SnippetString(`${tabs}if ${original}; err != nil {\n${tabs}\treturn \${2:nil, }\${3:err}\n${tabs}}\n`),
line.range,
{
undoStopBefore: true,
undoStopAfter: true
}
);
}
};

View file

@ -0,0 +1,95 @@
const vscode = require('vscode');
const { dirname } = require('path');
const cp = require('child_process');
const provideInterfaces = require('./interfaces-provider');
const debounce = require('lodash.debounce');
function selectReceiver() {
const receiverInput = vscode.window.createInputBox();
const pattern = /^([a-zA-Z_][a-zA-Z0-9_]*)\s+(\*?(?:[a-zA-Z_][a-zA-Z0-9]*\.)?[a-zA-Z_][a-zA-Z0-9_]*)$/;
receiverInput.placeholder = "Enter receiver (ex: 'm *MyType')";
receiverInput.onDidChangeValue(value => {
if (value != "" && !value.match(pattern)) {
receiverInput.validationMessage = `Valid format: "f *File", "m MyType", "c CustomType"`;
} else {
receiverInput.validationMessage = '';
}
});
receiverInput.onDidAccept(e => {
const receiver = receiverInput.value;
const matches = receiver.match(pattern);
if (!matches) {
vscode.window.showWarningMessage(`Receiver is not in the correct format. Valid: "f *File", "m MyType", "c CustomType"`);
return undefined;
}
selectInterface({ name: matches[1], type_: matches[2] });
receiverInput.hide();
});
receiverInput.onDidHide(() => {
receiverInput.dispose();
});
receiverInput.show();
}
/**
*
* @param {*} receiver
*/
function selectInterface(receiver) {
const quickPick = vscode.window.createQuickPick();
quickPick.placeholder = "Which interface would you like to implement?";
const debounced = debounce((value) => {
provideInterfaces(value, (interfaces) => {
const items = interfaces.map((label) => ({ label }));
quickPick.items = items;
});
}, 400, { trailing: true });
quickPick.onDidChangeValue(value => {
debounced(value);
});
quickPick.onDidChangeSelection(selection => {
if (selection[0]) {
implement(selection[0].label, receiver);
}
quickPick.hide();
});
quickPick.onDidHide(() => {
quickPick.dispose();
});
quickPick.show();
}
function implement(interface_, receiver) {
const editor = vscode.window.activeTextEditor;
vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Generating stub methods..."
}, (progress, token) => {
return new Promise((resolve) => {
const r = `${receiver.name} ${receiver.type_}`
cp.exec(`impl "${r}" ${interface_}`,
{ cwd: dirname(editor.document.fileName) },
(error, stdout, stderr) => {
if (error) {
vscode.window.showInformationMessage(stderr);
return resolve(true);
}
const position = editor.selection.active;
editor.insertSnippet(new vscode.SnippetString("\n" + stdout), position.with(position.line+1, 0));
resolve(true);
});
});
});
}
module.exports = selectReceiver;

View file

@ -0,0 +1,17 @@
const vscode = require('vscode');
function provideInterfaces(keyword, callback) {
vscode.commands.executeCommand("vscode.executeWorkspaceSymbolProvider", keyword)
.then(
/**
* @param {array} objects
*/
(objects) => {
const interfaces = objects.
filter(x => x.kind == vscode.SymbolKind.Interface).
map(x => x.name)
callback(interfaces);
});
}
module.exports = provideInterfaces