dsh-code-server-app 0.3.20 → 0.3.22
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/README.en.md +82 -19
- package/README.md +74 -21
- package/assets/extensions/dshcs-editor-bridge/extension.js +120 -64
- package/assets/extensions/dshcs-editor-bridge/lib/ask-panel.js +343 -174
- package/assets/extensions/dshcs-editor-bridge/lib/bridge-client.js +30 -4
- package/assets/extensions/dshcs-editor-bridge/package.json +2 -2
- package/assets/extensions/dshcs-editor-bridge/webview/THIRD-PARTY.md +21 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Typewriter-Regular-VBYJ4NRC.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/src/app.jsx +189 -0
- package/assets/extensions/dshcs-editor-bridge/webview/src/approval.jsx +53 -0
- package/assets/extensions/dshcs-editor-bridge/webview/src/official-tokens.css +8 -0
- package/assets/extensions/dshcs-editor-bridge/webview/src/panel.css +283 -0
- package/assets/extensions/dshcs-editor-bridge/webview/src/thread.jsx +72 -0
- package/assets/extensions/dshcs-editor-bridge/webview/thread.css +1 -0
- package/assets/extensions/dshcs-editor-bridge/webview/thread.js +462 -0
- package/lib/bridge-approval.mjs +189 -0
- package/lib/bridge-session.mjs +3 -1
- package/lib/bridge-thread.mjs +345 -0
- package/lib/bridge.mjs +15 -6
- package/lib/index.js +142 -17
- package/package.json +29 -6
- package/vendor/VENDOR.json +1 -1
- package/lib/bridge-answer.mjs +0 -155
|
@@ -13,8 +13,13 @@
|
|
|
13
13
|
// 就什么都不做。状态栏不显示任何东西,更不弹通知。
|
|
14
14
|
// 3. **绝不覆盖未保存改动**:agent 改了文件而该文档是脏的就只告警 + 给 diff,由用户决定。
|
|
15
15
|
//
|
|
16
|
-
// 「问 DSH」面板(0.2.0
|
|
17
|
-
//
|
|
16
|
+
// 「问 DSH」面板(0.2.0 起;0.2.3 起正文走 DSH 官方渲染器):
|
|
17
|
+
// - 提问走 `/ask`(host 侧以**用户输入**进对话);
|
|
18
|
+
// - 回答**不再是另一条通道**:host 用 `sessionController.follow` 把该会话的**新内容**
|
|
19
|
+
// 投影成条目,随 `/sync` 的 `thread` 回来,面板用官方 markdown 渲染器显示
|
|
20
|
+
// (产物由 scripts/build-webview.mjs 打成 webview/thread.js + thread.css);
|
|
21
|
+
// - 授权(写工作区外文件 / 执行命令):host 的 `approvals` 字段回到面板,用户在面板上
|
|
22
|
+
// 「允许一次 / 拒绝」,扩展调 `/approve` 提交(桥里唯一的非只读路由)。
|
|
18
23
|
//
|
|
19
24
|
// 只依赖 `vscode` 与 Node 内置模块;纯逻辑在 lib/ 下且不 require('vscode'),便于单测。
|
|
20
25
|
|
|
@@ -22,6 +27,7 @@
|
|
|
22
27
|
|
|
23
28
|
const vscode = require('vscode');
|
|
24
29
|
const crypto = require('crypto');
|
|
30
|
+
const fs = require('fs');
|
|
25
31
|
const path = require('path');
|
|
26
32
|
|
|
27
33
|
const {
|
|
@@ -32,14 +38,12 @@ const {
|
|
|
32
38
|
const { createProjector } = require('./lib/context-model.js');
|
|
33
39
|
const { createDiffCache, describeChange } = require('./lib/diff-model.js');
|
|
34
40
|
const {
|
|
35
|
-
|
|
41
|
+
applySync,
|
|
36
42
|
createPanelState,
|
|
37
|
-
|
|
38
|
-
describeContext,
|
|
43
|
+
failPanel,
|
|
39
44
|
panelPayload,
|
|
40
|
-
|
|
45
|
+
pendingQuestion,
|
|
41
46
|
renderPanelHtml,
|
|
42
|
-
statusText,
|
|
43
47
|
} = require('./lib/ask-panel.js');
|
|
44
48
|
|
|
45
49
|
/** 状态栏项(仅在桥连通时显示)。 */
|
|
@@ -52,8 +56,10 @@ let client = null;
|
|
|
52
56
|
let pollTimer = null;
|
|
53
57
|
/** 上次成功轮询的时间(状态栏 tooltip 用)。 */
|
|
54
58
|
let lastPollAt = 0;
|
|
55
|
-
/** 「问 DSH」面板:null = 没开。`{panel, state}`(状态模型见 lib/ask-panel.js)。 */
|
|
59
|
+
/** 「问 DSH」面板:null = 没开。`{panel, state, mode}`(状态模型见 lib/ask-panel.js)。 */
|
|
56
60
|
let askPanel = null;
|
|
61
|
+
/** 扩展根目录(activate 时记下;面板要按它取 webview 产物 URI)。 */
|
|
62
|
+
let extensionRoot = null;
|
|
57
63
|
/** 是否已经确认过 host 端点可达(避免把"IDE 刚起、扩展先加载"误判为断线)。 */
|
|
58
64
|
let connected = false;
|
|
59
65
|
/** 诊断集合缓存:host 请求时现算,这里只做"有没有变化"的计数上报。 */
|
|
@@ -357,6 +363,10 @@ async function pollOnce() {
|
|
|
357
363
|
}),
|
|
358
364
|
diagnostics,
|
|
359
365
|
at: Date.now(),
|
|
366
|
+
// 面板声明它要看哪个会话:host 据此对齐 `sessionController.follow` 订阅,
|
|
367
|
+
// 并用"有没有人在看"决定授权请求是先问面板还是直接交给 DSH 界面
|
|
368
|
+
// (见 host 侧 lib/bridge-thread.mjs / lib/bridge-approval.mjs)。
|
|
369
|
+
watch: askPanel !== null && askPanel.state.sessionId !== null ? [askPanel.state.sessionId] : [],
|
|
360
370
|
};
|
|
361
371
|
const result = await client.sync(payload);
|
|
362
372
|
if (result.ok !== true) {
|
|
@@ -378,20 +388,9 @@ async function pollOnce() {
|
|
|
378
388
|
await handleEvent(event);
|
|
379
389
|
}
|
|
380
390
|
if ((result.events ?? []).length > 0) client.persist();
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
if (askPanel !== null)
|
|
384
|
-
if (result.answers === undefined) {
|
|
385
|
-
// 旧版 host(≤0.3.18)没有这个字段:此时面板会永远停在"正在回答…",必须说出原因。
|
|
386
|
-
askPanel.stalePolls = (askPanel.stalePolls ?? 0) + 1;
|
|
387
|
-
if (askPanel.stalePolls === 15 && askPanel.state.sessionId !== null && askPanel.state.status === 'thinking') {
|
|
388
|
-
failAskPanel('宿主没有回复同步能力(插件版本过旧?):请重启 dsh web 让 host 侧升级到 0.3.19 以上');
|
|
389
|
-
}
|
|
390
|
-
} else {
|
|
391
|
-
askPanel.stalePolls = 0;
|
|
392
|
-
if (applyAnswers(askPanel.state, result.answers, askPanel.state.sessionId)) refreshAskPanel();
|
|
393
|
-
}
|
|
394
|
-
}
|
|
391
|
+
// 对话流 + 授权待决:host 每趟把被观看会话的**新内容**与待决授权一起回来
|
|
392
|
+
// (见 host 侧 lib/bridge-thread.mjs / lib/bridge-approval.mjs),面板有变化才重画。
|
|
393
|
+
if (askPanel !== null && applySync(askPanel.state, result)) refreshAskPanel();
|
|
395
394
|
}
|
|
396
395
|
|
|
397
396
|
function startPolling(context) {
|
|
@@ -419,19 +418,25 @@ function updateStatusBar() {
|
|
|
419
418
|
|
|
420
419
|
// ---------------------------------------------------------------- 命令 / 提问面板
|
|
421
420
|
|
|
422
|
-
/**
|
|
423
|
-
|
|
421
|
+
/**
|
|
422
|
+
* 当前编辑器上下文(发送时现取一次:用户可以在面板开着的同时换选区)。
|
|
423
|
+
*
|
|
424
|
+
* @param {'selection'|'file'} mode 提问意图:
|
|
425
|
+
* - `selection`(「针对选中内容提问」):有选区就带上**行区间 + 选区正文**;
|
|
426
|
+
* 没有选区时退化成**纯文件**(不带行号 —— 光标停在哪一行跟问题无关,行号只会误导 agent);
|
|
427
|
+
* - `file`(「针对当前文件提问」):**永远不带行号、不带选区**,只给文件路径。
|
|
428
|
+
*/
|
|
429
|
+
function captureAskContext(mode) {
|
|
424
430
|
const editor = vscode.window.activeTextEditor;
|
|
425
431
|
if (editor === undefined || editor === null) return null;
|
|
426
432
|
const doc = editor.document;
|
|
427
433
|
const selection = editor.selection;
|
|
428
|
-
const hasSelection =
|
|
434
|
+
const hasSelection = mode === 'selection'
|
|
435
|
+
&& selection !== undefined && selection !== null && selection.isEmpty === false;
|
|
429
436
|
const selectedText = hasSelection ? doc.getText(selection) : '';
|
|
430
437
|
return {
|
|
431
438
|
file: doc.uri.scheme === 'file' ? doc.uri.fsPath : null,
|
|
432
|
-
lineStart: hasSelection
|
|
433
|
-
? selection.start.line + 1
|
|
434
|
-
: (doc.isDirty ? null : selection.active.line + 1),
|
|
439
|
+
lineStart: hasSelection ? selection.start.line + 1 : null,
|
|
435
440
|
lineEnd: hasSelection ? selection.end.line + 1 : null,
|
|
436
441
|
selection: selectedText === '' ? null : selectedText,
|
|
437
442
|
languageId: doc.languageId ?? null,
|
|
@@ -441,32 +446,23 @@ function captureAskContext() {
|
|
|
441
446
|
/** 把状态推给面板(增量更新:输入框与滚动位置都不受影响)。 */
|
|
442
447
|
function refreshAskPanel() {
|
|
443
448
|
if (askPanel === null) return;
|
|
444
|
-
|
|
445
|
-
payload.contextText = askPanel.state.context === null
|
|
446
|
-
? '来自编辑器'
|
|
447
|
-
: `来自编辑器:${describeContext(askPanel.state.context)}`;
|
|
448
|
-
payload.statusText = askPanel.state.status === 'error'
|
|
449
|
-
? (askPanel.state.error ?? '出错了')
|
|
450
|
-
: statusText(askPanel.state);
|
|
451
|
-
void askPanel.panel.webview.postMessage(payload);
|
|
449
|
+
void askPanel.panel.webview.postMessage(panelPayload(askPanel.state));
|
|
452
450
|
}
|
|
453
451
|
|
|
454
|
-
/** 面板进入错误态:状态行 +
|
|
452
|
+
/** 面板进入错误态:状态行 + 最新一条本地提问都标上原因(绝不静默)。 */
|
|
455
453
|
function failAskPanel(message) {
|
|
456
454
|
if (askPanel === null) return;
|
|
457
|
-
askPanel.state
|
|
458
|
-
askPanel.state.error = message;
|
|
459
|
-
const turn = currentTurn(askPanel.state);
|
|
460
|
-
if (turn !== null) turn.error = message;
|
|
455
|
+
failPanel(askPanel.state, message);
|
|
461
456
|
log(message);
|
|
462
457
|
refreshAskPanel();
|
|
463
458
|
}
|
|
464
459
|
|
|
465
|
-
/** 面板收到一条提问:投递到 DSH
|
|
460
|
+
/** 面板收到一条提问:投递到 DSH,**本地先乐观显示**,回答由 host 的对话流回填。 */
|
|
466
461
|
async function sendAskFromPanel(text) {
|
|
467
462
|
if (askPanel === null || client === null) return;
|
|
468
|
-
|
|
469
|
-
const
|
|
463
|
+
// 用面板自己的意图(selection / file)取上下文:文件级提问永远不带行号。
|
|
464
|
+
const context = captureAskContext(askPanel.mode) ?? askPanel.state.context;
|
|
465
|
+
pendingQuestion(askPanel.state, text, context);
|
|
470
466
|
refreshAskPanel();
|
|
471
467
|
try {
|
|
472
468
|
const result = await client.ask({
|
|
@@ -482,39 +478,82 @@ async function sendAskFromPanel(text) {
|
|
|
482
478
|
askPanel.state.status = 'thinking';
|
|
483
479
|
log(`已投递编辑器消息(session=${result.sessionId ?? '?'})`);
|
|
484
480
|
} else {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
turn.error = askPanel.state.error;
|
|
481
|
+
failAskPanel(`未投递:${result.error ?? '未知原因'}`);
|
|
482
|
+
return;
|
|
488
483
|
}
|
|
489
484
|
} catch (error) {
|
|
490
485
|
const status = error && error.status;
|
|
491
|
-
|
|
486
|
+
failAskPanel(status === 409
|
|
492
487
|
? 'DSH 里没有可投递的会话:请先在 DSH 里打开或新建一个会话。'
|
|
493
488
|
: (status === 401 || status === 503
|
|
494
489
|
? '编辑器桥尚未就绪,请稍后重试。'
|
|
495
|
-
: `投递失败:${error && error.message ? error.message : error}`);
|
|
496
|
-
|
|
497
|
-
askPanel.state.status = 'error';
|
|
498
|
-
askPanel.state.error = message;
|
|
499
|
-
turn.error = message;
|
|
490
|
+
: `投递失败:${error && error.message ? error.message : error}`));
|
|
491
|
+
return;
|
|
500
492
|
}
|
|
501
493
|
refreshAskPanel();
|
|
502
494
|
}
|
|
503
495
|
|
|
504
|
-
/**
|
|
505
|
-
|
|
506
|
-
|
|
496
|
+
/**
|
|
497
|
+
* 面板对一条授权请求的决策 → `POST /approve`。
|
|
498
|
+
*
|
|
499
|
+
* 只接受白名单的两个值(host 侧同样只认这两个);失败必须说出来:面板里点不动的时候,
|
|
500
|
+
* 用户唯一的线索就是这行状态/日志。
|
|
501
|
+
*/
|
|
502
|
+
async function decideApproval(id, outcome) {
|
|
503
|
+
if (askPanel === null || client === null) return;
|
|
504
|
+
if (outcome !== 'allowed-once' && outcome !== 'rejected') return;
|
|
505
|
+
try {
|
|
506
|
+
await client.approve(id, outcome);
|
|
507
|
+
log(`面板授权决策已提交:${id} → ${outcome}`);
|
|
508
|
+
} catch (error) {
|
|
509
|
+
const status = error && error.status;
|
|
510
|
+
const message = status === 409
|
|
511
|
+
? '这条授权请求已经过期或已被处理(DSH 界面里会弹同一张卡片)。'
|
|
512
|
+
: (status === 401 || status === 503
|
|
513
|
+
? '编辑器桥尚未就绪,授权没有提交。'
|
|
514
|
+
: `授权提交失败:${error && error.message ? error.message : error}`);
|
|
515
|
+
// 卡片已经没意义了:本地撤掉,避免用户反复点。
|
|
516
|
+
askPanel.state.approvals = askPanel.state.approvals.filter((item) => item.id !== id);
|
|
517
|
+
failAskPanel(message);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
refreshAskPanel();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* 打开(或聚焦)「问 DSH」面板。
|
|
525
|
+
* @param {'selection'|'file'} mode 提问意图(决定发送时带不带行号/选区),见 captureAskContext。
|
|
526
|
+
*/
|
|
527
|
+
function openAskPanel(mode = 'selection') {
|
|
528
|
+
const contextInfo = captureAskContext(mode);
|
|
507
529
|
if (askPanel === null) {
|
|
530
|
+
// webview 产物是构建出来的(scripts/build-webview.mjs)。缺了就别开一个空白面板。
|
|
531
|
+
const webviewDir = vscode.Uri.joinPath(extensionRoot, 'webview');
|
|
532
|
+
const bundle = vscode.Uri.joinPath(webviewDir, 'thread.js');
|
|
533
|
+
const style = vscode.Uri.joinPath(webviewDir, 'thread.css');
|
|
534
|
+
if (!fs.existsSync(bundle.fsPath) || !fs.existsSync(style.fsPath)) {
|
|
535
|
+
const message = `面板产物缺失(${bundle.fsPath}):请重新安装插件,或在源码里跑 pnpm run build:webview`;
|
|
536
|
+
log(message);
|
|
537
|
+
void vscode.window.showErrorMessage(message);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
508
540
|
const panel = vscode.window.createWebviewPanel(
|
|
509
541
|
'dshAsk',
|
|
510
542
|
'DSH 提问',
|
|
511
543
|
{ viewColumn: vscode.ViewColumn.Beside, preserveFocus: false },
|
|
512
|
-
{
|
|
544
|
+
{
|
|
545
|
+
enableScripts: true,
|
|
546
|
+
retainContextWhenHidden: true,
|
|
547
|
+
// 只允许加载扩展自己的 webview 目录(产物 + 字体),不给别的本地文件开口子。
|
|
548
|
+
localResourceRoots: [webviewDir],
|
|
549
|
+
},
|
|
513
550
|
);
|
|
514
|
-
askPanel = { panel, state: createPanelState() };
|
|
551
|
+
askPanel = { panel, state: createPanelState(), mode };
|
|
515
552
|
panel.webview.html = renderPanelHtml({
|
|
516
553
|
cspSource: panel.webview.cspSource,
|
|
517
554
|
nonce: crypto.randomBytes(16).toString('base64url'),
|
|
555
|
+
scriptUri: panel.webview.asWebviewUri(bundle).toString(),
|
|
556
|
+
styleUri: panel.webview.asWebviewUri(style).toString(),
|
|
518
557
|
});
|
|
519
558
|
panel.webview.onDidReceiveMessage((message) => {
|
|
520
559
|
if (message === null || typeof message !== 'object') return;
|
|
@@ -525,6 +564,10 @@ function openAskPanel(context) {
|
|
|
525
564
|
if (message.type === 'ask' && typeof message.text === 'string') {
|
|
526
565
|
const text = message.text.trim();
|
|
527
566
|
if (text !== '') void sendAskFromPanel(text);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (message.type === 'approve' && typeof message.id === 'string') {
|
|
570
|
+
void decideApproval(message.id, message.outcome);
|
|
528
571
|
}
|
|
529
572
|
});
|
|
530
573
|
panel.onDidDispose(() => {
|
|
@@ -532,22 +575,32 @@ function openAskPanel(context) {
|
|
|
532
575
|
});
|
|
533
576
|
}
|
|
534
577
|
askPanel.state.context = contextInfo;
|
|
578
|
+
askPanel.mode = mode;
|
|
535
579
|
askPanel.panel.reveal(vscode.ViewColumn.Beside, false);
|
|
536
580
|
refreshAskPanel();
|
|
537
581
|
}
|
|
538
582
|
|
|
539
583
|
/** 选中内容 → DSH(编辑器→DSH 的主入口):打开面板并带上当前上下文。 */
|
|
540
584
|
async function askAboutSelection() {
|
|
585
|
+
await openAskPanelFor('selection');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/** 整个文件 → DSH(不带选中、**不带行号**)。 */
|
|
589
|
+
async function askAboutFile() {
|
|
590
|
+
await openAskPanelFor('file');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/** 两个命令的公共前置检查(桥可用 + 有活动编辑器)。 */
|
|
594
|
+
async function openAskPanelFor(mode) {
|
|
541
595
|
if (client === null || client.isDormant()) {
|
|
542
596
|
vscode.window.showInformationMessage('编辑器桥未启用:请在 DSH 里打开 Code Server 标签后重试。');
|
|
543
597
|
return;
|
|
544
598
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
await askAboutSelection();
|
|
599
|
+
if (captureAskContext(mode) === null) {
|
|
600
|
+
vscode.window.showInformationMessage('没有活动的编辑器:请先打开一个文件。');
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
openAskPanel(mode);
|
|
551
604
|
}
|
|
552
605
|
|
|
553
606
|
function showBridgeLog() {
|
|
@@ -560,6 +613,9 @@ function activate(context) {
|
|
|
560
613
|
output = vscode.window.createOutputChannel('DSH Editor Bridge');
|
|
561
614
|
context.subscriptions.push(output);
|
|
562
615
|
|
|
616
|
+
// 面板 webview 的产物按扩展根目录取 URI(0.2.3 起正文走官方渲染器,产物是打包出来的)。
|
|
617
|
+
extensionRoot = context.extensionUri;
|
|
618
|
+
|
|
563
619
|
statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 90);
|
|
564
620
|
context.subscriptions.push(statusBar);
|
|
565
621
|
|