Skip to content

Commit

Permalink
整理代码
Browse files Browse the repository at this point in the history
  • Loading branch information
sumneko committed Jul 12, 2024
1 parent a7a1172 commit 16f24b9
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 69 deletions.
129 changes: 60 additions & 69 deletions src/plugin/index.ts
Original file line number Diff line number Diff line change
@@ -1,89 +1,76 @@
import * as vscode from 'vscode';
import * as y3 from 'y3-helper';
import * as vm from 'vm';
import * as plugin from './plugin';

let scriptDir = 'y3-helper/plugin';

function makeSandbox() {
return {
require: (name: string) => {
if (name === 'y3-helper') {
return y3;
}
},
module: {},
exports: {},
console: console,
setTimeout: setTimeout,
setInterval: setInterval,
clearTimeout: clearTimeout,
clearInterval: clearInterval,
};
}

export async function runEditor(uri: vscode.Uri, funcName: string = 'main') {
const file = await y3.fs.readFile(uri);
let needExports: string[] = [];
let content = file!.string.replaceAll(/export\s+(async\s+)function\s+([\w_]+)/g, (_, async, name) => {
needExports.push(name);
return `${async}function ${name}`;
});
content = content + '\nmodule.exports = { ' + needExports.join(', ') + ' };\n';
let script = new vm.Script(content, {
filename: uri.path,
});
let exports = script.runInNewContext(vm.createContext(makeSandbox()));
if (typeof exports[funcName] !== 'function') {
throw new Error(`没有找到要执行的函数${funcName}`);
}
await exports[funcName]();
const name = uri.path.split('/').pop()!.replace(/\.js$/, '');
y3.log.info(`执行 "${name}/${funcName}" 成功!`);
}
let pluginManager: plugin.PluginManager | undefined;

