This commit is contained in:
Родитель
057a384935
Коммит
916ad587af
|
@ -1,59 +1 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
node_modules/
|
|
@ -0,0 +1,19 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"[typescript]": {
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false
|
||||
},
|
||||
"files.exclude": {
|
||||
"node_modules/**": true,
|
||||
"src/**/*.js": true,
|
||||
"src/**/*.js.map": true,
|
||||
"src/**/*.d.ts": true,
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/.DS_Store": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "0.1.0",
|
||||
"command": "tsc",
|
||||
"isShellCommand": true,
|
||||
"args": ["-w", "-p", "."],
|
||||
"showOutput": "silent",
|
||||
"isBackground": true,
|
||||
"problemMatcher": "$tsc-watch"
|
||||
}
|
14
README.md
14
README.md
|
@ -1,14 +0,0 @@
|
|||
|
||||
# Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
@ -0,0 +1,12 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const electron = require("electron");
|
||||
const child_process_1 = require("child_process");
|
||||
if (typeof electron === "string") {
|
||||
child_process_1.spawn(electron, [__filename], { stdio: ["ignore", "ignore", process.stderr, process.stdin, process.stdout] });
|
||||
}
|
||||
else {
|
||||
electron.app.on("ready", () => {
|
||||
require("./src/index");
|
||||
});
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import * as electron from "electron";
|
||||
import { spawn } from "child_process";
|
||||
|
||||
if (typeof electron === "string") {
|
||||
// make sure that electron can be electron!
|
||||
delete process.env['ELECTRON_RUN_AS_NODE'];
|
||||
|
||||
const proc = spawn(electron as any, [__filename], { stdio: ["ignore", "ignore", process.stderr, process.stdin, process.stdout] });
|
||||
(<any>process).on('exit', (code) => {
|
||||
proc.kill();
|
||||
});
|
||||
} else {
|
||||
electron.app.on("ready", () => {
|
||||
require("./src/index");
|
||||
});
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "autorest-interactive",
|
||||
"version": "0.1.0",
|
||||
"description": "AutoRest Interactive View",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"prepare" : "tsc -p . ",
|
||||
"start": "node index.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/olydis/autorest-interactive.git"
|
||||
},
|
||||
"keywords": [
|
||||
"autorest"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/olydis/autorest-interactive/issues"
|
||||
},
|
||||
"homepage": "https://github.com/olydis/autorest-interactive#readme",
|
||||
"devDependencies": {
|
||||
"@types/d3": "^4.8.0",
|
||||
"@types/electron": "^1.4.37",
|
||||
"@types/jquery": "^2.0.43",
|
||||
"@types/js-yaml": "^3.5.30",
|
||||
"@types/jsonpath": "^0.1.29",
|
||||
"@types/node": "^7.0.18",
|
||||
"typescript" : "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"d3": "^4.8.0",
|
||||
"electron": "^1.6.7",
|
||||
"jquery": "^3.2.1",
|
||||
"js-yaml": "^3.8.4",
|
||||
"jsonpath": "^0.2.11",
|
||||
"vscode-jsonrpc": "^3.2.0"
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 282 KiB |
|
@ -0,0 +1,89 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>AutoRest Interactive</title>
|
||||
<script>
|
||||
var exports = {};
|
||||
</script>
|
||||
<script src="index.js"></script>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#overlays>div {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: 80px;
|
||||
padding: 20px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: auto;
|
||||
background: white;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
#overlays>div>h1 {
|
||||
z-index: 10;
|
||||
position: fixed;
|
||||
padding: 20px;
|
||||
margin: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
background: white;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#overlays>div>h1>button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 20px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
#overlays>div>div {}
|
||||
|
||||
#overlays>div>div>table {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 1px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#pipelineGraph {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scalable:hover {
|
||||
transform: scale(2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
line.edge {
|
||||
stroke: #CCC;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<svg id="pipelineGraph"></svg>
|
||||
<div id="overlays"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,284 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const electron_1 = require("electron");
|
||||
const $ = require("jquery");
|
||||
const d3 = require("d3");
|
||||
const jsonpath_1 = require("jsonpath");
|
||||
window.onerror = e => electron_1.remote.dialog.showErrorBox("Unhandled Error", e);
|
||||
function remoteEval(expression) {
|
||||
return electron_1.ipcRenderer.sendSync("remoteEval", expression);
|
||||
}
|
||||
function readFile(uri) {
|
||||
return electron_1.ipcRenderer.sendSync("readFile", uri);
|
||||
}
|
||||
$(() => {
|
||||
const startTime = remoteEval("startTime");
|
||||
const pipeline = remoteEval("pipeline");
|
||||
const depth = (node) => node.inputs.map(i => depth(pipeline[i]) + 1).reduce((a, b) => Math.max(a, b), 0);
|
||||
const runningTime = node => {
|
||||
const selfFinished = node.state.finishedAt;
|
||||
if (!selfFinished) {
|
||||
return null;
|
||||
}
|
||||
const previousFinished = node.inputs.map(x => pipeline[x].state.finishedAt).reduce((a, b) => !b ? undefined : Math.max(a, b), startTime) || selfFinished;
|
||||
return ((selfFinished || Date.now()) - (previousFinished || selfFinished)) / 1000;
|
||||
};
|
||||
const nodes = Object.keys(pipeline).map(key => Object.assign(pipeline[key], { key: key, displayName: key.split("/") }));
|
||||
const links = [].concat.apply([], nodes.map(node => node.inputs.map(input => {
|
||||
return {
|
||||
source: pipeline[input],
|
||||
target: node
|
||||
};
|
||||
})));
|
||||
// horiz layout
|
||||
nodes.forEach(n => n.depth = depth(n));
|
||||
nodes.forEach(n => n.x = 0);
|
||||
nodes.forEach((n, i) => n.y = i / 10);
|
||||
const width = nodes.map(x => x.depth).reduce((a, b) => Math.max(a, b), 0);
|
||||
const height = width * 0.7;
|
||||
const simulation = d3.forceSimulation(nodes)
|
||||
.force("charge", d3.forceManyBody().strength(-1).distanceMin(10).distanceMin(30))
|
||||
.force("link", d3.forceLink(links).distance(1).strength(1).iterations(10))
|
||||
.force("y", d3.forceY(0))
|
||||
.stop();
|
||||
for (var i = 0; i < 1; ++i) {
|
||||
simulation.tick();
|
||||
nodes.forEach(n => n.y = Math.min(Math.max(n.y, -height / 2), height / 2));
|
||||
for (let d = 0; d <= width; ++d) {
|
||||
const nodeSet = nodes.filter(n => n.depth === d);
|
||||
const meanY = nodeSet.map(n => n.y).reduce((a, b) => a + b, 0) / nodeSet.length;
|
||||
for (let i = 0; i < nodeSet.length; ++i) {
|
||||
const n = nodeSet[i];
|
||||
n.x = d - width / 2;
|
||||
n.y = meanY + (i - nodeSet.length / 2) * 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
const vis = d3.select("#pipelineGraph").attr("viewBox", `0 0 ${width + 2} ${height + 2}`).append("g").attr("transform", `translate(${width / 2 + 1},${height / 2 + 1})`);
|
||||
let lastFootprint = null;
|
||||
const render = () => {
|
||||
const footprint = JSON.stringify(nodes) + JSON.stringify(links);
|
||||
if (lastFootprint === footprint)
|
||||
return;
|
||||
lastFootprint = footprint;
|
||||
const lineData = vis.selectAll("line.edge").data(links);
|
||||
{
|
||||
const enter = lineData.enter().append("line").attr("class", "edge").attr("stroke", "#000").attr("stroke-width", 0.02);
|
||||
lineData.merge(enter)
|
||||
.attr("x1", d => d.source.x.toFixed(3))
|
||||
.attr("y1", d => d.source.y.toFixed(3))
|
||||
.attr("x2", d => d.target.x.toFixed(3))
|
||||
.attr("y2", d => d.target.y.toFixed(3));
|
||||
lineData.exit().remove();
|
||||
}
|
||||
const nodeData = vis.selectAll(".node").data(nodes);
|
||||
{
|
||||
const enter = nodeData.enter()
|
||||
.append("g")
|
||||
.attr("class", "node")
|
||||
.attr("stroke", "#000")
|
||||
.attr("fill", "#FFF")
|
||||
.attr("stroke-width", 0.03)
|
||||
.attr("transform", d => `translate(${d.x},${d.y})`)
|
||||
.append("g")
|
||||
.attr("class", "scalable")
|
||||
.on("click", showNodeDetails);
|
||||
nodeData.exit().remove();
|
||||
const update = nodeData.select(".scalable").merge(enter);
|
||||
update.selectAll("*").remove();
|
||||
update.append("circle")
|
||||
.attr("r", "0.45em")
|
||||
.attr("fill", d => d.state.state === "running"
|
||||
? "#FFF"
|
||||
: (d.state.state === "complete"
|
||||
? `hsl(${(100 - 100 * Math.min(Math.max((runningTime(d) || 0) / 10, 0), 1)) | 0}, 100%, 80%)`
|
||||
: "#000"));
|
||||
update.append("text")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("style", d => `font-size: ${1.6 / (d.displayName.reduce((a, b) => Math.max(a, b.length), 0) + 1)}em`)
|
||||
.html(d => d.displayName.map((l, i) => `<tspan x="0" y="${(i - (d.displayName.length - 1) / 2) * 1.3 + 0.3}em">${l}</tspan>`).join(""))
|
||||
.attr("fill", d => d.state.state === "failed" ? "#FFF" : "#000").attr("stroke-width", 0);
|
||||
update.append("text")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("y", "3.2em")
|
||||
.attr("style", `font-size: 0.2em; font-weight: bold`)
|
||||
.text(d => {
|
||||
const sec = (runningTime(d) || 0).toFixed(1);
|
||||
return sec === "0.0" || sec === "0.1" ? "" : `${sec}s`;
|
||||
})
|
||||
.attr("fill", "#000").attr("stroke-width", 0);
|
||||
}
|
||||
};
|
||||
const update = () => {
|
||||
const states = remoteEval(`tasks`);
|
||||
for (const node of nodes) {
|
||||
node.state = node.state || { state: "running" };
|
||||
node.state.state = states[node.key]._state;
|
||||
node.state.finishedAt = states[node.key]._finishedAt;
|
||||
if (node.state.state === "complete" && !node.state.outputUris) {
|
||||
node.state.outputUris = remoteEval(`tasks[${JSON.stringify(node.key)}]._result().map(x => x.key)`);
|
||||
}
|
||||
}
|
||||
};
|
||||
const refresh = () => { update(); render(); };
|
||||
refresh();
|
||||
setInterval(refresh, 100);
|
||||
// $("#pipelineGraph").text(JSON.stringify(pipeline, null, 2));
|
||||
// setInterval(() => {
|
||||
// $("body").text(JSON.stringify(3));
|
||||
// }, 1000);
|
||||
});
|
||||
function showOverlay(title, content) {
|
||||
const overlay = $("<div>");
|
||||
overlay.append($("<h1>")
|
||||
.append($("<button>").text("X").click(() => overlay.remove()))
|
||||
.append($("<span>").text(title)));
|
||||
overlay.append($("<div>").append(content));
|
||||
$("#overlays").append(overlay);
|
||||
}
|
||||
function showNodeDetails(node) {
|
||||
const table = $("<table>");
|
||||
table.append($("<tr>")
|
||||
.append($("<td>").text("Plugin"))
|
||||
.append($("<td>").text(node.pluginName)));
|
||||
table.append($("<tr>")
|
||||
.append($("<td>").text("Configuration Scope"))
|
||||
.append($("<td>").text(jsonpath_1.stringify(["$"].concat(node.configScope)))));
|
||||
table.append($("<tr>")
|
||||
.append($("<td>").text("Output"))
|
||||
.append($("<td>").append(node.state.outputUris.map(uri => $("<a>")
|
||||
.attr("href", "#")
|
||||
.text(uri)
|
||||
.click(() => showUriDetails(uri))
|
||||
.append($("<br>"))))));
|
||||
// ext. extension
|
||||
const extensionName = remoteEval(`(external[${JSON.stringify(node.pluginName)}] || {}).extensionName`);
|
||||
if (extensionName) {
|
||||
table.append($("<tr>")
|
||||
.append($("<td>").text("Extension"))
|
||||
.append($("<td>").append(extensionName)));
|
||||
const traffic = remoteEval(`external[${JSON.stringify(node.pluginName)}].inspectTraffic`);
|
||||
for (const [timeStamp, isCore2Ext, payload] of traffic) {
|
||||
const payloadShortened = payload.length > 200 ? payload.substr(0, 200) + "..." : payload;
|
||||
table.append($("<tr>").css("background", isCore2Ext ? "#FEE" : "#EFE")
|
||||
.append($("<td>").text(new Date(timeStamp).toLocaleTimeString() + (isCore2Ext ? " (core => ext)" : " (ext => core)")))
|
||||
.append($("<td>").append($("<a>")
|
||||
.attr("href", "#")
|
||||
.text(payloadShortened)
|
||||
.click(() => showOverlay("", $("<textarea>").val(payload))))));
|
||||
}
|
||||
}
|
||||
showOverlay(node.key, table);
|
||||
}
|
||||
function showUriDetails(uri) {
|
||||
const content = $("<pre>").css("font-family", "monospace").text(readFile(uri));
|
||||
content.click(e => {
|
||||
const s = window.getSelection();
|
||||
showBlameTreeDetails(remoteEval(`blame(${JSON.stringify(uri)}, ${JSON.stringify({ index: s.anchorOffset })})`));
|
||||
});
|
||||
showOverlay(uri, content);
|
||||
}
|
||||
function showBlameTreeDetails(blameTree) {
|
||||
const content = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
const depth = (blameTree) => blameTree.blaming.map(n => depth(n) + 1).reduce((a, b) => Math.max(a, b), 0);
|
||||
const getNodes = blameTree => [blameTree].concat(...blameTree.blaming.map(getNodes));
|
||||
const nodes = getNodes(blameTree).map(node => Object.assign(node, { displayName: [node.node.source, node.node.line + ":" + node.node.column] }));
|
||||
const links = [].concat.apply([], nodes.map(node => node.blaming.map(input => {
|
||||
return {
|
||||
source: input,
|
||||
target: node
|
||||
};
|
||||
})));
|
||||
// horiz layout
|
||||
nodes.forEach(n => n.depth = depth(n));
|
||||
nodes.forEach((n, i) => n.x = i / 10);
|
||||
const height = nodes.map(x => x.depth).reduce((a, b) => Math.max(a, b), 0);
|
||||
let width = 5;
|
||||
for (let i = 0; i <= height; ++i) {
|
||||
width = Math.max(width, nodes.filter(n => n.depth === i).length);
|
||||
}
|
||||
const simulation = d3.forceSimulation(nodes)
|
||||
.force("charge", d3.forceManyBody().strength(-1).distanceMin(10).distanceMin(30))
|
||||
.force("link", d3.forceLink(links).distance(1).strength(1).iterations(10))
|
||||
.force("x", d3.forceX(0))
|
||||
.stop();
|
||||
for (var i = 0; i < 1; ++i) {
|
||||
simulation.tick();
|
||||
nodes.forEach(n => n.x = Math.min(Math.max(n.x, -width / 2), width / 2));
|
||||
for (let d = 0; d <= height; ++d) {
|
||||
const nodeSet = nodes.filter(n => n.depth === d);
|
||||
for (let i = 0; i < nodeSet.length; ++i) {
|
||||
const n = nodeSet[i];
|
||||
n.y = 5 * (d - height / 2);
|
||||
n.x = 0 + (i - nodeSet.length / 2) * 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
const vis = d3.select(content).attr("viewBox", `0 0 ${width + 2} ${5 * (height + 1)}`).append("g").attr("transform", `translate(${width / 2 + 1},${5 * (height + 1) / 2})`);
|
||||
let lastFootprint = null;
|
||||
const footprint = JSON.stringify(nodes) + JSON.stringify(links);
|
||||
if (lastFootprint === footprint)
|
||||
return;
|
||||
lastFootprint = footprint;
|
||||
const lineData = vis.selectAll("line.edge").data(links);
|
||||
{
|
||||
const enter = lineData.enter().append("line").attr("class", "edge").attr("stroke", "#000").attr("stroke-width", 0.02);
|
||||
lineData.merge(enter)
|
||||
.attr("x1", d => d.source.x.toFixed(3))
|
||||
.attr("y1", d => d.source.y.toFixed(3))
|
||||
.attr("x2", d => d.target.x.toFixed(3))
|
||||
.attr("y2", d => d.target.y.toFixed(3));
|
||||
lineData.exit().remove();
|
||||
}
|
||||
const nodeData = vis.selectAll(".node").data(nodes);
|
||||
{
|
||||
const enter = nodeData.enter()
|
||||
.append("g")
|
||||
.attr("writing-mode", "tb-rl")
|
||||
.attr("class", "node")
|
||||
.attr("stroke", "#000")
|
||||
.attr("fill", "#FFF")
|
||||
.attr("stroke-width", 0.03)
|
||||
.attr("transform", d => `translate(${d.x},${d.y})`)
|
||||
.append("g")
|
||||
.attr("class", "scalable")
|
||||
.on("click", d => electron_1.remote.dialog.showMessageBox({ title: d.node.source, message: d.node.name }));
|
||||
const update = nodeData.select(".scalable").merge(enter);
|
||||
update.selectAll("*").remove();
|
||||
update.append("rect")
|
||||
.attr("width", "0.9em")
|
||||
.attr("height", "4.9em")
|
||||
.attr("x", "-0.45em")
|
||||
.attr("y", "-2.45em")
|
||||
.attr("fill", "#FFF");
|
||||
update.append("text")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", "#000")
|
||||
.attr("style", d => `font-size: ${10 / (d.displayName.reduce((a, b) => Math.max(a, b.length), 0) + 1)}em`)
|
||||
.html(d => d.displayName.map((l, i) => `<tspan y="0" x="${-(i - (d.displayName.length - 1) / 2) * 1.3 - 0.3}em">${l}</tspan>`).join(""))
|
||||
.attr("stroke-width", 0);
|
||||
update.append("text")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("y", "3.2em")
|
||||
.attr("style", `font-size: 0.2em; font-weight: bold`)
|
||||
.text(d => "")
|
||||
.attr("fill", "#000").attr("stroke-width", 0);
|
||||
}
|
||||
showOverlay(`Blame`, $(content));
|
||||
}
|
||||
// let deltaX: number | null = null;
|
||||
// let deltaY: number | null = null;
|
||||
// function moveNodeStart(node: PipelineNode, e: MouseEvent): void {
|
||||
// deltaX = e.pageX - node.x;
|
||||
// deltaY = e.pageY - node.y;
|
||||
// }
|
||||
// function moveNode(node: PipelineNode, e: MouseEvent): void {
|
||||
// if (deltaX !== null) {
|
||||
// node.x = e.pageX - deltaX;
|
||||
// node.y = e.pageY - deltaY;
|
||||
// }
|
||||
// }
|
||||
// function moveNodeEnd(node: PipelineNode, e: MouseEvent): void {
|
||||
// deltaX = null;
|
||||
// deltaY = null;
|
||||
// }
|
|
@ -0,0 +1,347 @@
|
|||
import { ipcRenderer, remote } from "electron";
|
||||
import * as $ from "jquery";
|
||||
import * as d3 from "d3";
|
||||
import { nodes, stringify } from "jsonpath";
|
||||
import { JsonPath } from "../jsonrpc/types";
|
||||
|
||||
window.onerror = e => remote.dialog.showErrorBox("Unhandled Error", e);
|
||||
|
||||
function remoteEval(expression: string): any {
|
||||
return ipcRenderer.sendSync("remoteEval", expression);
|
||||
}
|
||||
function readFile(uri: string): any {
|
||||
return ipcRenderer.sendSync("readFile", uri);
|
||||
}
|
||||
|
||||
type PipelineNodeState = {
|
||||
state: "running" | "failed" | "complete";
|
||||
outputUris?: string[];
|
||||
finishedAt?: number;
|
||||
};
|
||||
type PipelineNode = {
|
||||
outputArtifact?: string;
|
||||
pluginName: string;
|
||||
configScope: JsonPath;
|
||||
inputs: string[];
|
||||
// artificial
|
||||
key: string;
|
||||
displayName: string[];
|
||||
x: number;
|
||||
y: number;
|
||||
depth: number;
|
||||
state: PipelineNodeState;
|
||||
};
|
||||
|
||||
$(() => {
|
||||
const startTime = remoteEval("startTime");
|
||||
const pipeline: { [name: string]: PipelineNode } = remoteEval("pipeline");
|
||||
|
||||
const depth = (node: PipelineNode) => node.inputs.map(i => depth(pipeline[i]) + 1).reduce((a, b) => Math.max(a, b), 0);
|
||||
const runningTime: (node: PipelineNode) => number | null = node => {
|
||||
const selfFinished = node.state.finishedAt;
|
||||
if (!selfFinished) {
|
||||
return null;
|
||||
}
|
||||
const previousFinished = node.inputs.map(x => pipeline[x].state.finishedAt).reduce((a, b) => !b ? undefined : Math.max(a, b), startTime) || selfFinished;
|
||||
return ((selfFinished || Date.now()) - (previousFinished || selfFinished)) / 1000;
|
||||
};
|
||||
|
||||
const nodes = Object.keys(pipeline).map(key => Object.assign(pipeline[key], { key: key, displayName: key.split("/") }));
|
||||
const links: { source: PipelineNode, target: PipelineNode }[] = [].concat.apply([],
|
||||
nodes.map(node => node.inputs.map(input => {
|
||||
return {
|
||||
source: pipeline[input],
|
||||
target: node
|
||||
};
|
||||
})));
|
||||
|
||||
// horiz layout
|
||||
nodes.forEach(n => n.depth = depth(n));
|
||||
nodes.forEach(n => n.x = 0);
|
||||
nodes.forEach((n, i) => n.y = i / 10);
|
||||
const width = nodes.map(x => x.depth).reduce((a, b) => Math.max(a, b), 0);
|
||||
const height = width * 0.7;
|
||||
|
||||
const simulation = d3.forceSimulation(nodes)
|
||||
.force("charge", d3.forceManyBody().strength(-1).distanceMin(10).distanceMin(30))
|
||||
.force("link", d3.forceLink(links).distance(1).strength(1).iterations(10))
|
||||
.force("y", d3.forceY(0))
|
||||
.stop();
|
||||
for (var i = 0; i < 1; ++i) {
|
||||
simulation.tick();
|
||||
nodes.forEach(n => n.y = Math.min(Math.max(n.y, -height / 2), height / 2));
|
||||
for (let d = 0; d <= width; ++d) {
|
||||
const nodeSet = nodes.filter(n => n.depth === d);
|
||||
const meanY = nodeSet.map(n => n.y).reduce((a, b) => a + b, 0) / nodeSet.length;
|
||||
for (let i = 0; i < nodeSet.length; ++i) {
|
||||
const n = nodeSet[i];
|
||||
n.x = d - width / 2;
|
||||
n.y = meanY + (i - nodeSet.length / 2) * 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const vis = d3.select("#pipelineGraph").attr("viewBox", `0 0 ${width + 2} ${height + 2}`).append("g").attr("transform", `translate(${width / 2 + 1},${height / 2 + 1})`);
|
||||
|
||||
let lastFootprint: string = null;
|
||||
const render = () => {
|
||||
const footprint = JSON.stringify(nodes) + JSON.stringify(links);
|
||||
if (lastFootprint === footprint) return;
|
||||
lastFootprint = footprint;
|
||||
|
||||
const lineData = vis.selectAll("line.edge").data(links);
|
||||
{
|
||||
const enter = lineData.enter().append("line").attr("class", "edge").attr("stroke", "#000").attr("stroke-width", 0.02);
|
||||
lineData.merge(enter)
|
||||
.attr("x1", d => d.source.x.toFixed(3))
|
||||
.attr("y1", d => d.source.y.toFixed(3))
|
||||
.attr("x2", d => d.target.x.toFixed(3))
|
||||
.attr("y2", d => d.target.y.toFixed(3));
|
||||
lineData.exit().remove();
|
||||
}
|
||||
|
||||
const nodeData = vis.selectAll(".node").data(nodes);
|
||||
{
|
||||
const enter = nodeData.enter()
|
||||
.append("g")
|
||||
.attr("class", "node")
|
||||
.attr("stroke", "#000")
|
||||
.attr("fill", "#FFF")
|
||||
.attr("stroke-width", 0.03)
|
||||
.attr("transform", d => `translate(${d.x},${d.y})`)
|
||||
.append("g")
|
||||
.attr("class", "scalable")
|
||||
.on("click", showNodeDetails)
|
||||
// .on("mousedown", d => moveNodeStart(d, d3.event))
|
||||
// .on("mousemove", d => moveNode(d, d3.event))
|
||||
// .on("mouseup", d => moveNodeEnd(d, d3.event))
|
||||
;
|
||||
nodeData.exit().remove();
|
||||
const update = nodeData.select(".scalable").merge(enter);
|
||||
update.selectAll("*").remove();
|
||||
update.append("circle")
|
||||
.attr("r", "0.45em")
|
||||
.attr("fill", d => d.state.state === "running"
|
||||
? "#FFF"
|
||||
: (d.state.state === "complete"
|
||||
? `hsl(${(100 - 100 * Math.min(Math.max((runningTime(d) || 0) / 10, 0), 1)) | 0}, 100%, 80%)`
|
||||
: "#000"));
|
||||
update.append("text")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("style", d => `font-size: ${1.6 / (d.displayName.reduce((a, b) => Math.max(a, b.length), 0) + 1)}em`)
|
||||
.html(d => d.displayName.map((l, i) => `<tspan x="0" y="${(i - (d.displayName.length - 1) / 2) * 1.3 + 0.3}em">${l}</tspan>`).join(""))
|
||||
.attr("fill", d => d.state.state === "failed" ? "#FFF" : "#000").attr("stroke-width", 0);
|
||||
update.append("text")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("y", "3.2em")
|
||||
.attr("style", `font-size: 0.2em; font-weight: bold`)
|
||||
.text(d => {
|
||||
const sec = (runningTime(d) || 0).toFixed(1);
|
||||
return sec === "0.0" || sec === "0.1" ? "" : `${sec}s`;
|
||||
})
|
||||
.attr("fill", "#000").attr("stroke-width", 0);
|
||||
}
|
||||
};
|
||||
|
||||
const update = () => {
|
||||
const states = remoteEval(`tasks`);
|
||||
for (const node of nodes) {
|
||||
node.state = node.state || { state: "running" };
|
||||
node.state.state = states[node.key]._state;
|
||||
node.state.finishedAt = states[node.key]._finishedAt;
|
||||
if (node.state.state === "complete" && !node.state.outputUris) {
|
||||
node.state.outputUris = remoteEval(`tasks[${JSON.stringify(node.key)}]._result().map(x => x.key)`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const refresh = () => { update(); render(); };
|
||||
|
||||
refresh();
|
||||
setInterval(refresh, 100);
|
||||
|
||||
// $("#pipelineGraph").text(JSON.stringify(pipeline, null, 2));
|
||||
// setInterval(() => {
|
||||
// $("body").text(JSON.stringify(3));
|
||||
// }, 1000);
|
||||
});
|
||||
|
||||
function showOverlay(title: string, content: JQuery): void {
|
||||
const overlay = $("<div>");
|
||||
overlay.append($("<h1>")
|
||||
.append($("<button>").text("X").click(() => overlay.remove()))
|
||||
.append($("<span>").text(title)));
|
||||
overlay.append($("<div>").append(content));
|
||||
$("#overlays").append(overlay);
|
||||
}
|
||||
|
||||
function showNodeDetails(node: PipelineNode): void {
|
||||
const table = $("<table>");
|
||||
table.append($("<tr>")
|
||||
.append($("<td>").text("Plugin"))
|
||||
.append($("<td>").text(node.pluginName)));
|
||||
table.append($("<tr>")
|
||||
.append($("<td>").text("Configuration Scope"))
|
||||
.append($("<td>").text(stringify(["$"].concat(node.configScope as any)))));
|
||||
table.append($("<tr>")
|
||||
.append($("<td>").text("Output"))
|
||||
.append($("<td>").append(node.state.outputUris.map(uri => $("<a>")
|
||||
.attr("href", "#")
|
||||
.text(uri)
|
||||
.click(() => showUriDetails(uri))
|
||||
.append($("<br>"))))));
|
||||
|
||||
// ext. extension
|
||||
const extensionName = remoteEval(`(external[${JSON.stringify(node.pluginName)}] || {}).extensionName`);
|
||||
if (extensionName) {
|
||||
table.append($("<tr>")
|
||||
.append($("<td>").text("Extension"))
|
||||
.append($("<td>").append(extensionName)));
|
||||
const traffic: [number, boolean, string][] = remoteEval(`external[${JSON.stringify(node.pluginName)}].inspectTraffic`);
|
||||
for (const [timeStamp, isCore2Ext, payload] of traffic) {
|
||||
const payloadShortened = payload.length > 200 ? payload.substr(0, 200) + "..." : payload;
|
||||
table.append($("<tr>").css("background", isCore2Ext ? "#FEE" : "#EFE")
|
||||
.append($("<td>").text(new Date(timeStamp).toLocaleTimeString() + (isCore2Ext ? " (core => ext)" : " (ext => core)")))
|
||||
.append($("<td>").append($("<a>")
|
||||
.attr("href", "#")
|
||||
.text(payloadShortened)
|
||||
.click(() => showOverlay("", $("<textarea>").val(payload))))));
|
||||
}
|
||||
}
|
||||
showOverlay(node.key, table);
|
||||
}
|
||||
|
||||
function showUriDetails(uri: string): void {
|
||||
const content = $("<pre>").css("font-family", "monospace").text(readFile(uri));
|
||||
content.click(e => {
|
||||
const s = window.getSelection();
|
||||
showBlameTreeDetails(remoteEval(`blame(${JSON.stringify(uri)}, ${JSON.stringify({ index: s.anchorOffset })})`));
|
||||
});
|
||||
showOverlay(uri, content);
|
||||
}
|
||||
|
||||
type BlameTree = { node: any, blaming: BlameTree[] };
|
||||
|
||||
function showBlameTreeDetails(blameTree: BlameTree): void {
|
||||
const content = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
|
||||
const depth = (blameTree: BlameTree) => blameTree.blaming.map(n => depth(n) + 1).reduce((a, b) => Math.max(a, b), 0);
|
||||
const getNodes: (blameTree: BlameTree) => BlameTree[] = blameTree => [blameTree].concat(...blameTree.blaming.map(getNodes));
|
||||
|
||||
const nodes: (BlameTree & {
|
||||
displayName: string[];
|
||||
depth: number;
|
||||
x: number;
|
||||
y: number;
|
||||
})[] = getNodes(blameTree).map(node => Object.assign(node, { displayName: [node.node.source, node.node.line + ":" + node.node.column] } as any));
|
||||
const links: { source: PipelineNode, target: PipelineNode }[] = [].concat.apply([],
|
||||
nodes.map(node => node.blaming.map(input => {
|
||||
return {
|
||||
source: input,
|
||||
target: node
|
||||
};
|
||||
})));
|
||||
|
||||
// horiz layout
|
||||
nodes.forEach(n => n.depth = depth(n));
|
||||
nodes.forEach((n, i) => n.x = i / 10);
|
||||
const height = nodes.map(x => x.depth).reduce((a, b) => Math.max(a, b), 0);
|
||||
let width = 5;
|
||||
for (let i = 0; i <= height; ++i) {
|
||||
width = Math.max(width, nodes.filter(n => n.depth === i).length);
|
||||
}
|
||||
|
||||
const simulation = d3.forceSimulation(nodes)
|
||||
.force("charge", d3.forceManyBody().strength(-1).distanceMin(10).distanceMin(30))
|
||||
.force("link", d3.forceLink(links).distance(1).strength(1).iterations(10))
|
||||
.force("x", d3.forceX(0))
|
||||
.stop();
|
||||
for (var i = 0; i < 1; ++i) {
|
||||
simulation.tick();
|
||||
nodes.forEach(n => n.x = Math.min(Math.max(n.x, -width / 2), width / 2));
|
||||
for (let d = 0; d <= height; ++d) {
|
||||
const nodeSet = nodes.filter(n => n.depth === d);
|
||||
for (let i = 0; i < nodeSet.length; ++i) {
|
||||
const n = nodeSet[i];
|
||||
n.y = 5 * (d - height / 2);
|
||||
n.x = 0 + (i - nodeSet.length / 2) * 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const vis = d3.select(content).attr("viewBox", `0 0 ${width + 2} ${5 * (height + 1)}`).append("g").attr("transform", `translate(${width / 2 + 1},${5 * (height + 1) / 2})`);
|
||||
|
||||
let lastFootprint: string = null;
|
||||
|
||||
const footprint = JSON.stringify(nodes) + JSON.stringify(links);
|
||||
if (lastFootprint === footprint) return;
|
||||
lastFootprint = footprint;
|
||||
|
||||
const lineData = vis.selectAll("line.edge").data(links);
|
||||
{
|
||||
const enter = lineData.enter().append("line").attr("class", "edge").attr("stroke", "#000").attr("stroke-width", 0.02);
|
||||
lineData.merge(enter)
|
||||
.attr("x1", d => d.source.x.toFixed(3))
|
||||
.attr("y1", d => d.source.y.toFixed(3))
|
||||
.attr("x2", d => d.target.x.toFixed(3))
|
||||
.attr("y2", d => d.target.y.toFixed(3));
|
||||
lineData.exit().remove();
|
||||
}
|
||||
|
||||
const nodeData = vis.selectAll(".node").data(nodes);
|
||||
{
|
||||
const enter = nodeData.enter()
|
||||
.append("g")
|
||||
.attr("writing-mode", "tb-rl")
|
||||
.attr("class", "node")
|
||||
.attr("stroke", "#000")
|
||||
.attr("fill", "#FFF")
|
||||
.attr("stroke-width", 0.03)
|
||||
.attr("transform", d => `translate(${d.x},${d.y})`)
|
||||
.append("g")
|
||||
.attr("class", "scalable")
|
||||
.on("click", d => remote.dialog.showMessageBox({ title: d.node.source, message: d.node.name }))
|
||||
;
|
||||
const update = nodeData.select(".scalable").merge(enter);
|
||||
update.selectAll("*").remove();
|
||||
update.append("rect")
|
||||
.attr("width", "0.9em")
|
||||
.attr("height", "4.9em")
|
||||
.attr("x", "-0.45em")
|
||||
.attr("y", "-2.45em")
|
||||
.attr("fill", "#FFF");
|
||||
update.append("text")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", "#000")
|
||||
.attr("style", d => `font-size: ${10 / (d.displayName.reduce((a, b) => Math.max(a, b.length), 0) + 1)}em`)
|
||||
.html(d => d.displayName.map((l, i) => `<tspan y="0" x="${-(i - (d.displayName.length - 1) / 2) * 1.3 - 0.3}em">${l}</tspan>`).join(""))
|
||||
.attr("stroke-width", 0);
|
||||
update.append("text")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("y", "3.2em")
|
||||
.attr("style", `font-size: 0.2em; font-weight: bold`)
|
||||
.text(d => "")
|
||||
.attr("fill", "#000").attr("stroke-width", 0);
|
||||
}
|
||||
showOverlay(`Blame`, $(content));
|
||||
}
|
||||
|
||||
// let deltaX: number | null = null;
|
||||
// let deltaY: number | null = null;
|
||||
|
||||
// function moveNodeStart(node: PipelineNode, e: MouseEvent): void {
|
||||
// deltaX = e.pageX - node.x;
|
||||
// deltaY = e.pageY - node.y;
|
||||
// }
|
||||
|
||||
// function moveNode(node: PipelineNode, e: MouseEvent): void {
|
||||
// if (deltaX !== null) {
|
||||
// node.x = e.pageX - deltaX;
|
||||
// node.y = e.pageY - deltaY;
|
||||
// }
|
||||
// }
|
||||
|
||||
// function moveNodeEnd(node: PipelineNode, e: MouseEvent): void {
|
||||
// deltaX = null;
|
||||
// deltaY = null;
|
||||
// }
|
|
@ -0,0 +1,40 @@
|
|||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const electron_1 = require("electron");
|
||||
const plugin_host_1 = require("./jsonrpc/plugin-host");
|
||||
const fs_1 = require("fs");
|
||||
const js_yaml_1 = require("js-yaml");
|
||||
// required to actually keep process running when window is closed
|
||||
electron_1.app.on('window-all-closed', () => { });
|
||||
const pluginHost = new plugin_host_1.AutoRestPluginHost();
|
||||
pluginHost.Add("autorest-interactive", (initiator) => __awaiter(this, void 0, void 0, function* () {
|
||||
const win = new electron_1.BrowserWindow({});
|
||||
win.maximize();
|
||||
win.setMenu(null);
|
||||
if (yield initiator.GetValue("debug")) {
|
||||
win.webContents.openDevTools();
|
||||
}
|
||||
const readFileListener = (event, uri) => __awaiter(this, void 0, void 0, function* () {
|
||||
event.returnValue = yield initiator.ReadFile(uri);
|
||||
});
|
||||
const remoteEvalListener = (event, expression) => __awaiter(this, void 0, void 0, function* () {
|
||||
event.returnValue = js_yaml_1.safeLoad(yield initiator.GetValue("__status." + new Buffer(expression).toString("base64")));
|
||||
});
|
||||
electron_1.ipcMain.on("readFile", readFileListener);
|
||||
electron_1.ipcMain.on("remoteEval", remoteEvalListener);
|
||||
win.loadURL(`${__dirname}/autorest-interactive/index.html`);
|
||||
yield new Promise(res => win.once("closed", res));
|
||||
electron_1.ipcMain.removeListener("remoteEval", remoteEvalListener);
|
||||
electron_1.ipcMain.removeListener("readFile", readFileListener);
|
||||
}));
|
||||
const parent_stdin = fs_1.createReadStream(null, { fd: 3 });
|
||||
const parent_stdout = fs_1.createWriteStream(null, { fd: 4 });
|
||||
pluginHost.Run(parent_stdin, parent_stdout);
|
|
@ -0,0 +1,32 @@
|
|||
import { app, BrowserWindow, dialog, ipcMain } from "electron";
|
||||
import { AutoRestPluginHost } from "./jsonrpc/plugin-host";
|
||||
import { createReadStream, createWriteStream } from "fs";
|
||||
import { safeLoad } from "js-yaml";
|
||||
|
||||
// required to actually keep process running when window is closed
|
||||
app.on('window-all-closed', () => { })
|
||||
|
||||
const pluginHost = new AutoRestPluginHost();
|
||||
pluginHost.Add("autorest-interactive", async initiator => {
|
||||
const win = new BrowserWindow({});
|
||||
win.maximize();
|
||||
win.setMenu(null);
|
||||
if (await initiator.GetValue("debug")) {
|
||||
win.webContents.openDevTools();
|
||||
}
|
||||
const readFileListener = async (event, uri) => {
|
||||
event.returnValue = await initiator.ReadFile(uri);
|
||||
};
|
||||
const remoteEvalListener = async (event, expression) => {
|
||||
event.returnValue = safeLoad(await initiator.GetValue("__status." + new Buffer(expression).toString("base64")));
|
||||
};
|
||||
ipcMain.on("readFile", readFileListener);
|
||||
ipcMain.on("remoteEval", remoteEvalListener);
|
||||
win.loadURL(`${__dirname}/autorest-interactive/index.html`);
|
||||
await new Promise<void>(res => win.once("closed", res));
|
||||
ipcMain.removeListener("remoteEval", remoteEvalListener);
|
||||
ipcMain.removeListener("readFile", readFileListener);
|
||||
});
|
||||
const parent_stdin = createReadStream(null, { fd: 3 });
|
||||
const parent_stdout = createWriteStream(null, { fd: 4 });
|
||||
pluginHost.Run(parent_stdin, parent_stdout);
|
|
@ -0,0 +1,87 @@
|
|||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
|
||||
var IAutoRestPluginTarget_Types;
|
||||
(function (IAutoRestPluginTarget_Types) {
|
||||
IAutoRestPluginTarget_Types.GetPluginNames = new vscode_jsonrpc_1.RequestType0("GetPluginNames");
|
||||
IAutoRestPluginTarget_Types.Process = new vscode_jsonrpc_1.RequestType2("Process");
|
||||
})(IAutoRestPluginTarget_Types || (IAutoRestPluginTarget_Types = {}));
|
||||
var IAutoRestPluginInitiator_Types;
|
||||
(function (IAutoRestPluginInitiator_Types) {
|
||||
IAutoRestPluginInitiator_Types.ReadFile = new vscode_jsonrpc_1.RequestType2("ReadFile");
|
||||
IAutoRestPluginInitiator_Types.GetValue = new vscode_jsonrpc_1.RequestType2("GetValue");
|
||||
IAutoRestPluginInitiator_Types.ListInputs = new vscode_jsonrpc_1.RequestType1("ListInputs");
|
||||
IAutoRestPluginInitiator_Types.WriteFile = new vscode_jsonrpc_1.NotificationType4("WriteFile");
|
||||
IAutoRestPluginInitiator_Types.Message = new vscode_jsonrpc_1.NotificationType2("Message");
|
||||
})(IAutoRestPluginInitiator_Types || (IAutoRestPluginInitiator_Types = {}));
|
||||
class AutoRestPluginHost {
|
||||
constructor() {
|
||||
this.plugins = {};
|
||||
}
|
||||
Add(name, handler) {
|
||||
this.plugins[name] = handler;
|
||||
}
|
||||
Run(input = process.stdin, output = process.stdout) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// connection setup
|
||||
const channel = vscode_jsonrpc_1.createMessageConnection(input, output, {
|
||||
error(message) { console.error("error: ", message); },
|
||||
info(message) { console.error("info: ", message); },
|
||||
log(message) { console.error("log: ", message); },
|
||||
warn(message) { console.error("warn: ", message); }
|
||||
});
|
||||
channel.onRequest(IAutoRestPluginTarget_Types.GetPluginNames, () => __awaiter(this, void 0, void 0, function* () { return Object.keys(this.plugins); }));
|
||||
channel.onRequest(IAutoRestPluginTarget_Types.Process, (pluginName, sessionId) => __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
const handler = this.plugins[pluginName];
|
||||
if (!handler) {
|
||||
throw new Error(`Plugin host could not find requested plugin '${pluginName}'.`);
|
||||
}
|
||||
yield handler({
|
||||
ReadFile(filename) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return yield channel.sendRequest(IAutoRestPluginInitiator_Types.ReadFile, sessionId, filename);
|
||||
});
|
||||
},
|
||||
GetValue(key) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return yield channel.sendRequest(IAutoRestPluginInitiator_Types.GetValue, sessionId, key);
|
||||
});
|
||||
},
|
||||
ListInputs() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return yield channel.sendRequest(IAutoRestPluginInitiator_Types.ListInputs, sessionId);
|
||||
});
|
||||
},
|
||||
WriteFile(filename, content, sourceMap) {
|
||||
channel.sendNotification(IAutoRestPluginInitiator_Types.WriteFile, sessionId, filename, content, sourceMap);
|
||||
},
|
||||
Message(message) {
|
||||
channel.sendNotification(IAutoRestPluginInitiator_Types.Message, sessionId, message);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
channel.sendNotification(IAutoRestPluginInitiator_Types.Message, sessionId, {
|
||||
Channel: "fatal",
|
||||
Text: "" + e,
|
||||
Details: e
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
// activate
|
||||
channel.listen();
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.AutoRestPluginHost = AutoRestPluginHost;
|
|
@ -0,0 +1,95 @@
|
|||
import {
|
||||
createMessageConnection, Logger,
|
||||
RequestType0, RequestType1, RequestType2,
|
||||
NotificationType2, NotificationType4
|
||||
} from "vscode-jsonrpc";
|
||||
import { Readable } from "stream";
|
||||
import { Mapping, Message, RawSourceMap } from "./types";
|
||||
|
||||
module IAutoRestPluginTarget_Types {
|
||||
export const GetPluginNames = new RequestType0<string[], Error, void>("GetPluginNames");
|
||||
export const Process = new RequestType2<string, string, boolean, Error, void>("Process");
|
||||
}
|
||||
interface IAutoRestPluginTarget {
|
||||
GetPluginNames(): Promise<string[]>;
|
||||
Process(pluginName: string, sessionId: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
module IAutoRestPluginInitiator_Types {
|
||||
export const ReadFile = new RequestType2<string, string, string, Error, void>("ReadFile");
|
||||
export const GetValue = new RequestType2<string, string, any, Error, void>("GetValue");
|
||||
export const ListInputs = new RequestType1<string, string[], Error, void>("ListInputs");
|
||||
export const WriteFile = new NotificationType4<string, string, string, Mapping[] | RawSourceMap | undefined, void>("WriteFile");
|
||||
export const Message = new NotificationType2<string, Message, void>("Message");
|
||||
}
|
||||
interface IAutoRestPluginInitiator {
|
||||
ReadFile(filename: string): Promise<string>;
|
||||
GetValue(key: string): Promise<any>;
|
||||
ListInputs(): Promise<string[]>;
|
||||
|
||||
WriteFile(filename: string, content: string, sourceMap?: Mapping[] | RawSourceMap): void;
|
||||
Message(message: Message): void;
|
||||
}
|
||||
|
||||
type AutoRestPluginHandler = (initiator: IAutoRestPluginInitiator) => Promise<void>;
|
||||
|
||||
export class AutoRestPluginHost {
|
||||
private readonly plugins: { [name: string]: AutoRestPluginHandler } = {};
|
||||
|
||||
public Add(name: string, handler: AutoRestPluginHandler): void {
|
||||
this.plugins[name] = handler;
|
||||
}
|
||||
|
||||
public async Run(input: NodeJS.ReadableStream = process.stdin, output: NodeJS.WritableStream = process.stdout): Promise<void> {
|
||||
// connection setup
|
||||
const channel = createMessageConnection(
|
||||
input,
|
||||
output,
|
||||
{
|
||||
error(message) { console.error("error: ", message); },
|
||||
info(message) { console.error("info: ", message); },
|
||||
log(message) { console.error("log: ", message); },
|
||||
warn(message) { console.error("warn: ", message); }
|
||||
}
|
||||
);
|
||||
|
||||
channel.onRequest(IAutoRestPluginTarget_Types.GetPluginNames, async () => Object.keys(this.plugins));
|
||||
channel.onRequest(IAutoRestPluginTarget_Types.Process, async (pluginName: string, sessionId: string) => {
|
||||
try {
|
||||
const handler = this.plugins[pluginName];
|
||||
if (!handler) {
|
||||
throw new Error(`Plugin host could not find requested plugin '${pluginName}'.`);
|
||||
}
|
||||
await handler({
|
||||
async ReadFile(filename: string): Promise<string> {
|
||||
return await channel.sendRequest(IAutoRestPluginInitiator_Types.ReadFile, sessionId, filename);
|
||||
},
|
||||
async GetValue(key: string): Promise<any> {
|
||||
return await channel.sendRequest(IAutoRestPluginInitiator_Types.GetValue, sessionId, key);
|
||||
},
|
||||
async ListInputs(): Promise<string[]> {
|
||||
return await channel.sendRequest(IAutoRestPluginInitiator_Types.ListInputs, sessionId);
|
||||
},
|
||||
|
||||
WriteFile(filename: string, content: string, sourceMap?: Mapping[] | RawSourceMap): void {
|
||||
channel.sendNotification(IAutoRestPluginInitiator_Types.WriteFile, sessionId, filename, content, sourceMap);
|
||||
},
|
||||
Message(message: Message): void {
|
||||
channel.sendNotification(IAutoRestPluginInitiator_Types.Message, sessionId, message);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
channel.sendNotification(IAutoRestPluginInitiator_Types.Message, sessionId, <Message>{
|
||||
Channel: "fatal" as any,
|
||||
Text: "" + e,
|
||||
Details: e
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// activate
|
||||
channel.listen();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@ -0,0 +1,37 @@
|
|||
/* line: 1-based, column: 0-based */
|
||||
export type Position = {
|
||||
line: number; // 1-based
|
||||
column: number; // 0-based
|
||||
} | { path?: JsonPath };
|
||||
|
||||
export type JsonPath = (string | number)[];
|
||||
|
||||
export interface SourceLocation {
|
||||
document: string;
|
||||
Position: Position;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
Channel: "information" | "warning" | "error" | "debug" | "verbose";
|
||||
Key?: Iterable<string>;
|
||||
Details?: any;
|
||||
Text: string;
|
||||
Source?: Array<SourceLocation>;
|
||||
}
|
||||
|
||||
export interface RawSourceMap {
|
||||
file?: string;
|
||||
sourceRoot?: string;
|
||||
version: string;
|
||||
sources: string[];
|
||||
names: string[];
|
||||
sourcesContent?: string[];
|
||||
mappings: string;
|
||||
}
|
||||
|
||||
export interface Mapping {
|
||||
generated: Position;
|
||||
original: Position;
|
||||
source: string;
|
||||
name?: string;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es2016",
|
||||
"dom"
|
||||
],
|
||||
"module": "commonjs",
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"target": "es2016"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
Загрузка…
Ссылка в новой задаче