Skip to content

Commit 0afeab9

Browse files
Re-Sync
1 parent 4ed6447 commit 0afeab9

File tree

218 files changed

+11084
-0
lines changed

Some content is hidden

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

218 files changed

+11084
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
return `Failed to connect to the processing service: ${this.cause}`;
21+
}
22+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @module GetActiveTextEditor
3+
* @description An Effect that retrieves the active VS Code text editor.
4+
*/
5+
6+
import { Effect, Option } from "effect";
7+
import * as Vscode from "vscode";
8+
9+
/**
10+
* An Effect that safely retrieves the active text editor.
11+
* @returns An `Effect` that resolves to an `Option<Vscode.TextEditor>`, which
12+
* will be `None` if no editor is active, and `Some` otherwise.
13+
*/
14+
export const GetActiveTextEditor = Effect.sync(() =>
15+
Option.fromNullable(Vscode.window.activeTextEditor),
16+
);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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 const GetDocumentText = (
15+
Document: Vscode.TextDocument,
16+
): Effect.Effect<string> => Effect.sync(() => Document.getText());
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @module InvokeProcessingService
3+
* @description An Effect that sends text to a backend service for processing.
4+
*/
5+
6+
import { Effect } from "effect";
7+
8+
import { ProcessingServiceError } from "./Error.js";
9+
10+
interface ProcessingResult {
11+
Id: string;
12+
Status: "Success";
13+
}
14+
15+
/**
16+
* An Effect that makes a `fetch` request to a local backend service.
17+
* @param TextContent - The text to be processed.
18+
* @returns An `Effect` that resolves to the JSON result from the service,
19+
* or fails with a `ProcessingServiceError`.
20+
*/
21+
export const InvokeProcessingService = (
22+
TextContent: string,
23+
): Effect.Effect<ProcessingResult, ProcessingServiceError> =>
24+
Effect.tryPromise({
25+
try: () =>
26+
fetch("http://localhost:3000/process", {
27+
method: "POST",
28+
headers: { "Content-Type": "text/plain" },
29+
body: TextContent,
30+
}).then((Response) => {
31+
if (!Response.ok) {
32+
throw new Error(
33+
`Server responded with status: ${Response.status}`,
34+
);
35+
}
36+
return Response.json() as Promise<ProcessingResult>;
37+
}),
38+
catch: (cause) => new ProcessingServiceError({ cause }),
39+
});
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 { InvokeProcessingService } from "../../Service/Mountain/InvokeProcessingService.js";
13+
import { GetActiveTextEditor } from "../../Service/Window/GetActiveTextEditor.js";
14+
import {
15+
ShowErrorMessage,
16+
ShowInformationMessage,
17+
} from "../../Service/Window/mod.js";
18+
import { ActiveEditorNotFoundError, ProcessingServiceError } from "./Error.js";
19+
import { GetDocumentText } from "./GetDocumentText.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 Error ? Error.message : String(Error)}`,
56+
),
57+
),
58+
);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @module AsExtensionEvent
3+
* @description Defines a higher-order function that wraps a raw event emitter
4+
* to provide safe error handling for extension listeners.
5+
*/
6+
7+
import type * as Vscode from "vscode";
8+
9+
import type { Log } from "../../Service/mod.js";
10+
11+
/**
12+
* Creates a safe `vscode.Event` by wrapping an existing event emitter.
13+
*
14+
* This function takes a raw `Event` and returns a new `Event` function. When an
15+
* extension subscribes to the new event, its listener is wrapped in a
16+
* `try...catch` block. This prevents a faulty or poorly-written listener in
17+
* one extension from throwing an unhandled exception and crashing the entire
18+
* extension host. Any errors caught are logged using the provided `LogService`.
19+
*
20+
* @param ExtensionId The identifier of the extension that will be listening.
21+
* @param LogService An instance of the logging service to report errors.
22+
* @param ActualEvent The original `vscode.Event<T>` to wrap.
23+
* @returns A new, safe `vscode.Event<T>` that can be exposed to extensions.
24+
*/
25+
export const AsExtensionEvent = <T>(
26+
ExtensionId: Vscode.ExtensionIdentifier,
27+
LogService: Log.Interface,
28+
ActualEvent: Vscode.Event<T>,
29+
): Vscode.Event<T> => {
30+
// Return a new event subscription function.
31+
return (Listener, ThisArgument, Disposables) => {
32+
// Create a "safe" listener that wraps the original extension-provided listener.
33+
const SafeListener = (Event: T) => {
34+
try {
35+
Listener.call(ThisArgument, Event);
36+
} catch (Error) {
37+
LogService.Error(
38+
`[${ExtensionId.value}] FAILED to handle event:`,
39+
Error,
40+
);
41+
// This is also where telemetry reporting for extension errors would be triggered.
42+
}
43+
};
44+
45+
// Subscribe the safe listener to the actual event.
46+
const Handle = ActualEvent(SafeListener);
47+
Disposables?.push(Handle);
48+
return Handle;
49+
};
50+
};

