Skip to content

Commit ca2572e

Browse files
Re-Sync
1 parent 9f0ef16 commit ca2572e

File tree

943 files changed

+22705
-4875
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

943 files changed

+22705
-4875
lines changed

Source/Cocoon.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* @module Cocoon
3+
* @description The main entry point for the Cocoon Node.js extension host.
4+
*
5+
* This file orchestrates the entire application lifecycle:
6+
* 1. Sets up the Node.js environment by patching module paths.
7+
* 2. Defines the main application workflow as a declarative `Effect`.
8+
* 3. Composes all service layers (`Core`, `IPC`, `Service`) into a single application layer.
9+
* 4. Executes the main Effect, which includes performing a handshake with Mountain,
10+
* initializing all services with data from the host, and activating extensions.
11+
*/
12+
13+
import * as Path from "path";
14+
import { Barrier, Context, Effect, Layer, Scope } from "effect";
15+
import type { IExtensionHostInitData } from "vs/workbench/services/extensions/common/extensionHostProtocol.js";
16+
17+
import { CoreServiceLayer } from "./Core.js";
18+
import { ExtensionHost } from "./Core/ExtensionHost.js";
19+
import { RequireInterceptor } from "./Core/RequireInterceptor.js";
20+
import { RunProcessPatch } from "./PatchProcess.js";
21+
import { AllServiceLayer } from "./Service.js";
22+
import { InitDataLayer } from "./Service/InitData.js";
23+
import { IPCProvider, Live as LiveIPC } from "./Service/IPC.js";
24+
25+
// --- Pre-initialization Steps ---
26+
// Add the bundled VS Code module path to Node's search paths. This allows
27+
// imports like `vs/base/common/uri.js` to resolve correctly.
28+
const VSCodeOutputDirectory =
29+
process.env["VSCODE_OUT_DIR"] ??
30+
Path.resolve(__dirname, "../../../Dependency/VSCode/out");
31+
(module as any).paths.unshift(VSCodeOutputDirectory);
32+
33+
// --- Application Logic ---
34+
35+
/**
36+
* An Effect that represents the full initialization of all services *after*
37+
* the handshake with Mountain is complete and the init data has been received.
38+
*/
39+
const FullApplicationInitialization = Effect.gen(function* (_) {
40+
const Interceptor = yield* _(RequireInterceptor.Tag);
41+
yield* _(Interceptor.Install());
42+
yield* _(Effect.logInfo("Node.js require() interceptor installed."));
43+
44+
// Now that the environment is fully set up, we can activate extensions.
45+
const Host = yield* _(ExtensionHost.Tag);
46+
// The '*' event signifies activating all extensions marked for startup.
47+
yield* _(
48+
Host.ActivateById("*" as any, { startup: true, activationEvent: "*" }),
49+
);
50+
51+
yield* _(Effect.logInfo("Startup extensions activated."));
52+
});
53+
54+
/**
55+
* The main application workflow, described as a single declarative Effect.
56+
*/
57+
const Main = Effect.gen(function* (_) {
58+
const InitializationBarrier = yield* _(Barrier.make());
59+
60+
// 1. Apply all low-level process patches (e.g., console piping, termination hooks).
61+
yield* _(RunProcessPatch);
62+
63+
// 2. Get the IPC provider.
64+
const IPC = yield* _(IPCProvider.Tag);
65+
66+
// 3. Register the handler that will be called by Mountain to kick off initialization.
67+
IPC.RegisterInvokeHandler(
68+
"initExtensionHost",
69+
(initializationData: IExtensionHostInitData) =>
70+
Effect.gen(function* (_) {
71+
yield* _(
72+
Effect.logInfo(
73+
"Received 'initExtensionHost' data from Mountain.",
74+
),
75+
);
76+
77+
// Compose the final application layer, providing the received init data.
78+
const ApplicationLayer = AllServiceLayer.pipe(
79+
Layer.provide(CoreServiceLayer),
80+
Layer.provide(InitDataLayer(initializationData)),
81+
);
82+
83+
// Provide the full layer to our initialization Effect and run it.
84+
yield* _(
85+
Effect.provide(
86+
FullApplicationInitialization,
87+
ApplicationLayer,
88+
),
89+
);
90+
91+
// Signal that initialization is complete.
92+
yield* _(Barrier.succeed(InitializationBarrier, undefined));
93+
return "initialized"; // Acknowledge completion back to Mountain.
94+
}).pipe(Effect.runPromise),
95+
);
96+
97+
// 4. Send the 'Ready' signal to Mountain, indicating we are ready for init data.
98+
yield* _(IPC.SendNotification("$initialHandshake", []));
99+
yield* _(Effect.logInfo("Cocoon is ready. Sent handshake to Mountain."));
100+
101+
// 5. Wait for the `initExtensionHost` handler to open the barrier.
102+
yield* _(Barrier.await(InitializationBarrier));
103+
yield* _(Effect.logInfo("Cocoon is fully initialized and operational."));
104+
105+
// 6. Keep the process alive indefinitely.
106+
yield* _(Effect.never);
107+
}).pipe(
108+
// Global error handler for the entire application.
109+
Effect.catchAllCause((cause) =>
110+
Effect.logFatal("Cocoon main process failed.", cause),
111+
),
112+
);
113+
114+
// --- Application Layer Composition ---
115+
116+
/**
117+
* A configuration object for the IPC layer.
118+
*/
119+
const ApplicationConfiguration = {
120+
MountainAddress: process.env["MOUNTAIN_ADDR"] || "localhost:50051",
121+
CocoonAddress: process.env["COCOON_ADDR"] || "localhost:50052",
122+
};
123+
124+
/**
125+
* The base Layer for the application, providing the IPC connection.
126+
* Other layers will be built on top of this.
127+
*/
128+
const CocoonBaseLayer = LiveIPC(ApplicationConfiguration);
129+
130+
// --- Run the Application ---
131+
132+
// We create a master Scope for the application. When this scope is closed (e.g., on SIGTERM),
133+
// all finalizers from our `Layer.scoped` resources (like the gRPC client/server)
134+
// will be executed, ensuring a graceful shutdown.
135+
const ApplicationWithScope = Scope.make().pipe(
136+
Effect.flatMap((scope) =>
137+
Effect.provide(Main, CocoonBaseLayer).pipe(Scope.extend(scope)),
138+
),
139+
);
140+
141+
// Fork the entire application into the background.
142+
Effect.runFork(ApplicationWithScope);

