@tsplc/debug 0.0.0-stage → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/format.js +121 -0
- package/dist/format.js.map +11 -0
- package/dist/index.js +1681 -0
- package/dist/index.js.map +24 -0
- package/dist/manifest.js +201 -0
- package/dist/manifest.js.map +10 -0
- package/dist/paths.js +138 -0
- package/dist/paths.js.map +10 -0
- package/package.json +46 -4
- package/src/adapter.ts +59 -0
- package/src/api.ts +182 -0
- package/src/debugger.ts +509 -0
- package/src/hardware.ts +125 -0
- package/src/http.ts +155 -0
- package/src/index.ts +8 -0
- package/src/jobs.ts +115 -0
- package/src/login.ts +284 -0
- package/src/manifest.ts +271 -0
- package/src/opcua.ts +69 -0
- package/src/paths.ts +185 -0
- package/src/telemetry.ts +110 -0
- package/src/timeline.ts +78 -0
- package/src/trace.ts +45 -0
- package/src/ui/bits.ts +41 -0
- package/src/ui/client.ts +48 -0
- package/src/ui/dev.ts +579 -0
- package/src/ui/dom.ts +168 -0
- package/src/ui/fixture.ts +548 -0
- package/src/ui/format.ts +125 -0
- package/src/ui/hardware.ts +368 -0
- package/src/ui/header.ts +179 -0
- package/src/ui/index.ts +20 -0
- package/src/ui/jobs.ts +249 -0
- package/src/ui/main.ts +74 -0
- package/src/ui/overview.ts +123 -0
- package/src/ui/page.ts +178 -0
- package/src/ui/recent.ts +322 -0
- package/src/ui/requests.ts +81 -0
- package/src/ui/store.ts +227 -0
- package/src/ui/tree.ts +156 -0
- package/src/ui/variables.ts +469 -0
- package/src/watch.ts +32 -0
- package/README.md +0 -3
package/src/ui/dom.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { DeviceState } from "../api.ts";
|
|
2
|
+
import type { DebugDevice, SourceRange } from "../manifest.ts";
|
|
3
|
+
import {
|
|
4
|
+
formatReadAt,
|
|
5
|
+
formatValue,
|
|
6
|
+
recordLocation,
|
|
7
|
+
sourceLocation,
|
|
8
|
+
valueStateLabels,
|
|
9
|
+
} from "./format.ts";
|
|
10
|
+
import type { Store, Topic } from "./store.ts";
|
|
11
|
+
import type { TreeNode } from "./tree.ts";
|
|
12
|
+
|
|
13
|
+
type Child = Node | string | null | undefined | false;
|
|
14
|
+
type Attribute = string | number | boolean | undefined | ((event: Event) => void);
|
|
15
|
+
|
|
16
|
+
export const h = <K extends keyof HTMLElementTagNameMap>(
|
|
17
|
+
tag: K,
|
|
18
|
+
attributes: Record<string, Attribute> = {},
|
|
19
|
+
...children: Child[]
|
|
20
|
+
): HTMLElementTagNameMap[K] => {
|
|
21
|
+
const element = document.createElement(tag);
|
|
22
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
23
|
+
if (value === undefined || value === false) continue;
|
|
24
|
+
if (typeof value === "function") element.addEventListener(name.slice(2), value);
|
|
25
|
+
else element.setAttribute(name, value === true ? "" : String(value));
|
|
26
|
+
}
|
|
27
|
+
return fill(element, ...children);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Replaces an element's children, skipping the absent ones. */
|
|
31
|
+
export const fill = <E extends Element>(element: E, ...children: Child[]) => {
|
|
32
|
+
element.replaceChildren(...children.filter((child): child is Node | string => Boolean(child)));
|
|
33
|
+
return element;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** A page: built once per route, then told which store topics changed each frame. */
|
|
37
|
+
export interface View {
|
|
38
|
+
element: HTMLElement;
|
|
39
|
+
update(topics: Set<Topic>): void;
|
|
40
|
+
dispose?(): void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const empty = (text: string) => h("p", { class: "empty" }, text);
|
|
44
|
+
|
|
45
|
+
const locationLink = (
|
|
46
|
+
{ label, href }: { label: string; href?: string },
|
|
47
|
+
title: string,
|
|
48
|
+
text?: string,
|
|
49
|
+
) =>
|
|
50
|
+
href
|
|
51
|
+
? h("a", { class: "src", href, title: `Open ${title} in VS Code` }, text ?? label)
|
|
52
|
+
: h("span", { class: "src", title }, text ?? label);
|
|
53
|
+
|
|
54
|
+
export const sourceLink = (store: Store, range: SourceRange, text?: string) => {
|
|
55
|
+
const location = sourceLocation(store.manifest, store.session.projectRoot, range);
|
|
56
|
+
return locationLink(location, location.label, text);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** A telemetry record's `source`, a project-relative `file:line`. */
|
|
60
|
+
export const recordSourceLink = (store: Store, source: string) =>
|
|
61
|
+
locationLink(recordLocation(store.session.projectRoot, source), source);
|
|
62
|
+
|
|
63
|
+
const variableHref = (path: string) => `#/variables?path=${encodeURIComponent(path)}`;
|
|
64
|
+
|
|
65
|
+
export const pathLink = (path: string, text: Node | string = path) =>
|
|
66
|
+
h("a", { class: "path", href: variableHref(path), title: `Show ${path} in Variables` }, text);
|
|
67
|
+
|
|
68
|
+
/** A path that wraps only between its segments. */
|
|
69
|
+
export const wrappingPath = (path: string) =>
|
|
70
|
+
h("span", {}, ...path.split(/(?=[.[])/).flatMap((segment) => [h("wbr"), segment]));
|
|
71
|
+
|
|
72
|
+
const setText = (element: HTMLElement, text: string) => {
|
|
73
|
+
if (element.textContent !== text) element.textContent = text;
|
|
74
|
+
};
|
|
75
|
+
const setClass = (element: HTMLElement, name: string) => {
|
|
76
|
+
if (element.className !== name) element.className = name;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** A variable's value and its quality and age, kept current by `update()` from the store. */
|
|
80
|
+
export class LiveValue {
|
|
81
|
+
readonly value = h("span");
|
|
82
|
+
readonly meta = h("span");
|
|
83
|
+
|
|
84
|
+
constructor(
|
|
85
|
+
private readonly store: Store,
|
|
86
|
+
readonly node: TreeNode,
|
|
87
|
+
) {
|
|
88
|
+
this.update();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Whether the stream should carry this path. */
|
|
92
|
+
get subscribed() {
|
|
93
|
+
return this.node.readable && !this.node.constant;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
update() {
|
|
97
|
+
const { node, store } = this;
|
|
98
|
+
if (node.constant)
|
|
99
|
+
return this.show(
|
|
100
|
+
"constant",
|
|
101
|
+
formatValue(store.manifest, node.type, node.constant.value),
|
|
102
|
+
"constant",
|
|
103
|
+
);
|
|
104
|
+
if (!node.readable)
|
|
105
|
+
return this.show(
|
|
106
|
+
"unavailable",
|
|
107
|
+
"—",
|
|
108
|
+
"not readable",
|
|
109
|
+
"This build can't read it from the controller",
|
|
110
|
+
);
|
|
111
|
+
const { sample, state } = store.live(node.path);
|
|
112
|
+
if (!sample) return this.show("unavailable", "—", "not read", "Not read yet");
|
|
113
|
+
const meta =
|
|
114
|
+
state === "unavailable" || state === "bad"
|
|
115
|
+
? valueStateLabels[state]
|
|
116
|
+
: `${valueStateLabels[state]} · ${formatReadAt(sample.receivedAt)}`;
|
|
117
|
+
this.show(
|
|
118
|
+
state,
|
|
119
|
+
formatValue(store.manifest, node.type, sample.value),
|
|
120
|
+
meta,
|
|
121
|
+
`${sample.status}\nread ${sample.receivedAt}`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private show(state: string, value: string, meta: string, title?: string) {
|
|
126
|
+
setClass(this.value, `value s-${state}`);
|
|
127
|
+
setClass(this.meta, `meta s-${state}`);
|
|
128
|
+
setText(this.value, value);
|
|
129
|
+
setText(this.meta, meta);
|
|
130
|
+
if (title !== undefined && this.meta.title !== title) this.meta.title = title;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export type Health = "ok" | "fault" | "stale" | "unknown";
|
|
135
|
+
|
|
136
|
+
interface HealthView {
|
|
137
|
+
health: Health;
|
|
138
|
+
text: string;
|
|
139
|
+
detail: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* A device's health as far as its driver reported it: "unknown" without a health target, "needs
|
|
144
|
+
* login" while nothing can read it, never "OK" by default.
|
|
145
|
+
*/
|
|
146
|
+
export const deviceHealth = (store: Store, device: DebugDevice): HealthView => {
|
|
147
|
+
const state: DeviceState | undefined = store.devices.get(device.id);
|
|
148
|
+
const { connection } = store.session;
|
|
149
|
+
if (!device.health)
|
|
150
|
+
return {
|
|
151
|
+
health: "unknown",
|
|
152
|
+
text: "unknown",
|
|
153
|
+
detail: "This build reads no health for this device",
|
|
154
|
+
};
|
|
155
|
+
if (connection.state === "unavailable")
|
|
156
|
+
return { health: "unknown", text: "needs login", detail: connection.error ?? "" };
|
|
157
|
+
if (!state || state.health.state === "unknown")
|
|
158
|
+
return { health: "unknown", text: "unknown", detail: "The driver's state has not been read" };
|
|
159
|
+
const { health } = state;
|
|
160
|
+
const detail = [health.name, health.message].filter(Boolean).join(" · ");
|
|
161
|
+
if (health.state === "stale") return { health: "stale", text: "stale", detail };
|
|
162
|
+
if (!store.connected || health.quality !== "good")
|
|
163
|
+
return { health: "stale", text: `stale, was ${health.state}`, detail };
|
|
164
|
+
return { health: health.state, text: health.state === "ok" ? "OK" : health.state, detail };
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const healthBadge = (health: HealthView) =>
|
|
168
|
+
h("span", { class: `badge h-${health.health}`, title: health.detail }, health.text);
|