extHostMcp.ts ×43

Frontier kind: Code frontier

unlabeled · c_b868cc492e8e

26 tests · 83393 LOC · 310 files · introduces 0 tests · 874 LOC · 7 files

Introduces — evidence that enters the hierarchy at this concept

Code
152 ranges874 lines · 7 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
5867 ranges83393 lines · 310 files · Browse complete extent
All tests (intent)
26 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

7 files ranked by introduced lines: 874 introduced LOC across 152 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/api/common/extHostMcp.ts 322 introduced LOC · 43 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extHostMcp.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as vscode from 'vscode';
7 > import { DeferredPromise, raceCancellationError, Sequencer, timeout } from '../../../base/common/async.js';
8 > import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js';
9 > import { CancellationError } from '../../../base/common/errors.js';
10 > import { Emitter, Event } from '../../../base/common/event.js';
11 > import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
12 > import { AUTH_SCOPE_SEPARATOR, fetchAuthorizationServerMetadata, fetchResourceMetadata, getDefaultMetadataForUrl, IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata, parseWWWAuthenticateHeader, scopesMatch } from '../../../base/common/oauth.js';
13 > import { SSEParser } from '../../../base/common/sseParser.js';
14 > import { URI, UriComponents } from '../../../base/common/uri.js';
15 > import { vArray, vNumber, vObj, vObjAny, vOptionalProp, vString } from '../../../base/common/validation.js';
16 > import { ConfigurationTarget } from '../../../platform/configuration/common/configuration.js';
17 > import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
18 > import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
19 > import { canLog, ILogService, LogLevel } from '../../../platform/log/common/log.js';
20 > import product from '../../../platform/product/common/product.js';
21 > import { StorageScope } from '../../../platform/storage/common/storage.js';
22 > import { extensionPrefixedIdentifier, McpCollectionDefinition, McpConnectionState, McpServerDefinition, McpServerLaunch, McpServerStaticMetadata, McpServerStaticToolAvailability, McpServerTransportHTTP, McpServerTransportType, UserInteractionRequiredError } from '../../contrib/mcp/common/mcpTypes.js';
23 > import { MCP } from '../../contrib/mcp/common/modelContextProtocol.js';
24 > import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js';
25 > import { ExtHostMcpShape, IMcpAuthenticationDetails, IAuthMetadataSource, IStartMcpOptions, MainContext, MainThreadMcpShape, IAuthResourceMetadataSource, IAuthServerMetadataSource } from './extHost.protocol.js';
26 > import { IExtHostInitDataService } from './extHostInitDataService.js';
27 > import { IExtHostRpcService } from './extHostRpcService.js';
28 > import * as Convert from './extHostTypeConverters.js';
29 > import { McpHttpServerDefinition, McpStdioServerDefinition, McpToolAvailability } from './extHostTypes.js';
30 > import { IExtHostVariableResolverProvider } from './extHostVariableResolverService.js';
31 > import { IExtHostWorkspace } from './extHostWorkspace.js';
32 >
33 > export const IExtHostMpcService = createDecorator<IExtHostMpcService>('IExtHostMpcService');
34 >
35 > export interface IExtHostMpcService extends ExtHostMcpShape {
36 > registerMcpConfigurationProvider(extension: IExtensionDescription, id: string, provider: vscode.McpServerDefinitionProvider): IDisposable;
37 >
38 > /** Event that fires when the set of MCP server definitions changes. */
39 > readonly onDidChangeMcpServerDefinitions: Event<void>;
40 >
41 > /** Returns all MCP server definitions known to the editor. */
42 > readonly mcpServerDefinitions: readonly vscode.McpServerDefinition[];
43 >
44 > /** Starts an MCP gateway that exposes MCP servers via HTTP endpoints. */
45 > startMcpGateway(chatSessionResource?: URI): Promise<vscode.McpGateway | undefined>;
46 > }
47 >
48 > const serverDataValidation = vObj({
49 > label: vString(),
50 > version: vOptionalProp(vString()),
51 > metadata: vOptionalProp(vObj({
52 > capabilities: vOptionalProp(vObjAny()),
53 > serverInfo: vOptionalProp(vObjAny()),
54 > tools: vOptionalProp(vArray(vObj({
55 > availability: vNumber(),
56 > definition: vObjAny(),
57 > }))),
58 > })),
59 > authentication: vOptionalProp(vObj({
60 > providerId: vString(),
61 > scopes: vArray(vString()),
62 > }))
63 > });
64 >
65 > // Can be validated with:
66 > // declare const _serverDataValidationTest: vscode.McpStdioServerDefinition | vscode.McpHttpServerDefinition;
67 > // const _serverDataValidationProd: ValidatorType<typeof serverDataValidation> = _serverDataValidationTest;
68 >
69 > export class ExtHostMcpService extends Disposable implements IExtHostMpcService {
70 > protected _proxy: MainThreadMcpShape;
71 > private readonly _initialProviderPromises = new Set<Promise<void>>();
72 > protected readonly _sseEventSources = this._register(new DisposableMap<number, McpHTTPHandle>());
73 > private readonly _unresolvedMcpServers = new Map</* collectionId */ string, {
74 > provider: vscode.McpServerDefinitionProvider;
75 > servers: vscode.McpServerDefinition[];
76 > }>();
77 >
78 > // MCP server definitions synced from main thread
79 > private readonly _onDidChangeMcpServerDefinitions = this._register(new Emitter<void>());
80 > readonly onDidChangeMcpServerDefinitions: Event<void> = this._onDidChangeMcpServerDefinitions.event;
81 > private _mcpServerDefinitions: readonly vscode.McpServerDefinition[] = [];
82 >
83 > // Active gateways with their server emitters for dynamic updates
84 > private readonly _activeGateways = new Map<string, {
85 > servers: vscode.McpGatewayServer[];
86 > onDidChangeServers: Emitter<readonly vscode.McpGatewayServer[]>;
87 > }>();
88 >
89 > constructor(
90 @IExtHostRpcService extHostRpc: IExtHostRpcService,
91 @ILogService protected readonly _logService: ILogService,
97 this._proxy = extHostRpc.getProxy(MainContext.MainThreadMcp);
98 }
100 > /** Returns all MCP server definitions known to the editor. */
101 > get mcpServerDefinitions(): readonly vscode.McpServerDefinition[] {
102 return this._mcpServerDefinitions;
103 }
105 > /** Called by main thread to notify that MCP server definitions have changed. */
106 > $onDidChangeMcpServerDefinitions(servers: McpServerDefinition.Serialized[]): void {
107 this._mcpServerDefinitions = servers.map(dto => Convert.McpServerDefinition.to(dto));
108 this._onDidChangeMcpServerDefinitions.fire();
109 }
111 > $startMcp(id: number, opts: IStartMcpOptions): void {
112 this._startMcp(id, McpServerLaunch.fromSerialized(opts.launch), opts.defaultCwd && URI.revive(opts.defaultCwd), opts.errorOnUserInteraction);
113 }
115 > protected _startMcp(id: number, launch: McpServerLaunch, _defaultCwd?: URI, errorOnUserInteraction?: boolean): void {
116 if (launch.type === McpServerTransportType.HTTP) {
117 this._sseEventSources.set(id, new McpHTTPHandle(id, launch, this._proxy, this._logService, errorOnUserInteraction));
121 throw new Error('not implemented');
122 }
124 > async $substituteVariables<T>(_workspaceFolder: UriComponents | undefined, value: T): Promise<T> {
125 const folderURI = URI.revive(_workspaceFolder);
126 const folder = folderURI && await this._workspaceService.resolveWorkspaceFolder(folderURI);
132 }, value) as T;
133 }
135 > $stopMcp(id: number): void {
136 this._sseEventSources.get(id)
137 ?.close()
138 .then(() => this._didClose(id));
139 }
141 > private _didClose(id: number) {
142 this._sseEventSources.deleteAndDispose(id);
143 }
145 > $sendMessage(id: number, message: string): void {
146 this._sseEventSources.get(id)?.send(message);
147 }
149 > async $waitForInitialCollectionProviders(): Promise<void> {
150 await Promise.all(this._initialProviderPromises);
151 }
153 > async $resolveMcpLaunch(collectionId: string, label: string): Promise<McpServerLaunch.Serialized | undefined> {
154 const rec = this._unresolvedMcpServers.get(collectionId);
155 if (!rec) {
168 return resolved ? Convert.McpServerDefinition.from(resolved) : undefined;
169 }
171 > /** {@link vscode.lm.registerMcpServerDefinitionProvider} */
172 > public registerMcpConfigurationProvider(extension: IExtensionDescription, id: string, provider: vscode.McpServerDefinitionProvider): IDisposable {
173 const store = new DisposableStore();
174
263 return store;
264 }
266 > /** {@link vscode.lm.startMcpGateway} */
267 > public async startMcpGateway(chatSessionResource?: URI): Promise<vscode.McpGateway | undefined> {
268 const result = await this._proxy.$startMcpGateway(chatSessionResource?.toJSON());
269 if (!result) {
290 };
291 }
293 > /** Called by main thread to notify that a gateway's server set has changed. */
294 > $onDidChangeGatewayServers(gatewayId: string, newServers: { label: string; address: UriComponents }[]): void {
295 const gateway = this._activeGateways.get(gatewayId);
296 if (!gateway) {
306 gateway.onDidChangeServers.fire(servers);
307 }
308 > } extHostMcp.ts
309 >
310 function stringifyError(err: unknown): string {
311 if (!(err instanceof Error)) {
320 return msg;
321 }
323 > const enum HttpMode {
324 > Unknown,
325 > Http,
326 > SSE,
327 > }
328 >
329 > type HttpModeT =
330 > | { value: HttpMode.Unknown }
331 > | { value: HttpMode.Http; sessionId: string | undefined }
332 > | { value: HttpMode.SSE; endpoint: string };
333 >
334 > const MAX_FOLLOW_REDIRECTS = 5;
335 > const REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308];
336 > // MCP server URLs are restricted to http(s) at configuration time; the redirect
337 > // path must enforce the same so a Location header cannot reach unix://, pipe://,
338 > // file://, etc.
339 > const ALLOWED_REDIRECT_PROTOCOLS = new Set(['http:', 'https:']);
340 > // Credential-bearing headers that must not be replayed to a different origin
341 > // after a redirect (matches browser fetch / curl behavior). Compared case-insensitively.
342 > const CROSS_ORIGIN_STRIPPED_HEADERS = new Set(['authorization', 'cookie', 'proxy-authorization', 'mcp-session-id']);
343 >
344 function setHostHeader(headers: Record<string, string>, name: string, value: string): void {
345 for (const configuredName of Object.keys(headers)) {
350 headers[name] = value;
351 }
353 > /**
354 > * Implementation of both MCP HTTP Streaming as well as legacy SSE.
355 > *
356 > * The first request will POST to the endpoint, assuming HTTP streaming. If the
357 > * server is legacy SSE, it should return some 4xx status in that case,
358 > * and we'll automatically fall back to SSE and res
359 > */
360 > export class McpHTTPHandle extends Disposable {
361 > private readonly _requestSequencer = new Sequencer();
362 > private readonly _postEndpoint = new DeferredPromise<{ url: string; transport: McpServerTransportHTTP }>();
363 > private _mode: HttpModeT = { value: HttpMode.Unknown };
364 > private readonly _cts = new CancellationTokenSource();
365 > private readonly _abortCtrl = new AbortController();
366 > private _authMetadata?: AuthMetadata;
367 > private _didSendClose = false;
368 >
369 > constructor(
370 private readonly _id: number,
371 private readonly _launch: McpServerTransportHTTP,
382 this._proxy.$onDidChangeState(this._id, { state: McpConnectionState.Kind.Running });
383 }
385 > async send(message: string) {
386 try {
387 if (this._mode.value === HttpMode.Unknown) {
395 }
396 }
398 > async close() {
399 if (this._mode.value === HttpMode.Http && this._mode.sessionId && !this._didSendClose) {
400 this._didSendClose = true;
408 this._proxy.$onDidChangeState(this._id, { state: McpConnectionState.Kind.Stopped });
409 }
411 > private async _closeSession(sessionId: string) {
412 const headers: Record<string, string> = {
413 ...Object.fromEntries(this._launch.headers),
432 );
433 }
435 > private _send(message: string) {
436 if (this._mode.value === HttpMode.SSE) {
437 return this._sendLegacySSE(this._mode.endpoint, message);
440 }
441 }
443 > /**
444 > * Sends a streamable-HTTP request.
445 > * 1. Posts to the endpoint
446 > * 2. Updates internal state as needed. Falls back to SSE if appropriate.
447 > * 3. If the response body is empty, JSON, or a JSON stream, handle it appropriately.
448 > */
449 > private async _sendStreamableHttp(message: string, sessionId: string | undefined) {
450 const asBytes = new TextEncoder().encode(message) as Uint8Array<ArrayBuffer>;
451 const headers: Record<string, string> = {
511 await this._handleSuccessfulStreamableHttp(res, message);
512 }
514 > private async _sseFallbackWithMessage(message: string) {
515 const endpoint = await this._attachSSE();
516 if (endpoint) {
519 }
520 }
522 > private async _handleSuccessfulStreamableHttp(res: CommonResponse, message: string) {
523 if (res.status === 202) {
524 return; // no body
554 }
555 }
557 > /**
558 > * Attaches the SSE backchannel that streamable HTTP servers can use
559 > * for async notifications. This is a "MAY" support, so if the server gives
560 > * us a 4xx code, we'll stop trying to connect..
561 > */
562 > private async _attachStreamableBackchannel() {
563 let lastEventId: string | undefined;
564 let canReconnectAt: number | undefined;
629 }
630 }
632 > /**
633 > * Starts a legacy SSE attachment, where the SSE response is the session lifetime.
634 > * Unlike `_attachStreamableBackchannel`, this fails the server if it disconnects.
635 > */
636 > private async _attachSSE(): Promise<string | undefined> {
637 const postEndpoint = new DeferredPromise<string>();
638 const headers: Record<string, string> = {
676 return postEndpoint.p;
677 }
679 > /**
680 > * Sends a legacy SSE message to the server. The response is always empty and
681 > * is otherwise received in {@link _attachSSE}'s loop.
682 > */
683 > private async _sendLegacySSE(url: string, message: string) {
684 const asBytes = new TextEncoder().encode(message) as Uint8Array<ArrayBuffer>;
685 const headers: Record<string, string> = {
698 }
699 }
701 > /** Generic handle to pipe a response into an SSE parser. */
702 > private async _doSSE(parser: SSEParser, res: CommonResponse) {
703 if (!res.body) {
704 return;
724 } while (!chunk.done);
725 }
727 > private async _addAuthHeader(headers: Record<string, string>, options?: { forceNewRegistration?: boolean; errorOnUserInteraction?: boolean }) {
728 const errorOnUserInteraction = options?.errorOnUserInteraction ?? this._errorOnUserInteraction;
729 if (this._authMetadata) {
782 return headers;
783 }
785 > private _log(level: LogLevel, message: string) {
786 if (!this._store.isDisposed) {
787 this._proxy.$onDidPublishLog(this._id, level, message);
788 }
789 }
791 > private async _getErrText(res: CommonResponse) {
792 try {
793 return await res.text();
796 }
797 }
799 > /**
800 > * Helper method to perform fetch with authentication retry logic.
801 > * If the initial request returns an auth error and we don't have auth metadata,
802 > * it will populate the auth metadata and retry once.
803 > * If we already have auth metadata, check if the scopes changed and update them.
804 > */
805 > private async _fetchWithAuthRetry(mcpUrl: string, init: MinimalRequestInit, headers: Record<string, string>): Promise<CommonResponse> {
806 const doFetch = () => this._fetch(mcpUrl, init);
807
845 return res;
846 }
848 > private async _fetch(url: string, init: MinimalRequestInit): Promise<CommonResponse> {
849 setHostHeader(init.headers, 'user-agent', `${product.nameLong}/${product.version}`);
850
921 return response;
922 }
924 > protected _fetchInternal(url: string, init?: CommonRequestInit): Promise<CommonResponse> {
925 return fetch(url, init);
926 }
927 > } extHostMcp.ts
928 >
929 > interface MinimalRequestInit {
930 > method: string;
931 > headers: Record<string, string>;
932 > body?: Uint8Array<ArrayBuffer>;
933 > }
934 >
935 > export interface CommonRequestInit extends MinimalRequestInit {
936 > signal?: AbortSignal;
937 > redirect?: RequestRedirect;
938 > }
939 >
940 > export interface CommonResponse {
941 > status: number;
942 > statusText: string;
943 > headers: Headers;
944 > body?: ReadableStream | null;
945 > url: string;
946 > json(): Promise<any>;
947 > text(): Promise<string>;
948 > }
949 >
950 function isJSON(str: string): boolean {
951 try {
956 }
957 }
959 function isAuthStatusCode(status: number): boolean {
960 return status === 401 || status === 403;
961 }
963 >
964 > //#region AuthMetadata
965 >
966 > /**
967 > * Logger callback type for AuthMetadata operations.
968 > */
969 > export type AuthMetadataLogger = (level: LogLevel, message: string) => void;
970 >
971 > /**
972 > * Interface for authentication metadata that can be updated when scopes change.
973 > */
974 > export interface IAuthMetadata {
975 > readonly authorizationServer: URI;
976 > readonly serverMetadata: IAuthorizationServerMetadata;
977 > readonly resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined;
978 > readonly scopes: string[] | undefined;
979 > /** Telemetry data about how auth metadata was discovered */
980 > readonly telemetry: IAuthMetadataSource;
981 >
982 > /**
983 > * Updates the scopes based on the WWW-Authenticate header in the response.
984 > * @param response The HTTP response containing potential scope challenges
985 > * @returns true if scopes were updated, false otherwise
986 > */
987 > update(responseHeaders: Headers): boolean;
988 > }
989 >
990 > /**
991 > * Concrete implementation of IAuthMetadata that manages OAuth authentication metadata.
992 > * Consumers should use {@link createAuthMetadata} to create instances.
993 > */
994 > class AuthMetadata implements IAuthMetadata {
995 > private _scopes: string[] | undefined;
996 >
997 > constructor(
998 public readonly authorizationServer: URI,
999 public readonly serverMetadata: IAuthorizationServerMetadata,
1005 this._scopes = scopes;
1006 }
1007 > extHostMcp.ts
1008 > get scopes(): string[] | undefined {
1009 return this._scopes;
1010 }
1011 > extHostMcp.ts
1012 > update(responseHeaders: Headers): boolean {
1013 const scopesChallenge = this._parseScopesFromResponse(responseHeaders);
1014 if (!scopesMatch(scopesChallenge, this._scopes)) {
1019 return false;
1020 }
1021 > extHostMcp.ts
1022 > private _parseScopesFromResponse(responseHeaders: Headers): string[] | undefined {
1023 const authHeader = responseHeaders.get('WWW-Authenticate');
1024 if (!authHeader) {
1037 return undefined;
1038 }
1039 > } extHostMcp.ts
1040 >
1041 > /**
1042 > * Options for creating AuthMetadata.
1043 > */
1044 > export interface ICreateAuthMetadataOptions {
1045 > /** Headers to include when fetching metadata from the same origin as the resource server */
1046 > sameOriginHeaders?: Record<string, string>;
1047 > /** Fetch function to use for HTTP requests */
1048 > fetch: (url: string, init: MinimalRequestInit) => Promise<CommonResponse>;
1049 > /** Logger function for diagnostic output */
1050 > log: AuthMetadataLogger;
1051 > }
1052 >
1053 > /**
1054 > * Creates an AuthMetadata instance by discovering OAuth metadata from the server.
1055 > *
1056 > * This function:
1057 > * 1. Parses the WWW-Authenticate header for resource_metadata and scope challenges
1058 > * 2. Fetches OAuth protected resource metadata from well-known URIs or the challenge URL
1059 > * 3. Fetches authorization server metadata
1060 > * 4. Falls back to default metadata if discovery fails
1061 > *
1062 > * @param resourceUrl The resource server URL
1063 > * @param wwwAuthenticateValue The value of the WWW-Authenticate header from the original HTTP response
1064 > * @param options Configuration options including headers, fetch function, and logger
1065 > * @returns A new AuthMetadata instance
1066 > */
1067 export async function createAuthMetadata(
1068 resourceUrl: string,
1164 );
1165 }
1166 > extHostMcp.ts
1167 > /**
1168 > * Parses the WWW-Authenticate header for resource_metadata and scope challenges.
1169 > */
1170 function parseWWWAuthenticateHeaderForChallenges(
1171 wwwAuthenticateValue: string | undefined,
1199 return { resourceMetadataChallenge, scopesChallenge };
1200 }
1201 > extHostMcp.ts
1202 > //#endregion
src/vs/workbench/api/common/extHostTextEditor.ts 153 introduced LOC · 32 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extHostTextEditor.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { ok } from '../../../base/common/assert.js';
7 > import { ReadonlyError, illegalArgument } from '../../../base/common/errors.js';
8 > import { IdGenerator } from '../../../base/common/idGenerator.js';
9 > import { TextEditorCursorStyle } from '../../../editor/common/config/editorOptions.js';
10 > import { IRange } from '../../../editor/common/core/range.js';
11 > import { ISingleEditOperation } from '../../../editor/common/core/editOperation.js';
12 > import { IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, MainThreadTextEditorsShape } from './extHost.protocol.js';
13 > import * as TypeConverters from './extHostTypeConverters.js';
14 > import { EndOfLine, Position, Range, Selection, SnippetString, TextEditorLineNumbersStyle, TextEditorRevealType } from './extHostTypes.js';
15 > import type * as vscode from 'vscode';
16 > import { ILogService } from '../../../platform/log/common/log.js';
17 > import { Lazy } from '../../../base/common/lazy.js';
18 > import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
19 >
20 > export class TextEditorDecorationType {
21 >
22 > private static readonly _Keys = new IdGenerator('TextEditorDecorationType');
23 >
24 > readonly value: vscode.TextEditorDecorationType;
25 >
26 > constructor(proxy: MainThreadTextEditorsShape, extension: IExtensionDescription, options: vscode.DecorationRenderOptions) {
27 const key = TextEditorDecorationType._Keys.nextId();
28 proxy.$registerTextEditorDecorationType(extension.identifier, key, TypeConverters.DecorationRenderOptions.from(options));
34 });
35 }
37 > }
38 >
39 > export interface ITextEditOperation {
40 > range: vscode.Range;
41 > text: string | null;
42 > forceMoveMarkers: boolean;
43 > }
44 >
45 > export interface IEditData {
46 > documentVersionId: number;
47 > edits: ITextEditOperation[];
48 > setEndOfLine: EndOfLine | undefined;
49 > undoStopBefore: boolean;
50 > undoStopAfter: boolean;
51 > }
52 >
53 > class TextEditorEdit {
54 >
55 > private readonly _document: vscode.TextDocument;
56 > private readonly _documentVersionId: number;
57 > private readonly _undoStopBefore: boolean;
58 > private readonly _undoStopAfter: boolean;
59 > private _collectedEdits: ITextEditOperation[] = [];
60 > private _setEndOfLine: EndOfLine | undefined = undefined;
61 > private _finalized: boolean = false;
62 >
63 > constructor(document: vscode.TextDocument, options: { undoStopBefore: boolean; undoStopAfter: boolean }) {
64 this._document = document;
65 this._documentVersionId = document.version;
67 this._undoStopAfter = options.undoStopAfter;
68 }
70 > finalize(): IEditData {
71 this._finalized = true;
72 return {
78 };
79 }
81 > private _throwIfFinalized() {
82 if (this._finalized) {
83 throw new Error('Edit is only valid while callback runs');
84 }
85 }
87 > replace(location: Position | Range | Selection, value: string): void {
88 this._throwIfFinalized();
89 let range: Range | null = null;
99 this._pushEdit(range, value, false);
100 }
102 > insert(location: Position, value: string): void {
103 this._throwIfFinalized();
104 this._pushEdit(new Range(location, location), value, true);
105 }
107 > delete(location: Range | Selection): void {
108 this._throwIfFinalized();
109 let range: Range | null = null;
117 this._pushEdit(range, null, true);
118 }
120 > private _pushEdit(range: Range, text: string | null, forceMoveMarkers: boolean): void {
121 const validRange = this._document.validateRange(range);
122 this._collectedEdits.push({
126 });
127 }
129 > setEndOfLine(endOfLine: EndOfLine): void {
130 this._throwIfFinalized();
131 if (endOfLine !== EndOfLine.LF && endOfLine !== EndOfLine.CRLF) {
135 this._setEndOfLine = endOfLine;
136 }
138 >
139 > export class ExtHostTextEditorOptions {
140 >
141 > private _proxy: MainThreadTextEditorsShape;
142 > private _id: string;
143 > private _logService: ILogService;
144 >
145 > private _tabSize!: number;
146 > private _indentSize!: number;
147 > private _originalIndentSize!: number | 'tabSize';
148 > private _insertSpaces!: boolean;
149 > private _cursorStyle!: TextEditorCursorStyle;
150 > private _lineNumbers!: TextEditorLineNumbersStyle;
151 >
152 > readonly value: vscode.TextEditorOptions;
153 >
154 > constructor(proxy: MainThreadTextEditorsShape, id: string, source: IResolvedTextEditorConfiguration, logService: ILogService) {
155 this._proxy = proxy;
156 this._id = id;
193 };
194 }
196 > public _accept(source: IResolvedTextEditorConfiguration): void {
197 this._tabSize = source.tabSize;
198 this._indentSize = source.indentSize;
202 this._lineNumbers = TypeConverters.TextEditorLineNumbersStyle.to(source.lineNumbers);
203 }
205 > // --- internal: tabSize
206 >
207 > private _validateTabSize(value: number | string): number | 'auto' | null {
208 if (value === 'auto') {
209 return 'auto';
222 return null;
223 }
225 > private _setTabSize(value: number | string) {
226 const tabSize = this._validateTabSize(value);
227 if (tabSize === null) {
241 }));
242 }
244 > // --- internal: indentSize
245 >
246 > private _validateIndentSize(value: number | string): number | 'tabSize' | null {
247 if (value === 'tabSize') {
248 return 'tabSize';
261 return null;
262 }
264 > private _setIndentSize(value: number | string) {
265 const indentSize = this._validateIndentSize(value);
266 if (indentSize === null) {
281 }));
282 }
284 > // --- internal: insert spaces
285 >
286 > private _validateInsertSpaces(value: boolean | string): boolean | 'auto' {
287 if (value === 'auto') {
288 return 'auto';
290 return (value === 'false' ? false : Boolean(value));
291 }
293 > private _setInsertSpaces(value: boolean | string) {
294 const insertSpaces = this._validateInsertSpaces(value);
295 if (typeof insertSpaces === 'boolean') {
305 }));
306 }
308 > // --- internal: cursor style
309 >
310 > private _setCursorStyle(value: TextEditorCursorStyle) {
311 if (this._cursorStyle === value) {
312 // nothing to do
318 }));
319 }
321 > // --- internal: line number
322 >
323 > private _setLineNumbers(value: TextEditorLineNumbersStyle) {
324 if (this._lineNumbers === value) {
325 // nothing to do
331 }));
332 }
334 > public assign(newOptions: vscode.TextEditorOptions) {
335 const bulkConfigurationUpdate: ITextEditorConfigurationUpdate = {};
336 let hasUpdate = false;
396 }
397 }
399 > private _warnOnError(action: string, promise: Promise<any>): void {
400 promise.catch(err => {
401 this._logService.warn(`ExtHostTextEditorOptions '${action}' failed:'`);
403 });
404 }
406 >
407 > export class ExtHostTextEditor {
408 >
409 > private _selections: Selection[];
410 > private _options: ExtHostTextEditorOptions;
411 > private _visibleRanges: Range[];
412 > private _viewColumn: vscode.ViewColumn | undefined;
413 > private _disposed: boolean = false;
414 > private _hasDecorationsForKey = new Set<string>();
415 > private _diffInformation: vscode.TextEditorDiffInformation[] | undefined;
416 >
417 > readonly value: vscode.TextEditor;
418 >
419 > constructor(
420 readonly id: string,
421 private readonly _proxy: MainThreadTextEditorsShape,
583 });
584 }
586 > dispose() {
587 ok(!this._disposed);
588 this._disposed = true;
589 }
591 > // --- incoming: extension host MUST accept what the renderer says
592 >
593 > _acceptOptions(options: IResolvedTextEditorConfiguration): void {
594 ok(!this._disposed);
595 this._options._accept(options);
596 }
598 > _acceptVisibleRanges(value: Range[]): void {
599 ok(!this._disposed);
600 this._visibleRanges = value;
601 }
603 > _acceptViewColumn(value: vscode.ViewColumn) {
604 ok(!this._disposed);
605 this._viewColumn = value;
606 }
608 > _acceptSelections(selections: Selection[]): void {
609 ok(!this._disposed);
610 this._selections = selections;
611 }
613 > _acceptDiffInformation(diffInformation: vscode.TextEditorDiffInformation[] | undefined): void {
614 ok(!this._disposed);
615 this._diffInformation = diffInformation;
616 }
618 > private async _trySetSelection(): Promise<vscode.TextEditor | null | undefined> {
619 const selection = this._selections.map(TypeConverters.Selection.from);
620 await this._runOnProxy(() => this._proxy.$trySetSelections(this.id, selection));
621 return this.value;
622 }
624 > private _applyEdit(editBuilder: TextEditorEdit): Promise<boolean> {
625 const editData = editBuilder.finalize();
626
675 });
676 }
677 > private _runOnProxy(callback: () => Promise<any>): Promise<ExtHostTextEditor | undefined | null> { extHostTextEditor.ts
678 if (this._disposed) {
679 this._logService.warn('TextEditor is closed/disposed');
src/vs/workbench/api/common/extHostEditorTabs.ts 114 introduced LOC · 25 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extHostEditorTabs.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { diffSets } from '../../../base/common/collections.js';
7 > import { Emitter } from '../../../base/common/event.js';
8 > import { assertReturnsDefined } from '../../../base/common/types.js';
9 > import { URI } from '../../../base/common/uri.js';
10 > import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
11 > import { IEditorTabDto, IEditorTabGroupDto, IExtHostEditorTabsShape, MainContext, MainThreadEditorTabsShape, TabInputKind, TabModelOperationKind, TabOperation } from './extHost.protocol.js';
12 > import { IExtHostRpcService } from './extHostRpcService.js';
13 > import * as typeConverters from './extHostTypeConverters.js';
14 > import { ChatEditorTabInput, CustomEditorTabInput, InteractiveWindowInput, NotebookDiffEditorTabInput, NotebookEditorTabInput, TerminalEditorTabInput, TextDiffTabInput, TextMergeTabInput, TextTabInput, WebviewEditorTabInput, TextMultiDiffTabInput } from './extHostTypes.js';
15 > import type * as vscode from 'vscode';
16 >
17 > export interface IExtHostEditorTabs extends IExtHostEditorTabsShape {
18 > readonly _serviceBrand: undefined;
19 > tabGroups: vscode.TabGroups;
20 > }
21 >
22 > export const IExtHostEditorTabs = createDecorator<IExtHostEditorTabs>('IExtHostEditorTabs');
23 >
24 > type AnyTabInput = TextTabInput | TextDiffTabInput | TextMultiDiffTabInput | CustomEditorTabInput | NotebookEditorTabInput | NotebookDiffEditorTabInput | WebviewEditorTabInput | TerminalEditorTabInput | InteractiveWindowInput | ChatEditorTabInput;
25 >
26 > class ExtHostEditorTab {
27 > private _apiObject: vscode.Tab | undefined;
28 > private _dto!: IEditorTabDto;
29 > private _input: AnyTabInput | undefined;
30 > private _parentGroup: ExtHostEditorTabGroup;
31 > private readonly _activeTabIdGetter: () => string;
32 >
33 > constructor(dto: IEditorTabDto, parentGroup: ExtHostEditorTabGroup, activeTabIdGetter: () => string) {
34 this._activeTabIdGetter = activeTabIdGetter;
35 this._parentGroup = parentGroup;
36 this.acceptDtoUpdate(dto);
37 }
39 > get apiObject(): vscode.Tab {
40 if (!this._apiObject) {
41 // Don't want to lose reference to parent `this` in the getters
69 return this._apiObject;
70 }
72 > get tabId(): string {
73 return this._dto.id;
74 }
76 > acceptDtoUpdate(dto: IEditorTabDto) {
77 this._dto = dto;
78 this._input = this._initInput();
79 }
81 > private _initInput() {
82 switch (this._dto.input.kind) {
83 case TabInputKind.TextInput:
107 }
108 }
110 >
111 > class ExtHostEditorTabGroup {
112 >
113 > private _apiObject: vscode.TabGroup | undefined;
114 > private _dto: IEditorTabGroupDto;
115 > private _tabs: ExtHostEditorTab[] = [];
116 > private _activeTabId: string = '';
117 > private _activeGroupIdGetter: () => number | undefined;
118 >
119 > constructor(dto: IEditorTabGroupDto, activeGroupIdGetter: () => number | undefined) {
120 this._dto = dto;
121 this._activeGroupIdGetter = activeGroupIdGetter;
123 this._reconcileTabs(dto);
124 }
126 > get apiObject(): vscode.TabGroup {
127 if (!this._apiObject) {
128 // Don't want to lose reference to parent `this` in the getters
147 return this._apiObject;
148 }
150 > get groupId(): number {
151 return this._dto.groupId;
152 }
154 > get tabs(): ExtHostEditorTab[] {
155 return this._tabs;
156 }
158 > acceptGroupDtoUpdate(dto: IEditorTabGroupDto) {
159 this._dto = dto;
160 }
162 > /**
163 > * Accepts a full group dto during a complete tab-model resync, reusing the
164 > * existing {@link ExtHostEditorTab} instances for tabs that still exist so
165 > * their (and this group's) frozen `apiObject` keeps a stable identity.
166 > * Extensions routinely key `Map`/`WeakMap`/`Set` collections by these
167 > * objects, so recreating them on every resync would break those lookups and
168 > * leak whatever they retain.
169 > */
170 > acceptModelUpdate(dto: IEditorTabGroupDto) {
171 this._dto = dto;
172 this._reconcileTabs(dto);
173 }
175 > private _reconcileTabs(dto: IEditorTabGroupDto) {
176 const existingTabsById = new Map<string, ExtHostEditorTab>();
177 for (const tab of this._tabs) {
192 });
193 }
195 > acceptTabOperation(operation: TabOperation): ExtHostEditorTab {
196 // In the open case we add the tab to the group
197 if (operation.kind === TabModelOperationKind.TAB_OPEN) {
239 return tab;
240 }
242 > // Not a getter since it must be a function to be used as a callback for the tabs
243 > activeTabId(): string {
244 return this._activeTabId;
245 }
247 >
248 > export class ExtHostEditorTabs implements IExtHostEditorTabs {
249 > readonly _serviceBrand: undefined;
250 >
251 > private readonly _proxy: MainThreadEditorTabsShape;
252 > private readonly _onDidChangeTabs = new Emitter<vscode.TabChangeEvent>();
253 > private readonly _onDidChangeTabGroups = new Emitter<vscode.TabGroupChangeEvent>();
254 >
255 > // Have to use ! because this gets initialized via an RPC proxy
256 > private _activeGroupId!: number;
257 >
258 > private _extHostTabGroups: ExtHostEditorTabGroup[] = [];
259 >
260 > private _apiObject: vscode.TabGroups | undefined;
261 >
262 > constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService) {
263 this._proxy = extHostRpc.getProxy(MainContext.MainThreadEditorTabs);
264 }
266 > get tabGroups(): vscode.TabGroups {
267 if (!this._apiObject) {
268 const that = this;
306 return this._apiObject;
307 }
309 > $acceptEditorTabModel(tabGroups: IEditorTabGroupDto[]): void {
310
311 const groupIdsBefore = new Set(this._extHostTabGroups.map(group => group.groupId));
347 this._onDidChangeTabGroups.fire(Object.freeze({ opened, closed, changed }));
348 }
350 > $acceptTabGroupUpdate(groupDto: IEditorTabGroupDto) {
351 const group = this._extHostTabGroups.find(group => group.groupId === groupDto.groupId);
352 if (!group) {
359 this._onDidChangeTabGroups.fire(Object.freeze({ changed: [group.apiObject], opened: [], closed: [] }));
360 }
362 > $acceptTabOperation(operation: TabOperation) {
363 const group = this._extHostTabGroups.find(group => group.groupId === operation.groupId);
364 if (!group) {
393 }
394 }
396 > private _findExtHostTabFromApi(apiTab: vscode.Tab): ExtHostEditorTab | undefined {
397 for (const group of this._extHostTabGroups) {
398 for (const tab of group.tabs) {
404 return;
405 }
407 > private _findExtHostTabGroupFromApi(apiTabGroup: vscode.TabGroup): ExtHostEditorTabGroup | undefined {
408 return this._extHostTabGroups.find(candidate => candidate.apiObject === apiTabGroup);
409 }
411 > private async _closeTabs(tabs: vscode.Tab[], preserveFocus?: boolean): Promise<boolean> {
412 const extHostTabIds: string[] = [];
413 for (const tab of tabs) {
420 return this._proxy.$closeTab(extHostTabIds, preserveFocus);
421 }
423 > private async _closeGroups(groups: vscode.TabGroup[], preserverFoucs?: boolean): Promise<boolean> {
424 const extHostGroupIds: number[] = [];
425 for (const group of groups) {
432 return this._proxy.$closeGroup(extHostGroupIds, preserverFoucs);
433 }
435 >
436 > //#region Utils
437 function isTabGroup(obj: unknown): obj is vscode.TabGroup {
438 const tabGroup = obj as vscode.TabGroup;
src/vs/workbench/api/common/extHostDocumentData.ts 84 introduced LOC · 25 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extHostDocumentData.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { ok } from '../../../base/common/assert.js';
7 > import { Schemas } from '../../../base/common/network.js';
8 > import { regExpLeadsToEndlessLoop } from '../../../base/common/strings.js';
9 > import { URI, UriComponents } from '../../../base/common/uri.js';
10 > import { MirrorTextModel } from '../../../editor/common/model/mirrorTextModel.js';
11 > import { ensureValidWordDefinition, getWordAtText } from '../../../editor/common/core/wordHelper.js';
12 > import type * as vscode from 'vscode';
13 > import { equals } from '../../../base/common/arrays.js';
14 > import { EndOfLine } from './extHostTypes/textEdit.js';
15 > import { Position } from './extHostTypes/position.js';
16 > import { Range } from './extHostTypes/range.js';
17 >
18 > const _languageId2WordDefinition = new Map<string, RegExp>();
19 > export function setWordDefinitionFor(languageId: string, wordDefinition: RegExp | undefined): void {
20 if (!wordDefinition) {
21 _languageId2WordDefinition.delete(languageId);
24 }
25 }
27 function getWordDefinitionFor(languageId: string): RegExp | undefined {
28 return _languageId2WordDefinition.get(languageId);
29 }
31 > export interface IExtHostDocumentSaveDelegate {
32 > $trySaveDocument(uri: UriComponents): Promise<boolean>;
33 > }
34 >
35 > export class ExtHostDocumentData extends MirrorTextModel {
36 >
37 > private _document?: vscode.TextDocument;
38 > private _isDisposed: boolean = false;
39 >
40 > constructor(
41 private readonly _proxy: IExtHostDocumentSaveDelegate,
42 uri: URI, lines: string[], eol: string, versionId: number,
48 super(uri, lines, eol, versionId);
49 }
51 > // eslint-disable-next-line local/code-must-use-super-dispose
52 > override dispose(): void {
53 // we don't really dispose documents but let
54 // extensions still read from them. some
58 this._isDirty = false;
59 }
61 > equalLines(lines: readonly string[]): boolean {
62 return equals(this._lines, lines);
63 }
65 > get document(): vscode.TextDocument {
66 if (!this._document) {
67 const that = this;
92 return Object.freeze(this._document);
93 }
95 > _acceptLanguageId(newLanguageId: string): void {
96 ok(!this._isDisposed);
97 this._languageId = newLanguageId;
98 }
100 > _acceptIsDirty(isDirty: boolean): void {
101 ok(!this._isDisposed);
102 this._isDirty = isDirty;
103 }
105 > _acceptEncoding(encoding: string): void {
106 ok(!this._isDisposed);
107 this._encoding = encoding;
108 }
110 > private _save(): Promise<boolean> {
111 if (this._isDisposed) {
112 return Promise.reject(new Error('Document has been closed'));
114 return this._proxy.$trySaveDocument(this._uri);
115 }
117 > private _getTextInRange(_range: vscode.Range): string {
118 const range = this._validateRange(_range);
119
139 return resultLines.join(lineEnding);
140 }
142 > private _lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine {
143
144 let line: number | undefined;
157 return new ExtHostDocumentLine(line, this._lines[line], line === this._lines.length - 1);
158 }
160 > private _offsetAt(position: vscode.Position): number {
161 position = this._validatePosition(position);
162 this._ensureLineStarts();
163 return this._lineStarts!.getPrefixSum(position.line - 1) + position.character;
164 }
166 > private _positionAt(offset: number): vscode.Position {
167 offset = Math.floor(offset);
168 offset = Math.max(0, offset);
176 return new Position(out.index, Math.min(out.remainder, lineLength));
177 }
179 > // ---- range math
180 >
181 > private _validateRange(range: vscode.Range): vscode.Range {
182 if (this._strictInstanceofChecks) {
183 if (!(range instanceof Range)) {
198 return new Range(start.line, start.character, end.line, end.character);
199 }
201 > private _validatePosition(position: vscode.Position): vscode.Position {
202 if (this._strictInstanceofChecks) {
203 if (!(position instanceof Position)) {
244 return new Position(line, character);
245 }
247 > private _getWordRangeAtPosition(_position: vscode.Position, regexp?: RegExp): vscode.Range | undefined {
248 const position = this._validatePosition(_position);
249
269 return undefined;
270 }
272 >
273 > export class ExtHostDocumentLine implements vscode.TextLine {
274 >
275 > private readonly _line: number;
276 > private readonly _text: string;
277 > private readonly _isLastLine: boolean;
278 >
279 > constructor(line: number, text: string, isLastLine: boolean) {
280 this._line = line;
281 this._text = text;
282 this._isLastLine = isLastLine;
283 }
285 > public get lineNumber(): number {
286 return this._line;
287 }
289 > public get text(): string {
290 return this._text;
291 }
293 > public get range(): Range {
294 return new Range(this._line, 0, this._line, this._text.length);
295 }
297 > public get rangeIncludingLineBreak(): Range {
298 if (this._isLastLine) {
299 return this.range;
301 return new Range(this._line, 0, this._line + 1, 0);
302 }
304 > public get firstNonWhitespaceCharacterIndex(): number {
305 //TODO@api, rename to 'leadingWhitespaceLength'
306 return /^(\s*)/.exec(this._text)![1].length;
307 }
309 > public get isEmptyOrWhitespace(): boolean {
310 return this.firstNonWhitespaceCharacterIndex === this._text.length;
311 }
src/vs/workbench/api/common/extHostDocumentsAndEditors.ts 69 introduced LOC · 11 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extHostDocumentsAndEditors.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as assert from '../../../base/common/assert.js';
7 > import * as vscode from 'vscode';
8 > import { Emitter, Event } from '../../../base/common/event.js';
9 > import { dispose } from '../../../base/common/lifecycle.js';
10 > import { URI } from '../../../base/common/uri.js';
11 > import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
12 > import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, MainContext } from './extHost.protocol.js';
13 > import { ExtHostDocumentData } from './extHostDocumentData.js';
14 > import { IExtHostRpcService } from './extHostRpcService.js';
15 > import { ExtHostTextEditor } from './extHostTextEditor.js';
16 > import * as typeConverters from './extHostTypeConverters.js';
17 > import { ILogService } from '../../../platform/log/common/log.js';
18 > import { ResourceMap } from '../../../base/common/map.js';
19 > import { Schemas } from '../../../base/common/network.js';
20 > import { Iterable } from '../../../base/common/iterator.js';
21 > import { Lazy } from '../../../base/common/lazy.js';
22 >
23 > class Reference<T> {
24 > private _count = 0;
25 > constructor(readonly value: T) { }
26 > ref() {
27 this._count++;
28 }
30 return --this._count === 0;
31 }
33 >
34 > export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape {
35 >
36 > readonly _serviceBrand: undefined;
37 >
38 > private _activeEditorId: string | null = null;
39 >
40 > private readonly _editors = new Map<string, ExtHostTextEditor>();
41 > private readonly _documents = new ResourceMap<Reference<ExtHostDocumentData>>();
42 >
43 > private readonly _onDidAddDocuments = new Emitter<readonly ExtHostDocumentData[]>();
44 > private readonly _onDidRemoveDocuments = new Emitter<readonly ExtHostDocumentData[]>();
45 > private readonly _onDidChangeVisibleTextEditors = new Emitter<readonly vscode.TextEditor[]>();
46 > private readonly _onDidChangeActiveTextEditor = new Emitter<vscode.TextEditor | undefined>();
47 >
48 > readonly onDidAddDocuments: Event<readonly ExtHostDocumentData[]> = this._onDidAddDocuments.event;
49 > readonly onDidRemoveDocuments: Event<readonly ExtHostDocumentData[]> = this._onDidRemoveDocuments.event;
50 > readonly onDidChangeVisibleTextEditors: Event<readonly vscode.TextEditor[]> = this._onDidChangeVisibleTextEditors.event;
51 > readonly onDidChangeActiveTextEditor: Event<vscode.TextEditor | undefined> = this._onDidChangeActiveTextEditor.event;
52 >
53 > constructor(
54 @IExtHostRpcService private readonly _extHostRpc: IExtHostRpcService,
55 @ILogService private readonly _logService: ILogService
56 ) { }
58 > $acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void {
59 this.acceptDocumentsAndEditorsDelta(delta);
60 }
62 > acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void {
63
64 const removedDocuments: ExtHostDocumentData[] = [];
162 }
163 }
165 > getDocument(uri: URI): ExtHostDocumentData | undefined {
166 return this._documents.get(uri)?.value;
167 }
169 > allDocuments(): Iterable<ExtHostDocumentData> {
170 return Iterable.map(this._documents.values(), ref => ref.value);
171 }
173 > getEditor(id: string): ExtHostTextEditor | undefined {
174 return this._editors.get(id);
175 }
177 > activeEditor(): vscode.TextEditor | undefined;
178 > activeEditor(internal: true): ExtHostTextEditor | undefined;
179 > activeEditor(internal?: true): vscode.TextEditor | ExtHostTextEditor | undefined {
180 if (!this._activeEditorId) {
181 return undefined;
188 }
189 }
191 > allEditors(): ExtHostTextEditor[] {
192 return [...this._editors.values()];
193 }
195 >
196 > export interface IExtHostDocumentsAndEditors extends ExtHostDocumentsAndEditors { }
197 > export const IExtHostDocumentsAndEditors = createDecorator<IExtHostDocumentsAndEditors>('IExtHostDocumentsAndEditors');
src/vs/workbench/api/common/extHostVariableResolverService.ts 66 introduced LOC · 5 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extHostVariableResolverService.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { Lazy } from '../../../base/common/lazy.js';
7 > import { Disposable } from '../../../base/common/lifecycle.js';
8 > import * as path from '../../../base/common/path.js';
9 > import * as process from '../../../base/common/process.js';
10 > import { URI } from '../../../base/common/uri.js';
11 > import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
12 > import { IExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors.js';
13 > import { IExtHostEditorTabs } from './extHostEditorTabs.js';
14 > import { IExtHostExtensionService } from './extHostExtensionService.js';
15 > import { CustomEditorTabInput, NotebookDiffEditorTabInput, NotebookEditorTabInput, TextDiffTabInput, TextTabInput } from './extHostTypes.js';
16 > import { IExtHostWorkspace } from './extHostWorkspace.js';
17 > import { IConfigurationResolverService } from '../../services/configurationResolver/common/configurationResolver.js';
18 > import { AbstractVariableResolverService } from '../../services/configurationResolver/common/variableResolver.js';
19 > import * as vscode from 'vscode';
20 > import { ExtHostConfigProvider, IExtHostConfiguration } from './extHostConfiguration.js';
21 >
22 > export interface IExtHostVariableResolverProvider {
23 > readonly _serviceBrand: undefined;
24 > getResolver(): Promise<IConfigurationResolverService>;
25 > }
26 >
27 > export const IExtHostVariableResolverProvider = createDecorator<IExtHostVariableResolverProvider>('IExtHostVariableResolverProvider');
28 >
29 > interface DynamicContext {
30 > folders: vscode.WorkspaceFolder[];
31 > }
32 >
33 > class ExtHostVariableResolverService extends AbstractVariableResolverService {
34 >
35 > constructor(
36 extensionService: IExtHostExtensionService,
37 workspaceService: IExtHostWorkspace,
132 }, undefined, homeDir ? Promise.resolve(homeDir) : undefined, Promise.resolve(process.env));
133 }
135 >
136 > export class ExtHostVariableResolverProviderService extends Disposable implements IExtHostVariableResolverProvider {
137 > declare readonly _serviceBrand: undefined;
138 >
139 > private _resolver = new Lazy(async () => {
140 > const configProvider = await this.configurationService.getConfigProvider();
141 > const folders = await this.workspaceService.getWorkspaceFolders2() || [];
142 >
143 > const dynamic: DynamicContext = { folders };
144 > this._register(this.workspaceService.onDidChangeWorkspace(async e => {
145 > dynamic.folders = await this.workspaceService.getWorkspaceFolders2() || [];
146 > }));
147 >
148 > return new ExtHostVariableResolverService(
149 > this.extensionService,
150 > this.workspaceService,
151 > this.editorService,
152 > this.editorTabs,
153 > configProvider,
154 > dynamic,
155 > this.homeDir(),
156 > );
157 > });
158 >
159 > constructor(
160 @IExtHostExtensionService private readonly extensionService: IExtHostExtensionService,
161 @IExtHostWorkspace private readonly workspaceService: IExtHostWorkspace,
src/vs/workbench/services/configurationResolver/common/variableResolver.ts 66 introduced LOC · 11 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- variableResolver.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { IStringDictionary } from '../../../../base/common/collections.js';
7 > import { normalizeDriveLetter } from '../../../../base/common/labels.js';
8 > import * as paths from '../../../../base/common/path.js';
9 > import { IProcessEnvironment, isWindows } from '../../../../base/common/platform.js';
10 > import * as process from '../../../../base/common/process.js';
11 > import * as types from '../../../../base/common/types.js';
12 > import { URI as uri } from '../../../../base/common/uri.js';
13 > import { localize } from '../../../../nls.js';
14 > import { ILabelService } from '../../../../platform/label/common/label.js';
15 > import { IWorkspaceFolderData } from '../../../../platform/workspace/common/workspace.js';
16 > import { allVariableKinds, IConfigurationResolverService, VariableError, VariableKind } from './configurationResolver.js';
17 > import { ConfigurationResolverExpression, IResolvedValue, Replacement } from './configurationResolverExpression.js';
18 >
19 > interface IVariableResolveContext {
20 > getFolderUri(folderName: string): uri | undefined;
21 > getWorkspaceFolderCount(): number;
22 > getConfigurationValue(folderUri: uri | undefined, section: string): string | undefined;
23 > getAppRoot(): string | undefined;
24 > getExecPath(): string | undefined;
25 > getFilePath(): string | undefined;
26 > getWorkspaceFolderPathForFile?(): string | undefined;
27 > getSelectedText(): string | undefined;
28 > getLineNumber(): string | undefined;
29 > getColumnNumber(): string | undefined;
30 > getExtension(id: string): Promise<{ readonly extensionLocation: uri } | undefined>;
31 > }
32 >
33 > type Environment = { env: IProcessEnvironment | undefined; userHome: string | undefined };
34 >
35 > export abstract class AbstractVariableResolverService implements IConfigurationResolverService {
36 >
37 > declare readonly _serviceBrand: undefined;
38 >
39 > private _context: IVariableResolveContext;
40 > private _labelService?: ILabelService;
41 > private _envVariablesPromise?: Promise<IProcessEnvironment>;
42 > private _userHomePromise?: Promise<string>;
43 > protected _contributedVariables: Map<string, () => Promise<string | undefined>> = new Map();
44 >
45 > public readonly resolvableVariables = new Set<string>(allVariableKinds);
46 >
47 > constructor(_context: IVariableResolveContext, _labelService?: ILabelService, _userHomePromise?: Promise<string>, _envVariablesPromise?: Promise<IProcessEnvironment>) {
48 this._context = _context;
49 this._labelService = _labelService;
55 }
56 }
58 > private prepareEnv(envVariables: IProcessEnvironment): IProcessEnvironment {
59 // windows env variables are case insensitive
60 if (isWindows) {
67 return envVariables;
68 }
70 > public async resolveWithEnvironment(environment: IProcessEnvironment, folder: IWorkspaceFolderData | undefined, value: string): Promise<string> {
71 const expr = ConfigurationResolverExpression.parse(value);
72
80 return expr.toObject();
81 }
83 > public async resolveAsync<T>(folder: IWorkspaceFolderData | undefined, config: T): Promise<T extends ConfigurationResolverExpression<infer R> ? R : T> {
84 const expr = ConfigurationResolverExpression.parse(config);
85
93 return expr.toObject() as (T extends ConfigurationResolverExpression<infer R> ? R : T);
94 }
96 > public resolveWithInteractionReplace(folder: IWorkspaceFolderData | undefined, config: unknown): Promise<unknown> {
97 throw new Error('resolveWithInteractionReplace not implemented.');
98 }
100 > public resolveWithInteraction(folder: IWorkspaceFolderData | undefined, config: unknown): Promise<Map<string, string> | undefined> {
101 throw new Error('resolveWithInteraction not implemented.');
102 }
104 > public contributeVariable(variable: string, resolution: () => Promise<string | undefined>): void {
105 if (this._contributedVariables.has(variable)) {
106 throw new Error('Variable ' + variable + ' is contributed twice.');
110 }
111 }
113 > private fsPath(displayUri: uri): string {
114 return this._labelService ? this._labelService.getUriLabel(displayUri, { noPrefix: true }) : displayUri.fsPath;
115 }
117 > protected async evaluateSingleVariable(replacement: Replacement, folderUri: uri | undefined, processEnvironment?: IProcessEnvironment, commandValueMapping?: IStringDictionary<IResolvedValue>): Promise<IResolvedValue | string | undefined> {
118
119
332 }
333 }
335 > private resolveFromMap(variableKind: VariableKind, match: string, argument: string | undefined, commandValueMapping: IStringDictionary<IResolvedValue> | undefined, prefix: string | undefined): string {
336 if (argument && commandValueMapping) {
337 const v = (prefix === undefined) ? commandValueMapping[argument] : commandValueMapping[prefix + ':' + argument];