Source/Command/ProcessUserData.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* @module ProcessUserData (Command)
3+
* @description The main module for the 'ProcessUserData' command.
4+
*
5+
* This orchestrates the entire workflow of getting text from the active
6+
* editor, sending it to a backend service for processing, and then
7+
* displaying the result to the user.
8+
*/
9+
10+
import { Effect, pipe } from "effect";
11+
12+
import {
13+
ShowErrorMessage,
14+
ShowInformationMessage,
15+
} from "../../Service/Window.js";
16+
import { ActiveEditorNotFoundError } from "./ProcessUserData/Error.js";
17+
import { GetActiveTextEditor } from "./ProcessUserData/GetActiveTextEditor.js";
18+
import { GetDocumentText } from "./ProcessUserData/GetDocumentText.js";
19+
import { InvokeProcessingService } from "./ProcessUserData/InvokeProcessingService.js";
20+
21+
/**
22+
* An `Effect` that encapsulates the entire workflow for processing user data
23+
* from the active text editor, demonstrating declarative, type-safe error
24+
* handling.
25+
*/
26+
export const ProcessUserData = pipe(
27+
Effect.gen(function* (_) {
28+
// Safely get the active editor, which may not exist.
29+
const MaybeEditor = yield* _(GetActiveTextEditor);
30+
31+
// Convert the Option into an Effect that fails with our specific error if empty.
32+
const Editor = yield* _(
33+
MaybeEditor,
34+
Effect.mapError(() => new ActiveEditorNotFoundError()),
35+
);
36+
37+
// If the above succeeds, the workflow proceeds.
38+
const TextContent = yield* _(GetDocumentText(Editor.document));
39+
const ProcessingResult = yield* _(InvokeProcessingService(TextContent));
40+
yield* _(
41+
ShowInformationMessage(
42+
`Processing complete: ${ProcessingResult.ID}`,
43+
),
44+
);
45+
}),
46+
// Declaratively handle all known, tagged failure cases for this workflow.
47+
Effect.catchTags({
48+
ActiveEditorNotFoundError: (Error) => ShowErrorMessage(Error.message),
49+
ProcessingServiceError: (Error) => ShowErrorMessage(Error.message),
50+
}),
51+
// Catch any other unexpected error that might have occurred, safely handling
52+
// the error type.
53+
Effect.catchAll((Error) =>
54+
ShowErrorMessage(
55+
`An unexpected error occurred: ${Error instanceof globalThis.Error ? Error.message : String(Error)}`,
56+
),
57+
),
58+
);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* @module Error (ProcessUserData)
3+
* @description Defines custom, tagged errors for the ProcessUserData command workflow.
4+
*/
5+
6+
import { Data } from "effect";
7+
8+
export class ActiveEditorNotFoundError extends Data.TaggedError(
9+
"ActiveEditorNotFoundError",
10+
)<{}> {
11+
message = "No active text editor found. Please open a file to process.";
12+
}
13+
14+
export class ProcessingServiceError extends Data.TaggedError(
15+
"ProcessingServiceError",
16+
)<{
17+
readonly cause: unknown;
18+
}> {
19+
get message() {
20+
const causeMessage =
21+
this.cause instanceof Error
22+
? this.cause.message
23+
: String(this.cause);
24+
return `Failed to connect to the processing service: ${causeMessage}`;
25+
}
26+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @module GetActiveTextEditor
3+
* @description An Effect that retrieves the active VS Code text editor by
4+
* delegating to the Window service.
5+
*/
6+
7+
import { Effect, Option } from "effect";
8+
import type * as vscode from "vscode";
9+
10+
import { Window } from "../../Service/Window.js";
11+
12+
/**
13+
* An Effect that safely retrieves the active text editor.
14+
* @returns An `Effect` that resolves to an `Option<vscode.TextEditor>`, which
15+
* will be `None` if no editor is active, and `Some` otherwise.
16+
*/
17+
export const GetActiveTextEditor = Effect.gen(function* (_) {
18+
const WindowService = yield* _(Window.Tag);
19+
return Option.fromNullable(WindowService.activeTextEditor);
20+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @module GetDocumentText
3+
* @description An Effect that retrieves the full text content of a document.
4+
*/
5+
6+
import { Effect } from "effect";
7+
import type * as VSCode from "vscode";
8+
9+
/**
10+
* An Effect that gets the full text content of a given document.
11+
* @param Document The `vscode.TextDocument` to read from.
12+
* @returns An `Effect` that synchronously resolves to the document's text content.
13+
*/
14+
export function GetDocumentText(
15+
Document: VSCode.TextDocument,
16+
): Effect.Effect<string> {
17+
return Effect.sync(() => Document.getText());
18+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* @module InvokeProcessingService
3+
* @description An Effect that sends text to a backend service for processing.
4+
* This version uses the internal IPC service instead of a direct `fetch`.
5+
*/
6+
7+
import { Effect } from "effect";
8+
9+
import { IPC } from "../../Service/IPC.js";
10+
import { ProcessingServiceError } from "./Error.js";
11+
12+
interface ProcessingResult {
13+
ID: string;
14+
Status: "Success";
15+
}
16+
17+
/**
18+
* An Effect that makes an RPC call to a backend service.
19+
* @param TextContent The text to be processed.
20+
* @returns An `Effect` that resolves to the result from the service,
21+
* or fails with a `ProcessingServiceError`.
22+
*/
23+
export function InvokeProcessingService(
24+
TextContent: string,
25+
): Effect.Effect<ProcessingResult, ProcessingServiceError> {
26+
return Effect.gen(function* (_) {
27+
const IPCService = yield* _(IPC.Tag);
28+
return yield* _(
29+
IPCService.SendRequest<ProcessingResult>("$processText", [
30+
TextContent,
31+
]),
32+
Effect.mapError((cause) => new ProcessingServiceError({ cause })),
33+
);
34+
});
35+
}

Source/Core.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @module Core
3+
* @description This is the aggregator module for all core services of the Cocoon
4+
* extension host. These services are fundamental to the runtime's operation,
5+
* managing the extension lifecycle, API creation, and module loading.
6+
*/
7+
8+
import { Layer } from "effect";
9+
10+
import { Live as LiveAPIFactory } from "./Core/APIFactory.js";
11+
import { Live as LiveESMInterceptor } from "./Core/ESMInterceptor.js";
12+
import { Live as LiveExtensionHost } from "./Core/ExtensionHost.js";
13+
import { Live as LiveExtensionPath } from "./Core/ExtensionPath.js";
14+
import { Live as LiveHostKindPicker } from "./Core/HostKindPicker.js";
15+
import { Live as LiveNodeModuleShim } from "./Core/NodeModuleShim.js";
16+
import { Live as LiveRequireInterceptor } from "./Core/RequireInterceptor.js";
17+
18+
// --- Re-exporting the full public API (Tag, Interface, Live Layer) for each core service ---
19+
20+
export * as APIFactory from "./Core/APIFactory.js";
21+
export * as ESMInterceptor from "./Core/ESMInterceptor.js";
22+
export * as ExtensionHost from "./Core/ExtensionHost.js";
23+
export * as ExtensionPath from "./Core/ExtensionPath.js";
24+
export * as HostKindPicker from "./Core/HostKindPicker.js";
25+
export * as NodeModuleShim from "./Core/NodeModuleShim.js";
26+
export * as RequireInterceptor from "./Core/RequireInterceptor.js";
27+
28+
/**
29+
* A single, composed layer that provides all core services of the extension host.
30+
* This simplifies the process of building the final application layer in `Cocoon.ts`.
31+
*/
32+
export const CoreServiceLayer = Layer.mergeAll(
33+
LiveAPIFactory,
34+
LiveESMInterceptor,
35+
LiveExtensionHost,
36+
LiveExtensionPath,
37+
LiveHostKindPicker,
38+
LiveNodeModuleShim,
39+
LiveRequireInterceptor,
40+
);

0 commit comments

Comments
 (0)