class RunButtonProvider implements vscode.CodeLensProvider {
public provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] | undefined {
public async provideCodeLenses(document: vscode.TextDocument): Promise<vscode.CodeLens[] | undefined> {
let plugin = await pluginManager?.findPlugin(document.uri);
if (!plugin) {
return undefined;
}
let codeLens: vscode.CodeLens[] = [];
for (let i = 0; i < document.lineCount; i++) {
const line = document.lineAt(i);
const name = line.text.match(/^export\s+(async\s+)function\s+([\w_]+)/)?.[2];
if (name) {
codeLens.push(new vscode.CodeLens(new vscode.Range(i, 0, i, 0), {
title: `$(debug-start)运行 ${name} 函数`,
command: 'y3-helper.runPlugin',
arguments: [document.uri, name],
}));
}
let infos = await plugin.getExports();
for (const name in infos) {
const info = infos[name];
const position = document.positionAt(info.offset);
codeLens.push(new vscode.CodeLens(new vscode.Range(position, position), {
title: `$(debug-start)运行 ${name} 函数`,
command: 'y3-helper.runPlugin',
arguments: [document.uri, name],
}));
}
return codeLens;
}
}

export function init() {
vscode.commands.registerCommand('y3-helper.initPlugin', async () => {
await y3.env.mapReady();
if (!y3.env.scriptUri) {
return;
async function initPlugin() {
await y3.env.mapReady();
if (!y3.env.scriptUri) {
return;
}
const targetDir = y3.uri(y3.env.scriptUri, scriptDir);
const templateDir = y3.extensionPath('template/plugin');
const listfile = await y3.fs.readFile(y3.uri(templateDir, 'listfile.json'));
const nameMap: { [key: string]: string } = listfile ? JSON.parse(listfile.string) : {};
for (const [name, fileType] of await y3.fs.dir(templateDir)) {
if (fileType === vscode.FileType.Directory) {
continue;
}
const targetDir = y3.uri(y3.env.scriptUri, scriptDir);
const templateDir = y3.extensionPath('template/plugin');
const listfile = await y3.fs.readFile(y3.uri(templateDir, 'listfile.json'));
const nameMap: { [key: string]: string } = listfile ? JSON.parse(listfile.string) : {};
for (const [name, fileType] of await y3.fs.dir(templateDir)) {
if (fileType === vscode.FileType.Directory) {
continue;
}
if (name === 'listfile.json') {
continue;
}
const newName = nameMap[name] ?? name;
let overwrite = name.endsWith('.d.ts');
await y3.fs.copy(y3.uri(templateDir, name), y3.uri(targetDir, newName), { overwrite: overwrite });
if (name === 'listfile.json') {
continue;
}
if (listfile) {
await vscode.commands.executeCommand('vscode.open', y3.uri(targetDir, nameMap['1.js']));
const newName = nameMap[name] ?? name;
let overwrite = name.endsWith('.d.ts');
await y3.fs.copy(y3.uri(templateDir, name), y3.uri(targetDir, newName), { overwrite: overwrite });
}
if (listfile) {
await vscode.commands.executeCommand('vscode.open', y3.uri(targetDir, nameMap['1.js']));
}
}

function initPluginManager() {
if (y3.env.scriptUri) {
pluginManager = new plugin.PluginManager(y3.uri(y3.env.scriptUri, scriptDir));
}
y3.env.onDidChange(() => {
pluginManager?.dispose();
if (y3.env.scriptUri) {
pluginManager = new plugin.PluginManager(y3.uri(y3.env.scriptUri, scriptDir));
}
});
}

export async function init() {
await y3.env.mapReady();

initPluginManager();

vscode.commands.registerCommand('y3-helper.initPlugin', initPlugin);

vscode.commands.registerCommand('y3-helper.runPlugin', async (uri?: vscode.Uri, funcName?: string) => {
if (!uri) {
Expand All @@ -92,9 +79,13 @@ export function init() {
return;
}
}
if (!pluginManager) {
vscode.window.showErrorMessage(`未找到插件目录`);
return;
}
y3.log.show();
try {
await runEditor(uri, funcName);
await pluginManager.run(uri, funcName ?? 'main');
} catch (error) {
vscode.window.showErrorMessage(`运行物编脚本出错:${error}`);
}
Expand Down
171 changes: 171 additions & 0 deletions src/plugin/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import * as vscode from 'vscode';
import * as y3 from 'y3-helper';
import * as vm from 'vm';
import { queue } from '../utility/decorators';

interface ExportInfo {
name: string;
async: string;
offset: number;
}

class Plugin {
private rawCode?: string | null;
private fixedCode?: string;
private script?: vm.Script;
private parseError?: string;
private exports: Record<string, ExportInfo> = {};
constructor(public uri: vscode.Uri, public name: string) {
this.parse();
}

@queue()
private async parse() {
if (this.rawCode !== undefined) {
return;
}
this.rawCode = (await y3.fs.readFile(this.uri))?.string ?? null;
if (!this.rawCode) {
this.parseError = '读取文件失败';
return;
}

const pattern = /export\s+(async\s+)function\s+([\w_]+)/g;
this.fixedCode = this.rawCode.replaceAll(pattern, (_, async, name) => {
return `${async}function ${name}`;
});

let match;
while (match = pattern.exec(this.rawCode)) {
const [_, async, name] = match;
this.exports[name] = {
name,
async,
offset: match.index,
};
}

this.fixedCode += '\nmodule.exports = { ' + Object.keys(this.exports).join(', ') + ' };\n';

try {
this.script = new vm.Script(this.fixedCode, {
filename: this.uri.path,
});
} catch (error) {
this.parseError = String(error);
}
}

public async getExports() {
await this.parse();
return this.exports;
}

public async run(funcName: string, sandbox: vm.Context) {
await this.parse();
if (this.parseError) {
throw new Error(this.parseError);
}
let exports = this.script!.runInNewContext(sandbox);
if (typeof exports[funcName] !== 'function') {
throw new Error(`没有找到要执行的函数${funcName}`);
}
await exports[funcName]();
}
}

export class PluginManager extends vscode.Disposable {
private _ready = false;
private _watcher: vscode.FileSystemWatcher;

constructor(public dir: vscode.Uri) {
super(() => {
this._watcher.dispose();
});
this.loadPlugins();
this._watcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(dir, '**/*.js')
);
this._watcher.onDidCreate((e) => {
let name = this.getName(e);
if (!name) {
return;
}
this.plugins[name] = new Plugin(e, name);
});
this._watcher.onDidDelete((e) => {
let name = this.getName(e);
if (!name) {
return;
}
delete this.plugins[name];
});
this._watcher.onDidChange((e) => {
let name = this.getName(e);
if (!name) {
return;
}
this.plugins[name] = new Plugin(e, name);
});
}

public plugins: Record<string, Plugin> = {};
private async loadPlugins() {
this._ready = false;
for (const [filename, fileType] of await y3.fs.scan(this.dir)) {
if (fileType === vscode.FileType.File && filename.endsWith('.js')) {
let name = filename.replace(/\.js$/, '');
const plugin = new Plugin(y3.uri(this.dir, filename), name);
this.plugins[name] = plugin;
}
}
this._ready = true;
}

private async ready() {
while (!this._ready) {
await y3.sleep(100);
}
}

private makeSandbox() {
const sandBox = {
require: (name: string) => {
if (name === 'y3-helper') {
return y3;
}
},
module: {},
exports: {},
};
return vm.createContext(new Proxy(sandBox as any, {
get(target, prop) {
return target[prop] ?? (global as any)[prop];
}
}));
}

public getName(uri: vscode.Uri) {
if (!uri.path.startsWith(this.dir.path)) {
return undefined;
}
return uri.path.slice(this.dir.path.length + 1).replace(/\.js$/, '');
}

public async findPlugin(uri: vscode.Uri) {
const name = this.getName(uri);
if (!name) {
return;
}
await this.ready();
return this.plugins[name];
}

public async run(uri: vscode.Uri, funcName: string) {
let plugin = await this.findPlugin(uri);
if (!plugin) {
throw new Error('没有找到插件');
}
await plugin.run(funcName, this.makeSandbox());
}
}
26 changes: 26 additions & 0 deletions src/tools/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,32 @@ export async function dir(uri: vscode.Uri | string, relativePath?: string) {
}
}

export async function scan(uri: vscode.Uri | string, relativePath?: string) {
if (typeof uri === 'string') {
uri = vscode.Uri.file(uri);
}
if (relativePath) {
uri = vscode.Uri.joinPath(uri, relativePath);
}

let result: [string, vscode.FileType][] = [];

async function doScan(uri: vscode.Uri, path?: string) {
let files = await dir(uri, path);
for (const [name, fileType] of files) {
let fullPath = path ? `${path}/${name}` : name;
result.push([fullPath, fileType]);
if (fileType === vscode.FileType.Directory) {
await doScan(uri, fullPath);
}
}
}

await doScan(uri);

return result;
}

export async function stat(uri: vscode.Uri | string, relativePath?: string) {
if (typeof uri === 'string') {
uri = vscode.Uri.file(uri);
Expand Down
4 changes: 4 additions & 0 deletions src/y3-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,7 @@ export function open(uri: vscode.Uri | string) {
}
vscode.commands.executeCommand('vscode.open', uri);
}

export async function sleep(ms: number) {
await new Promise(resolve => setTimeout(resolve, ms));
}

0 comments on commit 16f24b9

Please sign in to comment.