Source/Core/ApiFactory/Create.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* @module CreateApiFactory
3+
* @description The primary factory function that constructs the `vscode` API
4+
* object for a given extension.
5+
*/
6+
7+
import type { IExtensionDescription } from "vs/platform/extensions/common/extensions.js";
8+
import type * as Vscode from "vscode";
9+
10+
import type * as Service from "../../Service/mod.js";
11+
import * as ExtHostType from "../../Type/ExtHostTypes.js";
12+
import { AsExtensionEvent } from "./AsExtensionEvent.js";
13+
import { CreateCommandsNamespace } from "./CreateCommandsNamespace.js";
14+
import { CreateLanguagesNamespace } from "./CreateLanguagesNamespace.js";
15+
import { CreateWindowNamespace } from "./CreateWindowNamespace.js";
16+
import { CreateWorkspaceNamespace } from "./CreateWorkspaceNamespace.js";
17+
import type { Interface as ApiFactory } from "./Service.js";
18+
19+
// Placeholders for other namespace creators
20+
// import { CreateDebugNamespace } from "./CreateDebugNamespace.js";
21+
// import { CreateTasksNamespace } from "./CreateTasksNamespace.js";
22+
// import { CreateWebviewNamespace } from "./CreateWebviewNamespace.js";
23+
24+
/**
25+
* Creates an `ApiFactory` instance.
26+
*
27+
* This function uses a dependency injection pattern, taking all necessary
28+
* extension host services as arguments. It returns a factory object with a
29+
* `Create` method, which is then used to construct the specific `vscode` API
30+
* object for each individual extension.
31+
*
32+
* @param LogService The service for logging messages.
33+
* @param DeprecationService The service for handling API deprecations.
34+
* @param CommandsService The service for command registration and execution.
35+
* @param WorkspaceService The service for workspace-related information and events.
36+
* @param WindowService The service for window-related UI and events.
37+
* @param LanguageFeaturesService The service for registering language providers.
38+
* @param StatusBarService The service for managing status bar items.
39+
* @param WebviewPanelService The service for creating and managing webview panels.
40+
* @param CustomEditorService The service for registering custom editors.
41+
* @param TreeViewService The service for creating and managing tree views.
42+
* @returns An `ApiFactory` object capable of creating `vscode` API instances.
43+
*/
44+
export const CreateApiFactory = (
45+
LogService: Service.Log.Interface,
46+
DeprecationService: Service.ApiDeprecation.Interface,
47+
CommandsService: Service.Commands.Interface,
48+
WorkspaceService: Service.Workspace.Interface,
49+
WindowService: Service.Window.Interface,
50+
LanguageFeaturesService: Service.LanguageFeatures.Interface,
51+
StatusBarService: Service.StatusBar.Interface,
52+
WebviewPanelService: Service.WebviewPanel.Interface,
53+
CustomEditorService: Service.CustomEditor.Interface,
54+
TreeViewService: Service.TreeView.Interface,
55+
// Add other services like Debug, Tasks, etc. as they are implemented
56+
): ApiFactory => ({
57+
/**
58+
* Creates a new, sandboxed `vscode` API object for a specific extension.
59+
* @param Extension The full description of the extension.
60+
* @returns A frozen `vscode` API object.
61+
*/
62+
Create: (Extension: IExtensionDescription): typeof Vscode => {
63+
// --- Create Namespaces ---
64+
const CommandsNamespace = CreateCommandsNamespace(
65+
CommandsService,
66+
Extension,
67+
);
68+
const WorkspaceNamespace = CreateWorkspaceNamespace(
69+
WorkspaceService,
70+
DeprecationService,
71+
Extension,
72+
);
73+
const WindowNamespace = CreateWindowNamespace(
74+
WindowService,
75+
WorkspaceService,
76+
StatusBarService,
77+
WebviewPanelService,
78+
CustomEditorService,
79+
TreeViewService,
80+
(Event) =>
81+
AsExtensionEvent(Extension.identifier, LogService, Event),
82+
Extension,
83+
);
84+
const LanguagesNamespace = CreateLanguagesNamespace(
85+
LanguageFeaturesService,
86+
Extension,
87+
);
88+
// const DebugNamespace = CreateDebugNamespace(DebugService, Extension);
89+
// const TasksNamespace = CreateTasksNamespace(TasksService, Extension);
90+
91+
// --- Assemble Final API Object ---
92+
const Api = {
93+
// This version should come from a centralized product service.
94+
version: "1.85.0",
95+
commands: CommandsNamespace,
96+
window: WindowNamespace,
97+
workspace: WorkspaceNamespace,
98+
languages: LanguagesNamespace,
99+
// Other namespaces would be added here.
100+
// debug: DebugNamespace,
101+
// tasks: TasksNamespace,
102+
103+
// --- Static Types and Enums from VS Code ---
104+
...ExtHostType,
105+
};
106+
107+
// --- Freeze API to prevent runtime modification by extensions ---
108+
Object.freeze(Api.commands);
109+
Object.freeze(Api.window);
110+
Object.freeze(Api.workspace);
111+
Object.freeze(Api.languages);
112+
// ... freeze other namespaces ...
113+
114+
return Object.freeze(Api) as typeof Vscode;
115+
},
116+
});
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* @module CreateCommandsNamespace
3+
* @description Constructs the `vscode.commands` namespace for the API object
4+
* provided to an extension.
5+
*/
6+
7+
import { Effect } from "effect";
8+
import type { IExtensionDescription } from "vs/platform/extensions/common/extensions.js";
9+
import type * as Vscode from "vscode";
10+
11+
import type { Commands as CommandsService } from "../../Service/mod.js";
12+
13+
/**
14+
* Creates the `vscode.commands` namespace object.
15+
*
16+
* This factory function takes the central `CommandsService` and the specific
17+
* extension's description to create a sandboxed `commands` object. The methods
18+
* on this object delegate to the central service, ensuring that command
19+
* registration and execution are managed globally while providing the standard
20+
* `vscode` API to the extension.
21+
*
22+
* @param CommandsService The central service for command management.
23+
* @param Extension The description of the extension for which this API is being created.
24+
* @returns An object that implements the `vscode.commands` API.
25+
*/
26+
export const CreateCommandsNamespace = (
27+
CommandsService: CommandsService.Interface,
28+
Extension: IExtensionDescription,
29+
): typeof Vscode.commands => ({
30+
/**
31+
* Registers a command.
32+
*/
33+
registerCommand: (Id, Handler, ThisArgument) =>
34+
CommandsService.RegisterCommand(Id, Handler, ThisArgument, Extension),
35+
36+
/**
37+
* Registers a command that is only active when a text editor has focus.
38+
*/
39+
registerTextEditorCommand: (Id, Handler, ThisArgument) =>
40+
CommandsService.RegisterTextEditorCommand(
41+
Id,
42+
Handler,
43+
ThisArgument,
44+
Extension,
45+
),
46+
47+
/**
48+
* Executes a command.
49+
* It converts the `Effect`-based service call into a `Promise`, as
50+
* expected by the `vscode` API.
51+
*/
52+
executeCommand: <T>(Id: string, ...Argument: any[]) =>
53+
Effect.runPromise(CommandsService.ExecuteCommand<T>(Id, ...Argument)),
54+
55+
/**
56+
* Retrieves a list of all available command IDs.
57+
* It converts the `Effect`-based service call into a `Promise`, as
58+
* expected by the `vscode` API.
59+
*/
60+
getCommands: (FilterInternal?: boolean) =>
61+
Effect.runPromise(CommandsService.GetCommands(FilterInternal)),
62+
});

0 commit comments

Comments
 (0)