Improved typescript visualizer. Opens browser window in chrome appmode by default.

This commit is contained in:
Henning Dieterichs
2019-08-05 02:09:06 +02:00
parent fe2f5c95b7
commit 85692ac720
12 changed files with 187 additions and 28 deletions

View File

@@ -70,6 +70,7 @@ export interface TreeNode<TExtraData> {
id: string | undefined;
name: string;
value: string | undefined;
emphasizedValue: string | undefined;
children: TreeNode<TExtraData>[];
data: TExtraData;
isMarked: boolean;

View File

@@ -29,8 +29,9 @@ export class TypeScriptAstDataExtractor
var member = object[key];
if (member === value) return key;
if (Array.isArray(member) && member.indexOf(value) !== -1)
if (Array.isArray(member) && member.indexOf(value) !== -1) {
return key;
}
}
return null;
@@ -39,14 +40,14 @@ export class TypeScriptAstDataExtractor
function toTreeNode(
node: ts.Node,
memberName: string,
marked: Set<ts.Node>
marked: Set<ts.Node>,
emphasizedValueFn: (node: ts.Node) => string | undefined
): CommonDataTypes.AstData["root"] {
const name = tsApi.SyntaxKind[node.kind];
const children = node
.getChildren()
.map((childNode, idx) => {
let parentPropertyName = findKey(childNode, node) || "";
if (childNode.kind == tsApi.SyntaxKind.SyntaxList) {
childNode.getChildren().some(c => {
parentPropertyName = findKey(c, node) || "";
@@ -60,7 +61,12 @@ export class TypeScriptAstDataExtractor
parentPropertyName = "" + idx;
}
return toTreeNode(childNode, parentPropertyName, marked);
return toTreeNode(
childNode,
parentPropertyName,
marked,
emphasizedValueFn
);
})
.filter(c => c !== null);
@@ -68,6 +74,8 @@ export class TypeScriptAstDataExtractor
if (tsApi.isIdentifier(node)) {
value = node.text;
} else if (tsApi.isLiteralExpression(node)) {
value = node.text;
}
return {
@@ -78,6 +86,7 @@ export class TypeScriptAstDataExtractor
length: 0,
position: 0,
},
emphasizedValue: emphasizedValueFn(node),
isMarked: marked.has(node),
value,
// startPos: node.pos,
@@ -89,15 +98,36 @@ export class TypeScriptAstDataExtractor
return typeof node === "object" && (tsApi as any).isNode(node);
}
if (isNode(data) || (Array.isArray(data) && data.every(isNode))) {
if (
isNode(data) ||
(Array.isArray(data) && data.every(isNode)) ||
(typeof data === "object" &&
data &&
Object.entries(data).every(([k, v]) => k === "fn" || isNode(v)))
) {
let root: ts.SourceFile;
let marked: Set<ts.Node>;
let fn: (n: ts.Node) => string | undefined = (n: ts.Node) =>
undefined;
if (Array.isArray(data)) {
root = (data[0] as ts.Node).getSourceFile();
marked = new Set(data);
} else {
} else if (isNode(data)) {
root = data.getSourceFile();
marked = new Set([data]);
} else {
marked = new Set();
const map = new Map<ts.Node, string>();
fn = (n: ts.Node) => map.get(n);
for (const [k, v] of Object.entries(data)) {
if (k === "fn") {
fn = v;
} else {
root = v.getSourceFile();
marked.add(v);
map.set(v, k);
}
}
}
collector.addExtraction({
@@ -107,7 +137,7 @@ export class TypeScriptAstDataExtractor
extractData() {
return {
kind: { text: true, tree: true, ast: true },
root: toTreeNode(root, "root", marked),
root: toTreeNode(root, "root", marked, fn),
text: root.text,
fileType: "ts",
};

View File

@@ -40,6 +40,7 @@
"@hediet/typed-json-rpc-websocket-server": "^0.7.7",
"@types/express": "^4.17.0",
"@types/serve-static": "^1.13.2",
"chrome-launcher": "^0.11.2",
"express": "^4.17.1",
"open": "^6.4.0",
"serve-static": "^1.14.1",

View File

@@ -43,7 +43,11 @@ export class ConnectionHandler {
);
},
openInBrowser: async ({}) => {
open(server.indexUrl);
try {
await launchChrome(server.indexUrl);
} catch (e) {
open(server.indexUrl);
}
},
setPreferredDataExtractor: async ({ dataExtractorId }) => {
if (this.watcher) {
@@ -70,3 +74,13 @@ export class ConnectionHandler {
});
}
}
import chromeLauncher = require("chrome-launcher");
async function launchChrome(url: string): Promise<void> {
const chrome = await chromeLauncher.launch({
startingUrl: url,
// `--window-size=${width},${height}`
chromeFlags: ["--app=" + url],
});
}

View File

@@ -26,12 +26,11 @@ export class Server {
}
public get indexUrl(): string {
return `http://localhost:${this.port}/index.html?serverPort=${
this.port
}`;
const port = process.env.USE_DEV_UI ? 8080 : this.port;
return `http://localhost:${port}/index.html?serverPort=${this.port}`;
}
public get mainBundle(): string {
public get mainBundleUrl(): string {
return `http://localhost:${this.port}/main.js`;
}

View File

@@ -53,9 +53,6 @@ export class WebViews {
}
export class WebView {
private expression: string | null = null;
private lastFrameId: number | null = null;
constructor(private readonly webviewPanel: WebviewPanel) {}
}
@@ -89,11 +86,9 @@ export function getHtml(server: Server) {
</script>
${
isDev
? `<iframe src="http://localhost:8080/?serverPort=${
server.port
}" tabindex="0"></iframe>`
? `<iframe src="${server.indexUrl}"></iframe>`
: `<script type="text/javascript" src="${
server.mainBundle
server.mainBundleUrl
}"></script>`
}
</body>

View File

@@ -5,7 +5,11 @@ import {
registerUpdateReconciler,
getReloadCount,
} from "@hediet/node-reload";
enableHotReload({ entryModule: module, loggingEnabled: true });
if (process.env.USE_DEV_UI) {
enableHotReload({ entryModule: module, loggingEnabled: true });
}
import { Disposable } from "@hediet/std/disposable";
import { createJsDebuggerSource } from "./DataSource/JsDebuggerSource";
@@ -27,9 +31,11 @@ export class Extension {
private readonly views = this.dispose.track(new WebViews(this.server));
constructor() {
const i = this.dispose.track(window.createStatusBarItem());
i.text = "reload" + getReloadCount(module);
i.show();
if (getReloadCount(module) > 0) {
const i = this.dispose.track(window.createStatusBarItem());
i.text = "reload" + getReloadCount(module);
i.show();
}
this.views.createNew();
}
}

5
extension/src/types.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare namespace NodeJS {
declare interface ProcessEnv {
USE_DEV_UI?: "true";
}
}

View File

@@ -92,6 +92,7 @@ export class TreeNodeViewModel {
public readonly id: string | undefined,
public readonly name: string,
public readonly value: string | undefined,
public readonly emphasizedValue: string | undefined,
public readonly children: TreeNodeViewModel[]
) {}
@@ -167,6 +168,20 @@ export class TreeNodeViewModel {
}
}
const isValidFunctionName = (function() {
var validName = /^[$A-Z_][0-9A-Z_$]*$/i;
var reserved = {
abstract: true,
boolean: true,
// ...
with: true,
} as any;
return function(s: string) {
// Ensure a valid name and not reserved.
return validName.test(s) && !reserved[s];
};
})();
@observer
export class TreeWithPathView extends React.Component<{
model: TreeViewModel;
@@ -178,7 +193,39 @@ export class TreeWithPathView extends React.Component<{
<div className="part-path">
<span>
{model.selected ? (
<span>{model.selected.path.join(" / ")}</span>
<span>
{model.selected.path.reduce((acc, v) => {
acc = acc.slice();
function add() {
acc.push(
<span
className="part-path-item"
key={acc.length}
>
{v}
</span>
);
}
if (isValidFunctionName(v)) {
if (acc.length > 0) {
acc.push(
<span key={acc.length}>.</span>
);
}
add();
} else {
acc.push(
<span key={acc.length}>[</span>
);
add();
acc.push(
<span key={acc.length}>]</span>
);
}
return acc;
}, new Array<React.ReactElement>())}
</span>
) : (
"(Nothing Selected)"
)}
@@ -428,6 +475,11 @@ export class TreeNodeView extends React.Component<{
{model.value && (
<span className="part-value">{model.value}</span>
)}
{model.emphasizedValue && (
<span className="part-emphasized-value">
{model.emphasizedValue}
</span>
)}
</span>
</div>

View File

@@ -1,4 +1,9 @@
import { TreeViewModel, TreeNodeViewModel, TreeView } from "./Views";
import {
TreeViewModel,
TreeNodeViewModel,
TreeView,
TreeWithPathView,
} from "./Views";
import {
CommonDataTypes,
ExtractedData,
@@ -31,6 +36,7 @@ export function createTreeViewModelFromTreeNodeData(
node.id,
node.name,
node.value,
node.emphasizedValue,
children
);
model.isMarked = node.isMarked;
@@ -53,7 +59,7 @@ export class TreeVisualizer extends VisualizationProvider {
priority: 100,
render() {
const m = createTreeViewModelFromTreeNodeData(data.root);
return <TreeView model={m} />;
return <TreeWithPathView model={m} />;
},
});
}

View File

@@ -58,6 +58,19 @@
}
.part-value {
color: darkgreen;
text-decoration: underline;
/*&::before,
&::after {
content: '"';
}*/
padding: 1px 3px;
margin-left: 5px;
}
.part-emphasized-value {
color: darkred;
border: black solid 1px;
padding: 1px 3px;
@@ -99,6 +112,14 @@
white-space: pre;
padding: 5px 10px;
border-bottom: solid gray 1px;
color: lightgray;
.part-path-item {
color: black;
margin-left: 1px;
margin-right: 1px;
}
}
.part-tree {
overflow: auto;

View File

@@ -833,6 +833,17 @@ chownr@^1.1.1:
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6"
integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==
chrome-launcher@^0.11.2:
version "0.11.2"
resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.11.2.tgz#c9a248dbccd3a08565553acf61adff879bcc982c"
integrity sha512-jx0kJDCXdB2ARcDMwNCtrf04oY1Up4rOmVu+fqJ5MTPOOIG8EhRcEU9NZfXZc6dMw9FU8o1r21PNp8V2M0zQ+g==
dependencies:
"@types/node" "*"
is-wsl "^2.1.0"
lighthouse-logger "^1.0.0"
mkdirp "0.5.1"
rimraf "^2.6.1"
chrome-trace-event@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
@@ -1059,7 +1070,7 @@ date-now@^0.1.4:
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -1981,6 +1992,11 @@ is-wsl@^1.1.0:
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
is-wsl@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.0.tgz#94369bbeb2249ef07b831b1b08590e686330ccbb"
integrity sha512-pFTjpv/x5HRj8kbZ/Msxi9VrvtOMRBqaDi3OIcbwPI3OuH+r3lLxVWukLITBaOGJIbA/w2+M1eVmVa4XNQlAmQ==
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@@ -2079,6 +2095,14 @@ lcid@^2.0.0:
dependencies:
invert-kv "^2.0.0"
lighthouse-logger@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/lighthouse-logger/-/lighthouse-logger-1.2.0.tgz#b76d56935e9c137e86a04741f6bb9b2776e886ca"
integrity sha512-wzUvdIeJZhRsG6gpZfmSCfysaxNEr43i+QT+Hie94wvHDKFLi4n7C2GqZ4sTC+PH5b5iktmXJvU87rWvhP3lHw==
dependencies:
debug "^2.6.8"
marky "^1.2.0"
loader-runner@^2.3.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
@@ -2150,6 +2174,11 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
marky@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.1.tgz#a3fcf82ffd357756b8b8affec9fdbf3a30dc1b02"
integrity sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ==
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@@ -2306,7 +2335,7 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=