dsh-codegraph-init 0.0.0-stage → 0.1.1
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/CHANGELOG.md +42 -0
- package/LICENSE +21 -0
- package/README.md +76 -2
- package/cordis.patch.yml +3 -0
- package/icon.svg +8 -0
- package/lib/client.js +691 -0
- package/lib/index.js +1122 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +59 -4
package/lib/client.js
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codegraph wiring, browser half.
|
|
3
|
+
*
|
|
4
|
+
* Adds two rows to the sidebar workspace "…" menu — install and uninstall —
|
|
5
|
+
* and answers both with one dialog that streams the Host's step log. The
|
|
6
|
+
* dialog lives on `shell.overlay` because a menu row unmounts with its menu.
|
|
7
|
+
*
|
|
8
|
+
* This file is served raw as a classic script and registers a factory; every
|
|
9
|
+
* side effect, including `React.createElement` work, stays inside it. No
|
|
10
|
+
* Harness client package is required: the dialog is plain DOM plus the
|
|
11
|
+
* `--dsw-alias-*` theme tokens, so a missing or renamed primitive cannot break
|
|
12
|
+
* the plugin.
|
|
13
|
+
*/
|
|
14
|
+
window.__ModuleLoader__.load({
|
|
15
|
+
id: "dsh-codegraph-init",
|
|
16
|
+
factory(require) {
|
|
17
|
+
var module = { exports: {} };
|
|
18
|
+
var exports = module.exports;
|
|
19
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
20
|
+
var react = require("react");
|
|
21
|
+
var h = react.createElement;
|
|
22
|
+
|
|
23
|
+
/** Dictionary namespace owned by this plugin. */
|
|
24
|
+
var NS = "codegraphInit";
|
|
25
|
+
|
|
26
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
27
|
+
var zh = {
|
|
28
|
+
"menu.install": "安装 codegraph",
|
|
29
|
+
"menu.uninstall": "卸载 codegraph",
|
|
30
|
+
"dialog.install.title": "安装 codegraph 并接入 DSH?",
|
|
31
|
+
"dialog.uninstall.title": "卸载 codegraph 接线?",
|
|
32
|
+
"dialog.install.desc": "会全局安装 npm 包 codegraph(已装则跳过),然后把一个 codegraph MCP 服务写进当前 DSH 家目录。",
|
|
33
|
+
"dialog.uninstall.desc": "会移除 DSH 的 MCP 接线与说明块。不会删除任何仓库里的 .codegraph/ 索引,也不会动你在别处配好的其它 agent。",
|
|
34
|
+
"scope": "工作区",
|
|
35
|
+
"status.loading": "正在读取当前状态…",
|
|
36
|
+
"status.failed": "读取状态失败",
|
|
37
|
+
"status.cli": "codegraph CLI",
|
|
38
|
+
"status.cli.missing": "未安装",
|
|
39
|
+
"status.cli.present": "{version} · {path}",
|
|
40
|
+
"status.wiring": "DSH 接线",
|
|
41
|
+
"status.wiring.on": "已接入 {path}",
|
|
42
|
+
"status.wiring.off": "未接入 {path}",
|
|
43
|
+
"status.target": "dsh installer 目标",
|
|
44
|
+
"status.target.yes": "当前 CLI 支持,交给 codegraph install 执行",
|
|
45
|
+
"status.target.no": "当前 npm 版还没有该目标,由插件直接写接线",
|
|
46
|
+
"notes.heading": "安装会做三件事",
|
|
47
|
+
"notes.cli": "确保 PATH 上有 codegraph(npm install -g)。",
|
|
48
|
+
"notes.wiring": "在 cordis.patch.yml 里加一条 codegraph MCP 服务,并在 AGENTS.md 里加一段使用说明。",
|
|
49
|
+
"notes.restart": "新开一个会话后生效;若 dsh-hmr 已关闭,需要重启 DSH。",
|
|
50
|
+
"notes.uninstall.heading": "卸载会做两件事",
|
|
51
|
+
"notes.uninstall.wiring": "移除 cordis.patch.yml 里的 codegraph 行与 AGENTS.md 里的说明块。",
|
|
52
|
+
"notes.uninstall.cli": "保留全局 CLI(可在下面勾选一并移除),.codegraph/ 索引不受影响。",
|
|
53
|
+
"option.removeCli": "同时移除全局 CLI(npm uninstall -g codegraph)",
|
|
54
|
+
"log.heading": "执行日志",
|
|
55
|
+
"log.empty": "等待开始…",
|
|
56
|
+
"log.truncated": "(较早的 {n} 行已省略)",
|
|
57
|
+
"action.install": "开始安装",
|
|
58
|
+
"action.uninstall": "开始卸载",
|
|
59
|
+
"action.cancel": "中止并关闭",
|
|
60
|
+
"action.close": "关闭",
|
|
61
|
+
"action.again": "重新运行",
|
|
62
|
+
"action.starting": "正在启动…",
|
|
63
|
+
"result.ok": "完成",
|
|
64
|
+
"result.failed": "失败",
|
|
65
|
+
"result.cancelled": "已中止",
|
|
66
|
+
"result.hint": "把上面日志里的失败原因贴给 agent,通常就能定位。",
|
|
67
|
+
"close": "关闭"
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
71
|
+
var en = {
|
|
72
|
+
"menu.install": "Install codegraph",
|
|
73
|
+
"menu.uninstall": "Uninstall codegraph",
|
|
74
|
+
"dialog.install.title": "Install codegraph and wire it into DSH?",
|
|
75
|
+
"dialog.uninstall.title": "Remove the codegraph wiring?",
|
|
76
|
+
"dialog.install.desc": "Installs the codegraph npm package globally (skipped when present), then writes one codegraph MCP server into this DSH home.",
|
|
77
|
+
"dialog.uninstall.desc": "Removes the DSH wiring and its instructions block. No repository .codegraph/ index is touched, and other agents you configured elsewhere stay as they are.",
|
|
78
|
+
"scope": "Workspace",
|
|
79
|
+
"status.loading": "Reading the current state…",
|
|
80
|
+
"status.failed": "Could not read the current state",
|
|
81
|
+
"status.cli": "codegraph CLI",
|
|
82
|
+
"status.cli.missing": "not installed",
|
|
83
|
+
"status.cli.present": "{version} · {path}",
|
|
84
|
+
"status.wiring": "DSH wiring",
|
|
85
|
+
"status.wiring.on": "wired in {path}",
|
|
86
|
+
"status.wiring.off": "not wired in {path}",
|
|
87
|
+
"status.target": "dsh installer target",
|
|
88
|
+
"status.target.yes": "this CLI has it; codegraph install runs the wiring",
|
|
89
|
+
"status.target.no": "the published npm build lacks it, so the plugin writes the wiring",
|
|
90
|
+
"notes.heading": "Installing does three things",
|
|
91
|
+
"notes.cli": "Makes sure codegraph is on PATH (npm install -g).",
|
|
92
|
+
"notes.wiring": "Adds one codegraph MCP server to cordis.patch.yml and a usage block to AGENTS.md.",
|
|
93
|
+
"notes.restart": "Takes effect in a new session; restart DSH if dsh-hmr is off.",
|
|
94
|
+
"notes.uninstall.heading": "Uninstalling does two things",
|
|
95
|
+
"notes.uninstall.wiring": "Drops the codegraph row from cordis.patch.yml and the block from AGENTS.md.",
|
|
96
|
+
"notes.uninstall.cli": "Keeps the global CLI (tick the box below to remove it too); .codegraph/ indexes are untouched.",
|
|
97
|
+
"option.removeCli": "Also remove the global CLI (npm uninstall -g codegraph)",
|
|
98
|
+
"log.heading": "Run log",
|
|
99
|
+
"log.empty": "Waiting to start…",
|
|
100
|
+
"log.truncated": "({n} earlier lines omitted)",
|
|
101
|
+
"action.install": "Install",
|
|
102
|
+
"action.uninstall": "Uninstall",
|
|
103
|
+
"action.cancel": "Abort and close",
|
|
104
|
+
"action.close": "Close",
|
|
105
|
+
"action.again": "Run again",
|
|
106
|
+
"action.starting": "Starting…",
|
|
107
|
+
"result.ok": "Done",
|
|
108
|
+
"result.failed": "Failed",
|
|
109
|
+
"result.cancelled": "Aborted",
|
|
110
|
+
"result.hint": "Paste the failure from the log into a chat with an agent; it usually pinpoints the cause.",
|
|
111
|
+
"close": "Close"
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* One tiny observable store: the slot hook face wants a
|
|
116
|
+
* `{getSnapshot, subscribe}` pair, and nothing else here needs a store
|
|
117
|
+
* library.
|
|
118
|
+
*
|
|
119
|
+
* @param {*} initial - Starting snapshot.
|
|
120
|
+
* @returns {{getSnapshot: Function, subscribe: Function, set: Function}} the store.
|
|
121
|
+
*/
|
|
122
|
+
function createStore(initial) {
|
|
123
|
+
var value = initial;
|
|
124
|
+
var listeners = new Set();
|
|
125
|
+
return {
|
|
126
|
+
getSnapshot: function getSnapshot() {
|
|
127
|
+
return value;
|
|
128
|
+
},
|
|
129
|
+
subscribe: function subscribe(listener) {
|
|
130
|
+
listeners.add(listener);
|
|
131
|
+
return function unsubscribe() {
|
|
132
|
+
listeners.delete(listener);
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
set: function set(next) {
|
|
136
|
+
value = next;
|
|
137
|
+
Array.from(listeners).forEach(function notify(listener) {
|
|
138
|
+
listener();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Call one `codegraphInstall` host method over the ordinary API RPC
|
|
146
|
+
* channel and unwrap its result envelope.
|
|
147
|
+
*
|
|
148
|
+
* @param {object} ctx - Client context carrying the Connection.
|
|
149
|
+
* @param {string} method - Host method name.
|
|
150
|
+
* @param {object} request - Wire request body.
|
|
151
|
+
* @returns {Promise<*>} the decoded host result.
|
|
152
|
+
*/
|
|
153
|
+
function callHost(ctx, method, request) {
|
|
154
|
+
return Promise.resolve(ctx.connection.rpc.call("/api", "codegraphInstall/" + method, { args: { request: request } })).then(function unwrap(result) {
|
|
155
|
+
if (!result.ok) {
|
|
156
|
+
var failure = new Error(result.error && result.error.message ? result.error.message : "the host refused codegraphInstall/" + method);
|
|
157
|
+
failure.code = result.error && result.error.code;
|
|
158
|
+
throw failure;
|
|
159
|
+
}
|
|
160
|
+
return result.value;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Stylesheet: hover and focus states need real CSS, so one is injected once. */
|
|
165
|
+
var STYLE_ID = "dsh-codegraph-init-style";
|
|
166
|
+
var CSS = [
|
|
167
|
+
".dshcg-backdrop{position:fixed;inset:0;z-index:2147482000;display:flex;align-items:center;justify-content:center;padding:24px;background:var(--dsw-alias-bg-mask-2,rgba(0,0,0,.45));}",
|
|
168
|
+
".dshcg-card{display:flex;flex-direction:column;width:min(680px,100%);max-height:min(80vh,760px);overflow:hidden;border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,.14));border-radius:14px;background:var(--dsw-alias-bg-elevated,#1e1f23);color:var(--dsw-alias-label-primary,#eee);box-shadow:var(--dsw-elevation-panel,0 24px 64px rgba(0,0,0,.45));font-family:inherit;}",
|
|
169
|
+
".dshcg-head{display:flex;align-items:flex-start;gap:12px;padding:18px 20px 12px;}",
|
|
170
|
+
".dshcg-head h2{margin:0;font-size:15px;font-weight:600;line-height:1.4;flex:1;}",
|
|
171
|
+
".dshcg-x{flex:none;width:24px;height:24px;display:grid;place-items:center;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary,#999);cursor:pointer;font-size:15px;line-height:1;}",
|
|
172
|
+
".dshcg-x:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.08));color:var(--dsw-alias-label-primary,#eee);}",
|
|
173
|
+
".dshcg-body{display:flex;flex-direction:column;gap:14px;padding:0 20px 4px;overflow:auto;}",
|
|
174
|
+
".dshcg-desc{margin:0;font-size:13px;line-height:1.65;color:var(--dsw-alias-label-secondary,#b6b8bd);}",
|
|
175
|
+
".dshcg-meta{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;font-size:12px;line-height:1.6;}",
|
|
176
|
+
".dshcg-meta dt{color:var(--dsw-alias-label-tertiary,#8b8e95);white-space:nowrap;}",
|
|
177
|
+
".dshcg-meta dd{margin:0;color:var(--dsw-alias-label-secondary,#b6b8bd);word-break:break-all;}",
|
|
178
|
+
".dshcg-section{font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--dsw-alias-label-tertiary,#8b8e95);}",
|
|
179
|
+
".dshcg-list{margin:0;padding-left:18px;font-size:12.5px;line-height:1.7;color:var(--dsw-alias-label-secondary,#b6b8bd);}",
|
|
180
|
+
".dshcg-check{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--dsw-alias-label-secondary,#b6b8bd);cursor:pointer;}",
|
|
181
|
+
".dshcg-log{min-height:132px;max-height:280px;overflow:auto;margin:0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.09));border-radius:10px;background:var(--dsw-alias-bg-l1,rgba(255,255,255,.03));font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.6;}",
|
|
182
|
+
".dshcg-log div{white-space:pre-wrap;word-break:break-word;}",
|
|
183
|
+
".dshcg-step{color:var(--dsw-alias-label-primary,#eee);font-weight:600;}",
|
|
184
|
+
".dshcg-out{color:var(--dsw-alias-label-secondary,#b6b8bd);}",
|
|
185
|
+
".dshcg-note{color:var(--dsw-alias-label-tertiary,#8b8e95);}",
|
|
186
|
+
".dshcg-err{color:var(--dsw-alias-label-error,#f2737a);}",
|
|
187
|
+
".dshcg-ok{color:var(--dsw-alias-state-business-primary,#4ec07a);}",
|
|
188
|
+
".dshcg-empty{padding:10px 12px;font-size:12px;color:var(--dsw-alias-label-tertiary,#8b8e95);}",
|
|
189
|
+
".dshcg-banner{margin:0;padding:9px 12px;border-radius:9px;font-size:12.5px;line-height:1.6;border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,.14));}",
|
|
190
|
+
".dshcg-banner[data-tone=ok]{border-color:var(--dsw-alias-state-business-primary,#4ec07a);color:var(--dsw-alias-state-business-primary,#4ec07a);}",
|
|
191
|
+
".dshcg-banner[data-tone=bad]{border-color:var(--dsw-alias-state-error-primary,#f2737a);color:var(--dsw-alias-state-error-primary,#f2737a);}",
|
|
192
|
+
".dshcg-foot{display:flex;justify-content:flex-end;gap:8px;padding:14px 20px 18px;}",
|
|
193
|
+
".dshcg-btn{min-width:88px;height:32px;padding:0 14px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,.16));background:transparent;color:var(--dsw-alias-label-primary,#eee);font-size:13px;font-family:inherit;cursor:pointer;}",
|
|
194
|
+
".dshcg-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.08));}",
|
|
195
|
+
".dshcg-btn:disabled{opacity:.5;cursor:default;}",
|
|
196
|
+
".dshcg-btn[data-variant=primary]{border-color:transparent;background:var(--dsw-alias-button-primary-fill,#4b7bf5);color:var(--dsw-alias-label-on-color,#fff);}",
|
|
197
|
+
".dshcg-btn[data-variant=primary]:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,#5b88f7);}",
|
|
198
|
+
".dshcg-btn[data-variant=quiet]{border-color:transparent;color:var(--dsw-alias-label-secondary,#b6b8bd);}"
|
|
199
|
+
].join("");
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Install the stylesheet once, and remove it when the plugin unloads.
|
|
203
|
+
*
|
|
204
|
+
* @returns {Function} disposer.
|
|
205
|
+
*/
|
|
206
|
+
function installStyles() {
|
|
207
|
+
if (document.getElementById(STYLE_ID) !== null) return function noop() {};
|
|
208
|
+
var element = document.createElement("style");
|
|
209
|
+
element.id = STYLE_ID;
|
|
210
|
+
element.textContent = CSS;
|
|
211
|
+
document.head.appendChild(element);
|
|
212
|
+
return function removeStyles() {
|
|
213
|
+
element.remove();
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Interpolate `{name}` placeholders; the dictionaries stay plain data.
|
|
219
|
+
*
|
|
220
|
+
* @param {string} template - Localized string.
|
|
221
|
+
* @param {object} params - Replacement values.
|
|
222
|
+
* @returns {string} the rendered string.
|
|
223
|
+
*/
|
|
224
|
+
function fill(template, params) {
|
|
225
|
+
return template.replace(/\{(\w+)\}/gu, function replace(match, key) {
|
|
226
|
+
return params[key] === undefined ? match : String(params[key]);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** One graph icon, drawn locally so no icon package has to be present. */
|
|
231
|
+
function GraphIcon(props) {
|
|
232
|
+
var size = props && props.size ? props.size : 14;
|
|
233
|
+
return h(
|
|
234
|
+
"svg",
|
|
235
|
+
{ width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" },
|
|
236
|
+
h("circle", { cx: 6, cy: 6, r: 2.4 }),
|
|
237
|
+
h("circle", { cx: 18, cy: 6, r: 2.4 }),
|
|
238
|
+
h("circle", { cx: 12, cy: 18, r: 2.4 }),
|
|
239
|
+
h("path", { d: "M8.4 6h7.2" }),
|
|
240
|
+
h("path", { d: "M7.3 8.1 10.8 15.9" }),
|
|
241
|
+
h("path", { d: "M16.7 8.1 13.2 15.9" }),
|
|
242
|
+
props && props.slash ? h("path", { d: "M4 20 20 4" }) : null
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Keyed icon for the install row. */
|
|
247
|
+
function IconInstall(props) {
|
|
248
|
+
return h(GraphIcon, { size: (props && props.icon && props.icon.size) || 14 });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Keyed icon for the uninstall row. */
|
|
252
|
+
function IconUninstall(props) {
|
|
253
|
+
return h(GraphIcon, { size: (props && props.icon && props.icon.size) || 14, slash: true });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* The dialog shell: backdrop, card, header, scrolling body and footer.
|
|
258
|
+
*
|
|
259
|
+
* @param {object} props - Title, description, footer, body and close handler.
|
|
260
|
+
* @returns {object} the overlay element.
|
|
261
|
+
*/
|
|
262
|
+
function DialogShell(props) {
|
|
263
|
+
var onClose = props.onClose;
|
|
264
|
+
var dismissible = props.dismissible !== false;
|
|
265
|
+
|
|
266
|
+
// While a run streams, an accidental click outside or Escape would
|
|
267
|
+
// abort an in-flight `npm install -g`; only the explicit buttons in
|
|
268
|
+
// the footer close the dialog then.
|
|
269
|
+
react.useEffect(
|
|
270
|
+
function watchEscape() {
|
|
271
|
+
if (!dismissible) return undefined;
|
|
272
|
+
function onKeyDown(event) {
|
|
273
|
+
if (event.key === "Escape") onClose();
|
|
274
|
+
}
|
|
275
|
+
document.addEventListener("keydown", onKeyDown);
|
|
276
|
+
return function stopWatching() {
|
|
277
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
278
|
+
};
|
|
279
|
+
},
|
|
280
|
+
[dismissible, onClose]
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return h(
|
|
284
|
+
"div",
|
|
285
|
+
{
|
|
286
|
+
className: "dshcg-backdrop",
|
|
287
|
+
role: "presentation",
|
|
288
|
+
onMouseDown: function onBackdrop(event) {
|
|
289
|
+
if (!dismissible) return;
|
|
290
|
+
if (event.target === event.currentTarget) onClose();
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
h(
|
|
294
|
+
"div",
|
|
295
|
+
{ className: "dshcg-card", role: "dialog", "aria-modal": "true", "aria-label": props.title },
|
|
296
|
+
h(
|
|
297
|
+
"div",
|
|
298
|
+
{ className: "dshcg-head" },
|
|
299
|
+
h("h2", null, props.title),
|
|
300
|
+
h(
|
|
301
|
+
"button",
|
|
302
|
+
{ type: "button", className: "dshcg-x", "aria-label": props.closeLabel, onClick: onClose },
|
|
303
|
+
"✕"
|
|
304
|
+
)
|
|
305
|
+
),
|
|
306
|
+
h("div", { className: "dshcg-body" }, props.description === undefined ? null : h("p", { className: "dshcg-desc" }, props.description), props.children),
|
|
307
|
+
h("div", { className: "dshcg-foot" }, props.footer)
|
|
308
|
+
)
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* One status row pair for the meta list.
|
|
314
|
+
*
|
|
315
|
+
* @param {object} props - Term and detail.
|
|
316
|
+
* @returns {Array} `<dt>` and `<dd>`.
|
|
317
|
+
*/
|
|
318
|
+
function MetaRow(props) {
|
|
319
|
+
return [h("dt", { key: props.term }, props.term), h("dd", { key: props.term + ":v" }, props.detail)];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* The streaming log panel.
|
|
324
|
+
*
|
|
325
|
+
* @param {object} props - Lines, dropped count and empty copy.
|
|
326
|
+
* @returns {object} the panel.
|
|
327
|
+
*/
|
|
328
|
+
function LogPanel(props) {
|
|
329
|
+
var view = props.view;
|
|
330
|
+
var lines = view.lines;
|
|
331
|
+
return h(
|
|
332
|
+
"div",
|
|
333
|
+
null,
|
|
334
|
+
h("div", { className: "dshcg-section" }, props.heading),
|
|
335
|
+
h(
|
|
336
|
+
"div",
|
|
337
|
+
{ className: "dshcg-log", role: "log", "aria-live": "polite" },
|
|
338
|
+
view.dropped > 0 ? h("div", { className: "dshcg-note" }, fill(props.truncatedLabel, { n: view.dropped })) : null,
|
|
339
|
+
lines.length === 0 ? h("div", { className: "dshcg-note" }, props.emptyLabel) : null,
|
|
340
|
+
lines.map(function renderLine(line) {
|
|
341
|
+
return h("div", { key: line.seq, className: "dshcg-" + line.kind }, line.text);
|
|
342
|
+
})
|
|
343
|
+
)
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* The whole dialog: status, options, log and the run state machine.
|
|
349
|
+
*
|
|
350
|
+
* @param {object} props - Request hook, host calls and the locale.
|
|
351
|
+
* @returns {object|null} the dialog, or nothing while unrequested.
|
|
352
|
+
*/
|
|
353
|
+
function CodegraphDialog(props) {
|
|
354
|
+
var request = props.useCodegraphRequest(function pick(pending) {
|
|
355
|
+
return pending;
|
|
356
|
+
});
|
|
357
|
+
if (request === null) return null;
|
|
358
|
+
return h(DialogBody, Object.assign({}, props, { request: request }));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* The request-scoped body. Mounted per request, so its in-flight state
|
|
363
|
+
* dies with the request that raised it.
|
|
364
|
+
*
|
|
365
|
+
* @param {object} props - Same as the dialog, plus the captured request.
|
|
366
|
+
* @returns {object} the dialog contents.
|
|
367
|
+
*/
|
|
368
|
+
function DialogBody(props) {
|
|
369
|
+
var request = props.request;
|
|
370
|
+
var t = props.t;
|
|
371
|
+
var settle = props.settleCodegraphRequest;
|
|
372
|
+
var installing = request.action === "install";
|
|
373
|
+
|
|
374
|
+
var statusState = react.useState(null);
|
|
375
|
+
var status = statusState[0];
|
|
376
|
+
var setStatus = statusState[1];
|
|
377
|
+
var loadErrorState = react.useState(null);
|
|
378
|
+
var loadError = loadErrorState[0];
|
|
379
|
+
var setLoadError = loadErrorState[1];
|
|
380
|
+
|
|
381
|
+
var viewState = react.useState({ lines: [], dropped: 0, done: false, ok: null, error: null, running: false });
|
|
382
|
+
var view = viewState[0];
|
|
383
|
+
var setView = viewState[1];
|
|
384
|
+
|
|
385
|
+
var runState = react.useState(null);
|
|
386
|
+
var runId = runState[0];
|
|
387
|
+
var setRunId = runState[1];
|
|
388
|
+
var startErrorState = react.useState(null);
|
|
389
|
+
var startError = startErrorState[0];
|
|
390
|
+
var setStartError = startErrorState[1];
|
|
391
|
+
var removeCliState = react.useState(false);
|
|
392
|
+
var removeCli = removeCliState[0];
|
|
393
|
+
var setRemoveCli = removeCliState[1];
|
|
394
|
+
var startingState = react.useState(false);
|
|
395
|
+
var starting = startingState[0];
|
|
396
|
+
var setStarting = startingState[1];
|
|
397
|
+
|
|
398
|
+
var cursor = react.useRef(0);
|
|
399
|
+
|
|
400
|
+
react.useEffect(function loadStatus() {
|
|
401
|
+
var live = true;
|
|
402
|
+
props.getStatus().then(function apply(next) {
|
|
403
|
+
if (live) setStatus(next);
|
|
404
|
+
}).catch(function fail(reason) {
|
|
405
|
+
if (live) setLoadError(reason instanceof Error ? reason.message : String(reason));
|
|
406
|
+
});
|
|
407
|
+
return function cleanup() {
|
|
408
|
+
live = false;
|
|
409
|
+
};
|
|
410
|
+
}, []);
|
|
411
|
+
|
|
412
|
+
react.useEffect(function poll() {
|
|
413
|
+
if (runId === null || view.done) return undefined;
|
|
414
|
+
var live = true;
|
|
415
|
+
var timer = setInterval(function tick() {
|
|
416
|
+
props.pollRun(runId, cursor.current).then(function apply(next) {
|
|
417
|
+
if (!live) return;
|
|
418
|
+
cursor.current = next.next;
|
|
419
|
+
if (next.lines.length === 0 && next.dropped === view.dropped && !next.done) return;
|
|
420
|
+
setView(function merge(prev) {
|
|
421
|
+
return {
|
|
422
|
+
lines: prev.lines.concat(next.lines),
|
|
423
|
+
dropped: next.dropped,
|
|
424
|
+
done: next.done,
|
|
425
|
+
ok: next.ok,
|
|
426
|
+
error: next.error,
|
|
427
|
+
running: !next.done
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
}).catch(function fail(reason) {
|
|
431
|
+
if (!live) return;
|
|
432
|
+
setView(function merge(prev) {
|
|
433
|
+
return Object.assign({}, prev, { done: true, ok: false, running: false, error: reason instanceof Error ? reason.message : String(reason) });
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
}, 600);
|
|
437
|
+
return function cleanup() {
|
|
438
|
+
live = false;
|
|
439
|
+
clearInterval(timer);
|
|
440
|
+
};
|
|
441
|
+
}, [runId, view.done]);
|
|
442
|
+
|
|
443
|
+
react.useEffect(function forgetOnUnmount() {
|
|
444
|
+
return function cleanup() {
|
|
445
|
+
if (runId !== null) props.forgetRun(runId);
|
|
446
|
+
};
|
|
447
|
+
}, [runId]);
|
|
448
|
+
|
|
449
|
+
var running = starting || view.running;
|
|
450
|
+
var finish = view.done;
|
|
451
|
+
|
|
452
|
+
var start = function start() {
|
|
453
|
+
if (running) return;
|
|
454
|
+
setStarting(true);
|
|
455
|
+
setStartError(null);
|
|
456
|
+
cursor.current = 0;
|
|
457
|
+
setRunId(null);
|
|
458
|
+
setView({ lines: [], dropped: 0, done: false, ok: null, error: null, running: true });
|
|
459
|
+
props.startRun({ action: request.action, removeCli: removeCli }).then(function launched(result) {
|
|
460
|
+
setStarting(false);
|
|
461
|
+
setRunId(result.runId);
|
|
462
|
+
}).catch(function fail(reason) {
|
|
463
|
+
setStarting(false);
|
|
464
|
+
setStartError(reason instanceof Error ? reason.message : String(reason));
|
|
465
|
+
setView({ lines: [], dropped: 0, done: true, ok: false, error: reason instanceof Error ? reason.message : String(reason), running: false });
|
|
466
|
+
});
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
var close = function close() {
|
|
470
|
+
if (running && runId !== null) props.cancelRun(runId).catch(function ignore() {});
|
|
471
|
+
settle();
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
var title = installing ? t("dialog.install.title") : t("dialog.uninstall.title");
|
|
475
|
+
var description = installing ? t("dialog.install.desc") : t("dialog.uninstall.desc");
|
|
476
|
+
|
|
477
|
+
var scopeRow = request.path
|
|
478
|
+
? h("dl", { className: "dshcg-meta", key: "scope" }, MetaRow({ term: t("scope"), detail: request.displayTitle + " · " + request.path }))
|
|
479
|
+
: null;
|
|
480
|
+
|
|
481
|
+
var statusBlock;
|
|
482
|
+
if (loadError !== null) {
|
|
483
|
+
statusBlock = h("p", { className: "dshcg-banner", "data-tone": "bad" }, t("status.failed") + ": " + loadError);
|
|
484
|
+
} else if (status === null) {
|
|
485
|
+
statusBlock = h("div", { className: "dshcg-empty" }, t("status.loading"));
|
|
486
|
+
} else {
|
|
487
|
+
var cli = status.cli;
|
|
488
|
+
var wiring = status.wiring;
|
|
489
|
+
statusBlock = h(
|
|
490
|
+
"dl",
|
|
491
|
+
{ className: "dshcg-meta" },
|
|
492
|
+
MetaRow({
|
|
493
|
+
term: t("status.cli"),
|
|
494
|
+
detail: cli.found ? fill(t("status.cli.present"), { version: cli.version || "?", path: cli.path || "PATH" }) : t("status.cli.missing")
|
|
495
|
+
}),
|
|
496
|
+
MetaRow({
|
|
497
|
+
term: t("status.wiring"),
|
|
498
|
+
detail: fill(wiring.configured ? t("status.wiring.on") : t("status.wiring.off"), { path: wiring.home.patchPath })
|
|
499
|
+
}),
|
|
500
|
+
MetaRow({ term: t("status.target"), detail: status.cliSupportsDshTarget ? t("status.target.yes") : t("status.target.no") })
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
var notes = installing
|
|
505
|
+
? h(
|
|
506
|
+
"div",
|
|
507
|
+
null,
|
|
508
|
+
h("div", { className: "dshcg-section" }, t("notes.heading")),
|
|
509
|
+
h(
|
|
510
|
+
"ul",
|
|
511
|
+
{ className: "dshcg-list" },
|
|
512
|
+
h("li", null, t("notes.cli")),
|
|
513
|
+
h("li", null, t("notes.wiring")),
|
|
514
|
+
h("li", null, t("notes.restart"))
|
|
515
|
+
)
|
|
516
|
+
)
|
|
517
|
+
: h(
|
|
518
|
+
"div",
|
|
519
|
+
null,
|
|
520
|
+
h("div", { className: "dshcg-section" }, t("notes.uninstall.heading")),
|
|
521
|
+
h(
|
|
522
|
+
"ul",
|
|
523
|
+
{ className: "dshcg-list" },
|
|
524
|
+
h("li", null, t("notes.uninstall.wiring")),
|
|
525
|
+
h("li", null, t("notes.uninstall.cli"))
|
|
526
|
+
),
|
|
527
|
+
h(
|
|
528
|
+
"label",
|
|
529
|
+
{ className: "dshcg-check" },
|
|
530
|
+
h("input", {
|
|
531
|
+
type: "checkbox",
|
|
532
|
+
checked: removeCli,
|
|
533
|
+
disabled: running,
|
|
534
|
+
onChange: function onChange(event) {
|
|
535
|
+
setRemoveCli(event.target.checked);
|
|
536
|
+
}
|
|
537
|
+
}),
|
|
538
|
+
t("option.removeCli")
|
|
539
|
+
)
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
var banner = null;
|
|
543
|
+
if (finish) {
|
|
544
|
+
var tone = view.ok === true ? "ok" : "bad";
|
|
545
|
+
var label = view.ok === true ? t("result.ok") : view.error === "cancelled" ? t("result.cancelled") : t("result.failed");
|
|
546
|
+
banner = h(
|
|
547
|
+
"p",
|
|
548
|
+
{ className: "dshcg-banner", "data-tone": tone, role: "status" },
|
|
549
|
+
label + (view.ok === true ? "" : view.error ? ":" + view.error : ""),
|
|
550
|
+
view.ok === true ? null : h("span", { className: "dshcg-note" }, " " + t("result.hint"))
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
var primaryLabel = installing ? t("action.install") : t("action.uninstall");
|
|
555
|
+
var footer;
|
|
556
|
+
if (running) {
|
|
557
|
+
footer = [h("button", { key: "cancel", type: "button", className: "dshcg-btn", "data-variant": "quiet", onClick: close }, t("action.cancel"))];
|
|
558
|
+
} else if (finish) {
|
|
559
|
+
footer = [
|
|
560
|
+
h("button", { key: "again", type: "button", className: "dshcg-btn", onClick: start }, t("action.again")),
|
|
561
|
+
h("button", { key: "close", type: "button", className: "dshcg-btn", "data-variant": "primary", onClick: close }, t("action.close"))
|
|
562
|
+
];
|
|
563
|
+
} else {
|
|
564
|
+
footer = [
|
|
565
|
+
h("button", { key: "close", type: "button", className: "dshcg-btn", onClick: close }, t("action.close")),
|
|
566
|
+
h(
|
|
567
|
+
"button",
|
|
568
|
+
{ key: "go", type: "button", className: "dshcg-btn", "data-variant": "primary", disabled: starting, onClick: start },
|
|
569
|
+
starting ? t("action.starting") : primaryLabel
|
|
570
|
+
)
|
|
571
|
+
];
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return h(
|
|
575
|
+
DialogShell,
|
|
576
|
+
{ title: title, description: description, closeLabel: t("close"), onClose: close, dismissible: !view.running, footer: footer },
|
|
577
|
+
scopeRow,
|
|
578
|
+
statusBlock,
|
|
579
|
+
startError === null ? null : h("p", { className: "dshcg-banner", "data-tone": "bad" }, startError),
|
|
580
|
+
notes,
|
|
581
|
+
banner,
|
|
582
|
+
LogPanel({
|
|
583
|
+
view: view,
|
|
584
|
+
heading: t("log.heading"),
|
|
585
|
+
emptyLabel: t("log.empty"),
|
|
586
|
+
truncatedLabel: t("log.truncated")
|
|
587
|
+
})
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** Services this half needs; the rest is reached through the slot system. */
|
|
592
|
+
var inject = ["slots", "locale", "connection", "contextMenus"];
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Register the dictionaries, the two menu rows, the keyed icons and the
|
|
596
|
+
* overlay dialog.
|
|
597
|
+
*
|
|
598
|
+
* @param {object} ctx - Client root context.
|
|
599
|
+
* @returns {void}
|
|
600
|
+
*/
|
|
601
|
+
function apply(ctx) {
|
|
602
|
+
var request = createStore(null);
|
|
603
|
+
var t = ctx.locale.bind(NS);
|
|
604
|
+
var open = function open(next) {
|
|
605
|
+
request.set(next);
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
ctx.effect(installStyles, "codegraph: dialog styles");
|
|
609
|
+
ctx.effect(function registerLocale() {
|
|
610
|
+
return ctx.locale.register(NS, { zh: zh, en: en });
|
|
611
|
+
}, "codegraph: dictionaries");
|
|
612
|
+
|
|
613
|
+
ctx.slots.inject("contextMenu.icon", function* icons() {
|
|
614
|
+
yield ctx.slots.register({ name: "contextMenu.icon", key: "codegraph.install" }, IconInstall);
|
|
615
|
+
yield ctx.slots.register({ name: "contextMenu.icon", key: "codegraph.uninstall" }, IconUninstall);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
ctx.effect(function registerInstall() {
|
|
619
|
+
return ctx.contextMenus.register({
|
|
620
|
+
id: "codegraph.install",
|
|
621
|
+
target: "workspace",
|
|
622
|
+
order: 240,
|
|
623
|
+
resolve: function resolve(target) {
|
|
624
|
+
return [{
|
|
625
|
+
id: "install",
|
|
626
|
+
label: t("menu.install"),
|
|
627
|
+
icon: { key: "codegraph.install" },
|
|
628
|
+
separatorBefore: true,
|
|
629
|
+
run: function run() {
|
|
630
|
+
open({ action: "install", workspaceId: target.workspaceId, path: target.path, displayTitle: target.displayTitle });
|
|
631
|
+
}
|
|
632
|
+
}];
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
}, "codegraph: install menu action");
|
|
636
|
+
|
|
637
|
+
ctx.effect(function registerUninstall() {
|
|
638
|
+
return ctx.contextMenus.register({
|
|
639
|
+
id: "codegraph.uninstall",
|
|
640
|
+
target: "workspace",
|
|
641
|
+
order: 250,
|
|
642
|
+
resolve: function resolve(target) {
|
|
643
|
+
return [{
|
|
644
|
+
id: "uninstall",
|
|
645
|
+
label: t("menu.uninstall"),
|
|
646
|
+
icon: { key: "codegraph.uninstall" },
|
|
647
|
+
run: function run() {
|
|
648
|
+
open({ action: "uninstall", workspaceId: target.workspaceId, path: target.path, displayTitle: target.displayTitle });
|
|
649
|
+
}
|
|
650
|
+
}];
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
}, "codegraph: uninstall menu action");
|
|
654
|
+
|
|
655
|
+
ctx.slots.inject("shell.overlay", function overlay() {
|
|
656
|
+
return ctx.slots.register({
|
|
657
|
+
name: "shell.overlay",
|
|
658
|
+
id: "dsh-codegraph-init",
|
|
659
|
+
locale: NS,
|
|
660
|
+
inject: function injected() {
|
|
661
|
+
return {
|
|
662
|
+
hooks: { codegraphRequest: request },
|
|
663
|
+
settleCodegraphRequest: function settle() {
|
|
664
|
+
request.set(null);
|
|
665
|
+
},
|
|
666
|
+
getStatus: function getStatus() {
|
|
667
|
+
return callHost(ctx, "status", {});
|
|
668
|
+
},
|
|
669
|
+
startRun: function startRun(body) {
|
|
670
|
+
return callHost(ctx, "start", body);
|
|
671
|
+
},
|
|
672
|
+
pollRun: function pollRun(id, after) {
|
|
673
|
+
return callHost(ctx, "poll", { runId: id, after: after });
|
|
674
|
+
},
|
|
675
|
+
cancelRun: function cancelRun(id) {
|
|
676
|
+
return callHost(ctx, "cancel", { runId: id });
|
|
677
|
+
},
|
|
678
|
+
forgetRun: function forgetRun(id) {
|
|
679
|
+
return callHost(ctx, "forget", { runId: id });
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
}, CodegraphDialog);
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
exports.inject = inject;
|
|
688
|
+
exports.apply = apply;
|
|
689
|
+
return module.exports;
|
|
690
|
+
}
|
|
691
|
+
});
|