@hienlh/ppm 0.9.0-beta.8 → 0.9.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 +238 -0
- package/bun.lock +17 -0
- package/dist/web/assets/api-settings-BUvk6Saw.js +1 -0
- package/dist/web/assets/arrow-up-BYhx9ckd.js +1 -0
- package/dist/web/assets/browser-tab-CrkhFCaw.js +1 -0
- package/dist/web/assets/chat-tab-C6jpiwh7.js +8 -0
- package/dist/web/assets/chevron-right-5HgK6l7K.js +1 -0
- package/dist/web/assets/code-editor-CBIPzlP2.js +2 -0
- package/dist/web/assets/columns-2-cEVJHYd7.js +1 -0
- package/dist/web/assets/createLucideIcon-PuMiQgHl.js +1 -0
- package/dist/web/assets/{csv-preview-DLqYtXxt.js → csv-preview-ncSOnJSC.js} +2 -2
- package/dist/web/assets/database-viewer-BqOJR_zi.js +1 -0
- package/dist/web/assets/diff-viewer-CcLyp4eY.js +4 -0
- package/dist/web/assets/{dist-CALwEtco.js → dist-DIV6WgAG.js} +1 -1
- package/dist/web/assets/{dist-DGDPTxs1.js → dist-ovWkrgO-.js} +1 -1
- package/dist/web/assets/extension-webview-NiZ7Ybvv.js +3 -0
- package/dist/web/assets/git-graph-CoTvMrIo.js +1 -0
- package/dist/web/assets/index-C8byznLO.js +37 -0
- package/dist/web/assets/index-KwC2YrG4.css +2 -0
- package/dist/web/assets/jsx-runtime-kMwlnEGE.js +1 -0
- package/dist/web/assets/keybindings-store-DPYzBe_M.js +1 -0
- package/dist/web/assets/{markdown-renderer-DklUd_Gv.js → markdown-renderer-DPLdR9xc.js} +4 -4
- package/dist/web/assets/postgres-viewer-BeiK4lCa.js +1 -0
- package/dist/web/assets/settings-tab-D3AvU4lu.js +1 -0
- package/dist/web/assets/sqlite-viewer-nA2sD4Yv.js +1 -0
- package/dist/web/assets/tab-store-BOgTrqRr.js +1 -0
- package/dist/web/assets/table-DFevCOMd.js +1 -0
- package/dist/web/assets/tag-CXMT0QB6.js +1 -0
- package/dist/web/assets/{terminal-tab-CqRuiIFn.js → terminal-tab-BBi0pEji.js} +2 -2
- package/dist/web/assets/{use-monaco-theme-Dcz3aLAE.js → use-monaco-theme-B5pG2d1w.js} +1 -1
- package/dist/web/index.html +8 -8
- package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
- package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
- package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
- package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
- package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
- package/dist/web/sw.js +1 -1
- package/docs/code-standards.md +128 -1
- package/docs/codebase-summary.md +79 -12
- package/docs/extension-development-guide.md +532 -0
- package/docs/project-changelog.md +51 -1
- package/docs/project-roadmap.md +9 -3
- package/docs/streaming-input-guide.md +267 -0
- package/docs/system-architecture.md +432 -3
- package/package.json +6 -3
- package/packages/ext-database/package.json +41 -0
- package/packages/ext-database/src/connection-tree.ts +142 -0
- package/packages/ext-database/src/extension.ts +346 -0
- package/packages/ext-database/src/query-panel.ts +120 -0
- package/packages/ext-database/src/table-viewer-panel.ts +410 -0
- package/packages/ext-database/tsconfig.json +8 -0
- package/packages/vscode-compat/package.json +16 -0
- package/packages/vscode-compat/src/commands.ts +39 -0
- package/packages/vscode-compat/src/context.ts +65 -0
- package/packages/vscode-compat/src/disposable.ts +21 -0
- package/packages/vscode-compat/src/env.ts +20 -0
- package/packages/vscode-compat/src/event-emitter.ts +28 -0
- package/packages/vscode-compat/src/index.ts +93 -0
- package/packages/vscode-compat/src/not-supported.ts +15 -0
- package/packages/vscode-compat/src/types.ts +167 -0
- package/packages/vscode-compat/src/uri.ts +65 -0
- package/packages/vscode-compat/src/window.ts +229 -0
- package/packages/vscode-compat/src/workspace.ts +76 -0
- package/packages/vscode-compat/tsconfig.json +10 -0
- package/snapshot-state.md +1526 -0
- package/src/cli/commands/autostart.ts +1 -1
- package/src/cli/commands/ext-cmd.ts +121 -0
- package/src/cli/commands/restart.ts +9 -1
- package/src/cli/commands/status.ts +19 -0
- package/src/index.ts +5 -3
- package/src/providers/claude-agent-sdk.ts +221 -17
- package/src/providers/cli-provider-base.ts +6 -0
- package/src/server/index.ts +55 -155
- package/src/server/routes/chat.ts +81 -11
- package/src/server/routes/extensions.ts +81 -0
- package/src/server/routes/project-scoped.ts +2 -0
- package/src/server/routes/settings.ts +27 -0
- package/src/server/routes/workspace.ts +35 -0
- package/src/server/ws/chat.ts +9 -3
- package/src/server/ws/extensions.ts +175 -0
- package/src/services/account-selector.service.ts +14 -5
- package/src/services/account.service.ts +20 -15
- package/src/services/claude-usage.service.ts +29 -24
- package/src/services/cloud-ws.service.ts +228 -0
- package/src/services/cloud.service.ts +11 -6
- package/src/services/contribution-registry.ts +110 -0
- package/src/services/db.service.ts +181 -4
- package/src/services/extension-host-worker.ts +160 -0
- package/src/services/extension-installer.ts +112 -0
- package/src/services/extension-manifest.ts +65 -0
- package/src/services/extension-rpc-handlers.ts +235 -0
- package/src/services/extension-rpc.ts +105 -0
- package/src/services/extension.service.ts +228 -0
- package/src/services/mcp-config.service.ts +15 -6
- package/src/services/supervisor.ts +271 -25
- package/src/types/api.ts +1 -0
- package/src/types/chat.ts +4 -0
- package/src/types/extension-messages.ts +64 -0
- package/src/types/extension.ts +131 -0
- package/src/web/app.tsx +69 -48
- package/src/web/components/chat/account-rotation-settings.tsx +163 -0
- package/src/web/components/chat/chat-history-bar.tsx +106 -10
- package/src/web/components/chat/chat-tab.tsx +15 -10
- package/src/web/components/chat/chat-welcome.tsx +148 -0
- package/src/web/components/chat/message-list.tsx +19 -6
- package/src/web/components/chat/session-picker.tsx +80 -32
- package/src/web/components/chat/usage-badge.tsx +68 -8
- package/src/web/components/editor/editor-breadcrumb.tsx +20 -29
- package/src/web/components/extensions/extension-inputbox.tsx +92 -0
- package/src/web/components/extensions/extension-quickpick.tsx +194 -0
- package/src/web/components/extensions/extension-tree-view.tsx +240 -0
- package/src/web/components/extensions/extension-webview.tsx +83 -0
- package/src/web/components/layout/command-palette.tsx +22 -2
- package/src/web/components/layout/editor-panel.tsx +163 -18
- package/src/web/components/layout/mobile-nav.tsx +2 -1
- package/src/web/components/layout/sidebar.tsx +21 -3
- package/src/web/components/layout/status-bar.tsx +64 -0
- package/src/web/components/layout/tab-bar.tsx +2 -0
- package/src/web/components/layout/tab-content.tsx +5 -0
- package/src/web/components/layout/upgrade-banner.tsx +15 -5
- package/src/web/components/settings/change-password-section.tsx +128 -0
- package/src/web/components/settings/extension-manager-section.tsx +214 -0
- package/src/web/components/settings/settings-tab.tsx +9 -2
- package/src/web/components/shared/connection-lost-overlay.tsx +89 -0
- package/src/web/hooks/use-chat.ts +28 -0
- package/src/web/hooks/use-extension-ws.ts +181 -0
- package/src/web/hooks/use-global-keybindings.ts +18 -2
- package/src/web/hooks/use-server-reload.ts +9 -0
- package/src/web/hooks/use-url-sync.ts +173 -21
- package/src/web/stores/connection-store.ts +39 -0
- package/src/web/stores/extension-store.ts +204 -0
- package/src/web/stores/panel-store.ts +63 -9
- package/src/web/stores/panel-utils.ts +145 -3
- package/src/web/stores/settings-store.ts +7 -2
- package/src/web/stores/tab-store.ts +2 -1
- package/test-session-ops.mjs +444 -0
- package/test-tokens.mjs +212 -0
- package/tsconfig.json +3 -1
- package/dist/web/assets/api-settings-D21InCnR.js +0 -1
- package/dist/web/assets/arrow-up--LjUXLEt.js +0 -1
- package/dist/web/assets/browser-tab-BEe89aSD.js +0 -1
- package/dist/web/assets/chat-tab-9lqvWozA.js +0 -7
- package/dist/web/assets/chevron-right-CHnjJt4E.js +0 -1
- package/dist/web/assets/code-editor-COAIZx-B.js +0 -2
- package/dist/web/assets/columns-2-DbesTfa7.js +0 -1
- package/dist/web/assets/database-viewer-aRR9n_Ui.js +0 -1
- package/dist/web/assets/diff-viewer-C4KMvpHr.js +0 -4
- package/dist/web/assets/dist-CVTST7Gc.js +0 -1
- package/dist/web/assets/git-graph-CfJjl4E3.js +0 -1
- package/dist/web/assets/index-Db8uky1a.css +0 -2
- package/dist/web/assets/index-DxZuwBDe.js +0 -37
- package/dist/web/assets/jsx-runtime-BRW_vwa9.js +0 -1
- package/dist/web/assets/keybindings-store-_uWVCZMv.js +0 -1
- package/dist/web/assets/postgres-viewer-DEAvAyaX.js +0 -1
- package/dist/web/assets/settings-tab-BQedc-No.js +0 -1
- package/dist/web/assets/sqlite-viewer-BPA5idzT.js +0 -1
- package/dist/web/assets/tab-store-DhK6EpBT.js +0 -1
- package/dist/web/assets/table-CQVQM2SB.js +0 -1
- package/dist/web/assets/tag-Q2dZiSPX.js +0 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ppm/vscode-compat — VSCode-compatible API shim for PPM extensions.
|
|
3
|
+
*
|
|
4
|
+
* Extension authors replace `import * as vscode from 'vscode'`
|
|
5
|
+
* with `import * as vscode from '@ppm/vscode-compat'`.
|
|
6
|
+
*
|
|
7
|
+
* All API calls serialize over RPC to the main PPM process.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Re-export types and classes
|
|
11
|
+
export { Disposable } from "./disposable.ts";
|
|
12
|
+
export { EventEmitter, type Event } from "./event-emitter.ts";
|
|
13
|
+
export { Uri } from "./uri.ts";
|
|
14
|
+
export {
|
|
15
|
+
TreeItemCollapsibleState, ViewColumn, StatusBarAlignment,
|
|
16
|
+
ConfigurationTarget, DiagnosticSeverity, ThemeIcon,
|
|
17
|
+
type TreeItem, type TreeDataProvider, type WebviewPanel,
|
|
18
|
+
type Webview, type WebviewOptions, type StatusBarItem,
|
|
19
|
+
type QuickPickItem, type QuickPickOptions, type InputBoxOptions,
|
|
20
|
+
type OutputChannel, type WorkspaceFolder, type WorkspaceConfiguration,
|
|
21
|
+
type RpcClient,
|
|
22
|
+
} from "./types.ts";
|
|
23
|
+
export { type ExtensionContext, Memento } from "./context.ts";
|
|
24
|
+
|
|
25
|
+
// Service imports
|
|
26
|
+
import { CommandService } from "./commands.ts";
|
|
27
|
+
import { WindowService } from "./window.ts";
|
|
28
|
+
import { WorkspaceService } from "./workspace.ts";
|
|
29
|
+
import { createExtensionContext, type ExtensionContext } from "./context.ts";
|
|
30
|
+
import { createEnvNamespace } from "./env.ts";
|
|
31
|
+
import { createNotSupported } from "./not-supported.ts";
|
|
32
|
+
import { Uri } from "./uri.ts";
|
|
33
|
+
import { Disposable } from "./disposable.ts";
|
|
34
|
+
import { EventEmitter } from "./event-emitter.ts";
|
|
35
|
+
import {
|
|
36
|
+
TreeItemCollapsibleState, ViewColumn, StatusBarAlignment,
|
|
37
|
+
ConfigurationTarget, DiagnosticSeverity, ThemeIcon,
|
|
38
|
+
} from "./types.ts";
|
|
39
|
+
import type { RpcClient } from "./types.ts";
|
|
40
|
+
|
|
41
|
+
export interface CreateVscodeCompatOptions {
|
|
42
|
+
extensionId: string;
|
|
43
|
+
extensionPath: string;
|
|
44
|
+
storagePath: string;
|
|
45
|
+
rpc: RpcClient;
|
|
46
|
+
appName?: string;
|
|
47
|
+
machineId?: string;
|
|
48
|
+
storedState?: { global?: Record<string, string | null>; workspace?: Record<string, string | null> };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Create a scoped vscode-compatible API instance for an extension */
|
|
52
|
+
export function createVscodeCompat(options: CreateVscodeCompatOptions) {
|
|
53
|
+
const { extensionId, extensionPath, storagePath, rpc, storedState } = options;
|
|
54
|
+
|
|
55
|
+
const commands = new CommandService(rpc, extensionId);
|
|
56
|
+
const window = new WindowService(rpc, extensionId);
|
|
57
|
+
const workspace = new WorkspaceService(rpc, extensionId);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
// Active API namespaces
|
|
61
|
+
commands,
|
|
62
|
+
window,
|
|
63
|
+
workspace,
|
|
64
|
+
env: createEnvNamespace(options.appName ?? "PPM", options.machineId ?? "ppm-local"),
|
|
65
|
+
|
|
66
|
+
// Classes & utilities
|
|
67
|
+
Uri,
|
|
68
|
+
Disposable,
|
|
69
|
+
EventEmitter,
|
|
70
|
+
|
|
71
|
+
// Enums
|
|
72
|
+
TreeItemCollapsibleState,
|
|
73
|
+
ViewColumn,
|
|
74
|
+
StatusBarAlignment,
|
|
75
|
+
ConfigurationTarget,
|
|
76
|
+
DiagnosticSeverity,
|
|
77
|
+
ThemeIcon,
|
|
78
|
+
|
|
79
|
+
// Unsupported namespaces (throw descriptive errors)
|
|
80
|
+
languages: createNotSupported("languages"),
|
|
81
|
+
debug: createNotSupported("debug"),
|
|
82
|
+
tasks: createNotSupported("tasks"),
|
|
83
|
+
scm: createNotSupported("scm"),
|
|
84
|
+
notebooks: createNotSupported("notebooks"),
|
|
85
|
+
authentication: createNotSupported("authentication"),
|
|
86
|
+
tests: createNotSupported("tests"),
|
|
87
|
+
|
|
88
|
+
// Helper: create ExtensionContext for this extension
|
|
89
|
+
_createContext(): ExtensionContext {
|
|
90
|
+
return createExtensionContext(rpc, extensionId, extensionPath, storagePath, storedState);
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Create a Proxy that throws on any property access for unsupported VSCode APIs */
|
|
2
|
+
export function createNotSupported(namespace: string): unknown {
|
|
3
|
+
return new Proxy({}, {
|
|
4
|
+
get(_target, prop) {
|
|
5
|
+
if (prop === Symbol.toPrimitive || prop === "toString" || prop === "valueOf") {
|
|
6
|
+
return () => `[vscode.${namespace} — not supported in PPM]`;
|
|
7
|
+
}
|
|
8
|
+
if (typeof prop === "symbol") return undefined;
|
|
9
|
+
throw new Error(
|
|
10
|
+
`vscode.${namespace}.${prop} is not supported in PPM. ` +
|
|
11
|
+
`See https://github.com/hienlh/ppm/docs/extension-migration.md for alternatives.`,
|
|
12
|
+
);
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { Uri } from "./uri.ts";
|
|
2
|
+
import type { Event } from "./event-emitter.ts";
|
|
3
|
+
|
|
4
|
+
// --- Enums ---
|
|
5
|
+
|
|
6
|
+
export enum TreeItemCollapsibleState {
|
|
7
|
+
None = 0,
|
|
8
|
+
Collapsed = 1,
|
|
9
|
+
Expanded = 2,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export enum ViewColumn {
|
|
13
|
+
Active = -1,
|
|
14
|
+
Beside = -2,
|
|
15
|
+
One = 1,
|
|
16
|
+
Two = 2,
|
|
17
|
+
Three = 3,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export enum StatusBarAlignment {
|
|
21
|
+
Left = 1,
|
|
22
|
+
Right = 2,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export enum ConfigurationTarget {
|
|
26
|
+
Global = 1,
|
|
27
|
+
Workspace = 2,
|
|
28
|
+
WorkspaceFolder = 3,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export enum DiagnosticSeverity {
|
|
32
|
+
Error = 0,
|
|
33
|
+
Warning = 1,
|
|
34
|
+
Information = 2,
|
|
35
|
+
Hint = 3,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- Tree View ---
|
|
39
|
+
|
|
40
|
+
export interface TreeItem {
|
|
41
|
+
label?: string;
|
|
42
|
+
id?: string;
|
|
43
|
+
iconPath?: string | Uri | { light: string | Uri; dark: string | Uri };
|
|
44
|
+
description?: string;
|
|
45
|
+
tooltip?: string;
|
|
46
|
+
collapsibleState?: TreeItemCollapsibleState;
|
|
47
|
+
command?: { command: string; title: string; arguments?: unknown[] };
|
|
48
|
+
contextValue?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TreeDataProvider<T> {
|
|
52
|
+
getTreeItem(element: T): TreeItem | Promise<TreeItem>;
|
|
53
|
+
getChildren(element?: T): T[] | Promise<T[]>;
|
|
54
|
+
onDidChangeTreeData?: Event<T | undefined | null | void>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// --- Webview ---
|
|
58
|
+
|
|
59
|
+
export interface WebviewOptions {
|
|
60
|
+
enableScripts?: boolean;
|
|
61
|
+
enableForms?: boolean;
|
|
62
|
+
localResourceRoots?: Uri[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Webview {
|
|
66
|
+
html: string;
|
|
67
|
+
options: WebviewOptions;
|
|
68
|
+
onDidReceiveMessage: Event<unknown>;
|
|
69
|
+
postMessage(message: unknown): Promise<boolean>;
|
|
70
|
+
asWebviewUri(localResource: Uri): Uri;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface WebviewPanel {
|
|
74
|
+
viewType: string;
|
|
75
|
+
title: string;
|
|
76
|
+
webview: Webview;
|
|
77
|
+
viewColumn?: ViewColumn;
|
|
78
|
+
active: boolean;
|
|
79
|
+
visible: boolean;
|
|
80
|
+
onDidDispose: Event<void>;
|
|
81
|
+
onDidChangeViewState: Event<{ webviewPanel: WebviewPanel }>;
|
|
82
|
+
reveal(viewColumn?: ViewColumn, preserveFocus?: boolean): void;
|
|
83
|
+
dispose(): void;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- Status Bar ---
|
|
87
|
+
|
|
88
|
+
export interface StatusBarItem {
|
|
89
|
+
alignment: StatusBarAlignment;
|
|
90
|
+
priority?: number;
|
|
91
|
+
text: string;
|
|
92
|
+
tooltip?: string;
|
|
93
|
+
color?: string;
|
|
94
|
+
command?: string;
|
|
95
|
+
show(): void;
|
|
96
|
+
hide(): void;
|
|
97
|
+
dispose(): void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// --- Quick Pick / Input ---
|
|
101
|
+
|
|
102
|
+
export interface QuickPickItem {
|
|
103
|
+
label: string;
|
|
104
|
+
description?: string;
|
|
105
|
+
detail?: string;
|
|
106
|
+
picked?: boolean;
|
|
107
|
+
alwaysShow?: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface QuickPickOptions {
|
|
111
|
+
title?: string;
|
|
112
|
+
placeHolder?: string;
|
|
113
|
+
canPickMany?: boolean;
|
|
114
|
+
matchOnDescription?: boolean;
|
|
115
|
+
matchOnDetail?: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface InputBoxOptions {
|
|
119
|
+
title?: string;
|
|
120
|
+
value?: string;
|
|
121
|
+
prompt?: string;
|
|
122
|
+
placeHolder?: string;
|
|
123
|
+
password?: boolean;
|
|
124
|
+
validateInput?(value: string): string | undefined | null | Promise<string | undefined | null>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- Output Channel ---
|
|
128
|
+
|
|
129
|
+
export interface OutputChannel {
|
|
130
|
+
name: string;
|
|
131
|
+
append(value: string): void;
|
|
132
|
+
appendLine(value: string): void;
|
|
133
|
+
clear(): void;
|
|
134
|
+
show(preserveFocus?: boolean): void;
|
|
135
|
+
hide(): void;
|
|
136
|
+
dispose(): void;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --- Workspace ---
|
|
140
|
+
|
|
141
|
+
export interface WorkspaceFolder {
|
|
142
|
+
uri: Uri;
|
|
143
|
+
name: string;
|
|
144
|
+
index: number;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface WorkspaceConfiguration {
|
|
148
|
+
get<T>(section: string, defaultValue?: T): T | undefined;
|
|
149
|
+
has(section: string): boolean;
|
|
150
|
+
update(section: string, value: unknown, target?: ConfigurationTarget): Promise<void>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// --- Theme ---
|
|
154
|
+
|
|
155
|
+
export class ThemeIcon {
|
|
156
|
+
static readonly File = new ThemeIcon("file");
|
|
157
|
+
static readonly Folder = new ThemeIcon("folder");
|
|
158
|
+
readonly id: string;
|
|
159
|
+
constructor(id: string) { this.id = id; }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// --- RPC client interface (injected into Worker) ---
|
|
163
|
+
|
|
164
|
+
export interface RpcClient {
|
|
165
|
+
request<T = unknown>(method: string, ...params: unknown[]): Promise<T>;
|
|
166
|
+
notify(event: string, data: unknown): void;
|
|
167
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** VSCode-compatible Uri implementation */
|
|
2
|
+
export class Uri {
|
|
3
|
+
readonly scheme: string;
|
|
4
|
+
readonly authority: string;
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly query: string;
|
|
7
|
+
readonly fragment: string;
|
|
8
|
+
|
|
9
|
+
private constructor(scheme: string, authority: string, path: string, query: string, fragment: string) {
|
|
10
|
+
this.scheme = scheme;
|
|
11
|
+
this.authority = authority;
|
|
12
|
+
this.path = path;
|
|
13
|
+
this.query = query;
|
|
14
|
+
this.fragment = fragment;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** File system path (decoded, platform-specific) */
|
|
18
|
+
get fsPath(): string {
|
|
19
|
+
return this.path;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static file(path: string): Uri {
|
|
23
|
+
return new Uri("file", "", path, "", "");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static parse(value: string): Uri {
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL(value);
|
|
29
|
+
return new Uri(
|
|
30
|
+
url.protocol.replace(":", ""),
|
|
31
|
+
url.host,
|
|
32
|
+
decodeURIComponent(url.pathname),
|
|
33
|
+
url.search.replace("?", ""),
|
|
34
|
+
url.hash.replace("#", ""),
|
|
35
|
+
);
|
|
36
|
+
} catch {
|
|
37
|
+
// Treat as file path if not a valid URL
|
|
38
|
+
return Uri.file(value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): Uri {
|
|
43
|
+
return new Uri(
|
|
44
|
+
change.scheme ?? this.scheme,
|
|
45
|
+
change.authority ?? this.authority,
|
|
46
|
+
change.path ?? this.path,
|
|
47
|
+
change.query ?? this.query,
|
|
48
|
+
change.fragment ?? this.fragment,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
toString(): string {
|
|
53
|
+
if (this.scheme === "file") return `file://${this.path}`;
|
|
54
|
+
let result = `${this.scheme}://`;
|
|
55
|
+
if (this.authority) result += this.authority;
|
|
56
|
+
result += this.path;
|
|
57
|
+
if (this.query) result += `?${this.query}`;
|
|
58
|
+
if (this.fragment) result += `#${this.fragment}`;
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
toJSON(): { scheme: string; authority: string; path: string; query: string; fragment: string } {
|
|
63
|
+
return { scheme: this.scheme, authority: this.authority, path: this.path, query: this.query, fragment: this.fragment };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { Disposable } from "./disposable.ts";
|
|
2
|
+
import { EventEmitter } from "./event-emitter.ts";
|
|
3
|
+
import type {
|
|
4
|
+
RpcClient, StatusBarItem, StatusBarAlignment, QuickPickItem,
|
|
5
|
+
QuickPickOptions, InputBoxOptions, OutputChannel, ViewColumn,
|
|
6
|
+
} from "./types.ts";
|
|
7
|
+
import { StatusBarAlignment as SBAlign } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
/** VSCode-compatible window namespace — all UI ops go through RPC to main→browser */
|
|
10
|
+
export class WindowService {
|
|
11
|
+
private rpc: RpcClient;
|
|
12
|
+
private extId: string;
|
|
13
|
+
/** @internal Monotonic counter for unique panel/statusbar IDs */
|
|
14
|
+
private _idCounter = 0;
|
|
15
|
+
/** @internal Panel emitters keyed by panelId — for webview message delivery */
|
|
16
|
+
private _panelEmitters = new Map<string, EventEmitter<unknown>>();
|
|
17
|
+
/** @internal Tree providers keyed by viewId — for tree expand/refresh */
|
|
18
|
+
private _treeProviders = new Map<string, { provider: any }>();
|
|
19
|
+
/** @internal Cache original tree elements by ID so getChildren receives the real object */
|
|
20
|
+
private _treeElementCache = new Map<string, Map<string, unknown>>();
|
|
21
|
+
|
|
22
|
+
constructor(rpc: RpcClient, extId: string) {
|
|
23
|
+
this.rpc = rpc;
|
|
24
|
+
this.extId = extId;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** @internal Deliver a message from the WS bridge to a webview panel's onDidReceiveMessage */
|
|
28
|
+
_deliverWebviewMessage(panelId: string, message: unknown): boolean {
|
|
29
|
+
const emitter = this._panelEmitters.get(panelId);
|
|
30
|
+
if (emitter) { emitter.fire(message); return true; }
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** @internal Get tree children for a viewId (used by Worker for tree:expand) */
|
|
35
|
+
async _getTreeChildren(viewId: string, parentId?: string): Promise<unknown[]> {
|
|
36
|
+
const entry = this._treeProviders.get(viewId);
|
|
37
|
+
if (!entry) return [];
|
|
38
|
+
const provider = entry.provider;
|
|
39
|
+
// Resolve parentId → original cached element (or undefined for root)
|
|
40
|
+
let parentElement: unknown = undefined;
|
|
41
|
+
if (parentId) {
|
|
42
|
+
const cache = this._treeElementCache.get(viewId);
|
|
43
|
+
parentElement = cache?.get(parentId) ?? parentId;
|
|
44
|
+
}
|
|
45
|
+
const children = await Promise.resolve(provider.getChildren(parentElement));
|
|
46
|
+
if (!children || !Array.isArray(children)) return [];
|
|
47
|
+
return this._serializeTreeItems(provider, children, viewId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @internal Serialize tree elements into TreeItemMsg-compatible objects */
|
|
51
|
+
private async _serializeTreeItems(provider: any, elements: unknown[], viewId?: string): Promise<unknown[]> {
|
|
52
|
+
const results: unknown[] = [];
|
|
53
|
+
// Ensure element cache exists for this view
|
|
54
|
+
if (viewId && !this._treeElementCache.has(viewId)) {
|
|
55
|
+
this._treeElementCache.set(viewId, new Map());
|
|
56
|
+
}
|
|
57
|
+
const cache = viewId ? this._treeElementCache.get(viewId)! : null;
|
|
58
|
+
|
|
59
|
+
for (const el of elements) {
|
|
60
|
+
const treeItem = provider.getTreeItem ? await Promise.resolve(provider.getTreeItem(el)) : el;
|
|
61
|
+
const id = treeItem.id ?? String(el);
|
|
62
|
+
// Cache the original element so getChildren receives the real object on expand
|
|
63
|
+
if (cache) cache.set(id, el);
|
|
64
|
+
results.push({
|
|
65
|
+
id,
|
|
66
|
+
label: treeItem.label ?? String(el),
|
|
67
|
+
description: treeItem.description,
|
|
68
|
+
tooltip: treeItem.tooltip,
|
|
69
|
+
icon: treeItem.iconPath?.id ?? treeItem.iconPath ?? undefined,
|
|
70
|
+
collapsibleState: treeItem.collapsibleState === 0 ? "none" : treeItem.collapsibleState === 2 ? "expanded" : treeItem.collapsibleState === 1 ? "collapsed" : (treeItem.collapsibleState ?? "none"),
|
|
71
|
+
command: typeof treeItem.command === "string" ? treeItem.command : treeItem.command?.command,
|
|
72
|
+
commandArgs: treeItem.commandArgs ?? (typeof treeItem.command === "object" ? treeItem.command?.arguments : undefined),
|
|
73
|
+
color: treeItem.color,
|
|
74
|
+
badge: treeItem.badge,
|
|
75
|
+
actions: treeItem.actions,
|
|
76
|
+
contextValue: treeItem.contextValue,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return results;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// --- Messages ---
|
|
83
|
+
|
|
84
|
+
async showInformationMessage(message: string, ...items: string[]): Promise<string | undefined> {
|
|
85
|
+
return this.rpc.request<string | undefined>("window:showMessage", "info", message, items);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async showWarningMessage(message: string, ...items: string[]): Promise<string | undefined> {
|
|
89
|
+
return this.rpc.request<string | undefined>("window:showMessage", "warn", message, items);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async showErrorMessage(message: string, ...items: string[]): Promise<string | undefined> {
|
|
93
|
+
return this.rpc.request<string | undefined>("window:showMessage", "error", message, items);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- Quick Pick / Input ---
|
|
97
|
+
|
|
98
|
+
async showQuickPick(
|
|
99
|
+
items: string[] | QuickPickItem[],
|
|
100
|
+
options?: QuickPickOptions,
|
|
101
|
+
): Promise<string | QuickPickItem | undefined> {
|
|
102
|
+
return this.rpc.request("window:showQuickPick", items, options ?? {});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async showInputBox(options?: InputBoxOptions): Promise<string | undefined> {
|
|
106
|
+
// Remove non-serializable validateInput before sending over RPC
|
|
107
|
+
const { validateInput, ...serializableOpts } = options ?? {};
|
|
108
|
+
return this.rpc.request<string | undefined>("window:showInputBox", serializableOpts);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- Status Bar ---
|
|
112
|
+
|
|
113
|
+
createStatusBarItem(alignment?: StatusBarAlignment, priority?: number): StatusBarItem {
|
|
114
|
+
const id = `${this.extId}-sb-${++this._idCounter}`;
|
|
115
|
+
const rpc = this.rpc;
|
|
116
|
+
const extId = this.extId;
|
|
117
|
+
const item: StatusBarItem = {
|
|
118
|
+
alignment: (alignment as StatusBarAlignment) ?? SBAlign.Left,
|
|
119
|
+
priority,
|
|
120
|
+
text: "",
|
|
121
|
+
tooltip: undefined,
|
|
122
|
+
color: undefined,
|
|
123
|
+
command: undefined,
|
|
124
|
+
show() {
|
|
125
|
+
rpc.request("window:statusbar:update", {
|
|
126
|
+
id, text: item.text, tooltip: item.tooltip, command: item.command as string | undefined,
|
|
127
|
+
alignment: item.alignment === SBAlign.Left ? "left" : "right",
|
|
128
|
+
priority: item.priority ?? 0, extensionId: extId,
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
hide() { rpc.request("window:statusbar:remove", id); },
|
|
132
|
+
dispose() { rpc.request("window:statusbar:remove", id); },
|
|
133
|
+
};
|
|
134
|
+
return item;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// --- Output Channel ---
|
|
138
|
+
|
|
139
|
+
createOutputChannel(name: string): OutputChannel {
|
|
140
|
+
const rpc = this.rpc;
|
|
141
|
+
const extId = this.extId;
|
|
142
|
+
let buffer = "";
|
|
143
|
+
return {
|
|
144
|
+
name,
|
|
145
|
+
append(value: string) { buffer += value; },
|
|
146
|
+
appendLine(value: string) {
|
|
147
|
+
buffer += value + "\n";
|
|
148
|
+
rpc.notify("window:output:append", { extId, name, text: buffer });
|
|
149
|
+
buffer = "";
|
|
150
|
+
},
|
|
151
|
+
clear() { rpc.notify("window:output:clear", { extId, name }); },
|
|
152
|
+
show() { rpc.notify("window:output:show", { extId, name }); },
|
|
153
|
+
hide() { rpc.notify("window:output:hide", { extId, name }); },
|
|
154
|
+
dispose() { rpc.notify("window:output:dispose", { extId, name }); },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// --- Tree View ---
|
|
159
|
+
|
|
160
|
+
createTreeView(viewId: string, options: { treeDataProvider: unknown }): Disposable {
|
|
161
|
+
const rpc = this.rpc;
|
|
162
|
+
const provider = options.treeDataProvider as any;
|
|
163
|
+
this._treeProviders.set(viewId, { provider });
|
|
164
|
+
// Initial tree data push — serialize via getTreeItem
|
|
165
|
+
if (provider.getChildren) {
|
|
166
|
+
this._getTreeChildren(viewId).then((items) => {
|
|
167
|
+
rpc.request("window:tree:update", viewId, items);
|
|
168
|
+
}).catch((e) => console.error(`[vscode-compat] tree init error (${viewId}):`, e));
|
|
169
|
+
}
|
|
170
|
+
// Subscribe to onDidChangeTreeData — re-push entire tree on change
|
|
171
|
+
let changeUnsub: (() => void) | undefined;
|
|
172
|
+
if (provider.onDidChangeTreeData) {
|
|
173
|
+
changeUnsub = provider.onDidChangeTreeData(() => {
|
|
174
|
+
this._getTreeChildren(viewId).then((items) => {
|
|
175
|
+
rpc.request("window:tree:update", viewId, items);
|
|
176
|
+
}).catch((e) => console.error(`[vscode-compat] tree refresh error (${viewId}):`, e));
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return new Disposable(() => {
|
|
180
|
+
if (changeUnsub) changeUnsub();
|
|
181
|
+
this._treeProviders.delete(viewId);
|
|
182
|
+
this._treeElementCache.delete(viewId);
|
|
183
|
+
rpc.request("window:tree:refresh", viewId);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// --- Webview Panel ---
|
|
188
|
+
|
|
189
|
+
createWebviewPanel(viewType: string, title: string, showOptions: ViewColumn): unknown {
|
|
190
|
+
const panelId = `${this.extId}-wv-${++this._idCounter}`;
|
|
191
|
+
const rpc = this.rpc;
|
|
192
|
+
const extId = this.extId;
|
|
193
|
+
rpc.request("window:webview:create", panelId, extId, viewType, title);
|
|
194
|
+
|
|
195
|
+
const onDidDispose = new EventEmitter<void>();
|
|
196
|
+
const onDidReceiveMessage = new EventEmitter<unknown>();
|
|
197
|
+
this._panelEmitters.set(panelId, onDidReceiveMessage);
|
|
198
|
+
|
|
199
|
+
let currentHtml = "";
|
|
200
|
+
const webview = {
|
|
201
|
+
get html() { return currentHtml; },
|
|
202
|
+
set html(value: string) {
|
|
203
|
+
currentHtml = value;
|
|
204
|
+
rpc.request("window:webview:html", panelId, value);
|
|
205
|
+
},
|
|
206
|
+
options: {},
|
|
207
|
+
async postMessage(message: unknown): Promise<boolean> {
|
|
208
|
+
await rpc.request("window:webview:postMessage", panelId, message);
|
|
209
|
+
return true;
|
|
210
|
+
},
|
|
211
|
+
onDidReceiveMessage: onDidReceiveMessage.event,
|
|
212
|
+
asWebviewUri: (uri: unknown) => uri,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
viewType, title, webview,
|
|
217
|
+
onDidDispose: onDidDispose.event,
|
|
218
|
+
onDidChangeViewState: new EventEmitter().event,
|
|
219
|
+
reveal() {},
|
|
220
|
+
dispose: () => {
|
|
221
|
+
this._panelEmitters.delete(panelId);
|
|
222
|
+
rpc.request("window:webview:dispose", panelId);
|
|
223
|
+
onDidDispose.fire();
|
|
224
|
+
onDidDispose.dispose();
|
|
225
|
+
onDidReceiveMessage.dispose();
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { EventEmitter } from "./event-emitter.ts";
|
|
2
|
+
import { Uri } from "./uri.ts";
|
|
3
|
+
import type { RpcClient, WorkspaceFolder, WorkspaceConfiguration, ConfigurationTarget } from "./types.ts";
|
|
4
|
+
|
|
5
|
+
/** VSCode-compatible workspace namespace — config, fs, folders via RPC */
|
|
6
|
+
export class WorkspaceService {
|
|
7
|
+
private rpc: RpcClient;
|
|
8
|
+
private extId: string;
|
|
9
|
+
private _onDidChangeConfiguration = new EventEmitter<{ affectsConfiguration(section: string): boolean }>();
|
|
10
|
+
readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event;
|
|
11
|
+
|
|
12
|
+
constructor(rpc: RpcClient, extId: string) {
|
|
13
|
+
this.rpc = rpc;
|
|
14
|
+
this.extId = extId;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getConfiguration(section?: string): WorkspaceConfiguration {
|
|
18
|
+
const rpc = this.rpc;
|
|
19
|
+
// Config reads go through RPC — returns a snapshot object
|
|
20
|
+
return {
|
|
21
|
+
async get<T>(key: string, defaultValue?: T): Promise<T | undefined> {
|
|
22
|
+
const fullKey = section ? `${section}.${key}` : key;
|
|
23
|
+
const val = await rpc.request<T | null>("workspace:config:get", fullKey);
|
|
24
|
+
return val ?? defaultValue;
|
|
25
|
+
},
|
|
26
|
+
has(key: string): boolean {
|
|
27
|
+
// Synchronous check not possible over RPC — always returns true
|
|
28
|
+
return true;
|
|
29
|
+
},
|
|
30
|
+
async update(key: string, value: unknown, target?: ConfigurationTarget): Promise<void> {
|
|
31
|
+
const fullKey = section ? `${section}.${key}` : key;
|
|
32
|
+
await rpc.request("workspace:config:update", fullKey, value, target);
|
|
33
|
+
},
|
|
34
|
+
} as WorkspaceConfiguration;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get workspaceFolders(): WorkspaceFolder[] | undefined {
|
|
38
|
+
// This is sync in VSCode but we return a cached value; updated via RPC event
|
|
39
|
+
return this._cachedFolders;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private _cachedFolders: WorkspaceFolder[] | undefined;
|
|
43
|
+
|
|
44
|
+
/** Called during init to hydrate workspace folders */
|
|
45
|
+
_setFolders(folders: Array<{ uri: string; name: string; index: number }>): void {
|
|
46
|
+
this._cachedFolders = folders.map((f) => ({
|
|
47
|
+
uri: Uri.file(f.uri),
|
|
48
|
+
name: f.name,
|
|
49
|
+
index: f.index,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- File system operations ---
|
|
54
|
+
|
|
55
|
+
readonly fs = {
|
|
56
|
+
readFile: async (uri: Uri): Promise<Uint8Array> => {
|
|
57
|
+
const base64 = await this.rpc.request<string>("workspace:fs:readFile", uri.fsPath);
|
|
58
|
+
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
59
|
+
},
|
|
60
|
+
writeFile: async (uri: Uri, content: Uint8Array): Promise<void> => {
|
|
61
|
+
const base64 = btoa(String.fromCharCode(...content));
|
|
62
|
+
await this.rpc.request("workspace:fs:writeFile", uri.fsPath, base64);
|
|
63
|
+
},
|
|
64
|
+
stat: async (uri: Uri): Promise<{ type: number; size: number; mtime: number }> => {
|
|
65
|
+
return this.rpc.request("workspace:fs:stat", uri.fsPath);
|
|
66
|
+
},
|
|
67
|
+
readDirectory: async (uri: Uri): Promise<Array<[string, number]>> => {
|
|
68
|
+
return this.rpc.request("workspace:fs:readDirectory", uri.fsPath);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
async findFiles(pattern: string, maxResults?: number): Promise<Uri[]> {
|
|
73
|
+
const paths = await this.rpc.request<string[]>("workspace:findFiles", pattern, maxResults ?? 100);
|
|
74
|
+
return paths.map((p) => Uri.file(p));
|
|
75
|
+
}
|
|
76
|
+
}
|