Merge branch 'main' into dev/mjbvz/comprehensive-raven

This commit is contained in:
Matt Bierner
2025-10-06 15:49:31 -07:00
committed by GitHub
40 changed files with 346 additions and 1646 deletions
@@ -250,7 +250,7 @@ export async function activate(context: vscode.ExtensionContext) {
const machineId = await vscode.env.machineId;
const remoteAuthority = vscode.env.remoteName;
context.subscriptions.push(vscode.window.registerTerminalCompletionProvider('terminal-suggest', {
context.subscriptions.push(vscode.window.registerTerminalCompletionProvider({
async provideTerminalCompletions(terminal: vscode.Terminal, terminalContext: vscode.TerminalCompletionContext, token: vscode.CancellationToken): Promise<vscode.TerminalCompletionItem[] | vscode.TerminalCompletionList | undefined> {
currentTerminalEnv = terminal.shellIntegration?.env?.value ?? process.env;
if (token.isCancellationRequested) {
+2 -2
View File
@@ -685,7 +685,7 @@ export abstract class ReferenceCollection<T> {
private readonly references: Map<string, { readonly object: T; counter: number }> = new Map();
acquire(key: string, ...args: any[]): IReference<T> {
acquire(key: string, ...args: unknown[]): IReference<T> {
let reference = this.references.get(key);
if (!reference) {
@@ -706,7 +706,7 @@ export abstract class ReferenceCollection<T> {
return { object, dispose };
}
protected abstract createReferencedObject(key: string, ...args: any[]): T;
protected abstract createReferencedObject(key: string, ...args: unknown[]): T;
protected abstract destroyReferencedObject(key: string, object: T): void;
}
+137 -25
View File
@@ -259,6 +259,108 @@ export interface IAuthorizationServerMetadata {
code_challenge_methods_supported?: string[];
}
/**
* Request for the dynamic client registration endpoint.
* @see https://datatracker.ietf.org/doc/html/rfc7591#section-2
*/
export interface IAuthorizationDynamicClientRegistrationRequest {
/**
* OPTIONAL. Array of redirection URI strings for use in redirect-based flows
* such as the authorization code and implicit flows.
*/
redirect_uris?: string[];
/**
* OPTIONAL. String indicator of the requested authentication method for the token endpoint.
* Values: "none", "client_secret_post", "client_secret_basic".
* Default is "client_secret_basic".
*/
token_endpoint_auth_method?: string;
/**
* OPTIONAL. Array of OAuth 2.0 grant type strings that the client can use at the token endpoint.
* Default is ["authorization_code"].
*/
grant_types?: string[];
/**
* OPTIONAL. Array of the OAuth 2.0 response type strings that the client can use at the authorization endpoint.
* Default is ["code"].
*/
response_types?: string[];
/**
* OPTIONAL. Human-readable string name of the client to be presented to the end-user during authorization.
*/
client_name?: string;
/**
* OPTIONAL. URL string of a web page providing information about the client.
*/
client_uri?: string;
/**
* OPTIONAL. URL string that references a logo for the client.
*/
logo_uri?: string;
/**
* OPTIONAL. String containing a space-separated list of scope values that the client can use when requesting access tokens.
*/
scope?: string;
/**
* OPTIONAL. Array of strings representing ways to contact people responsible for this client, typically email addresses.
*/
contacts?: string[];
/**
* OPTIONAL. URL string that points to a human-readable terms of service document for the client.
*/
tos_uri?: string;
/**
* OPTIONAL. URL string that points to a human-readable privacy policy document.
*/
policy_uri?: string;
/**
* OPTIONAL. URL string referencing the client's JSON Web Key (JWK) Set document.
*/
jwks_uri?: string;
/**
* OPTIONAL. Client's JSON Web Key Set document value.
*/
jwks?: object;
/**
* OPTIONAL. A unique identifier string assigned by the client developer or software publisher.
*/
software_id?: string;
/**
* OPTIONAL. A version identifier string for the client software.
*/
software_version?: string;
/**
* OPTIONAL. A software statement containing client metadata values about the client software as claims.
*/
software_statement?: string;
/**
* OPTIONAL. Application type. Usually "native" for OAuth clients.
* https://openid.net/specs/openid-connect-registration-1_0.html
*/
application_type?: 'native' | 'web' | string;
/**
* OPTIONAL. Additional metadata fields as defined by extensions.
*/
[key: string]: unknown;
}
/**
* Response from the dynamic client registration endpoint.
*/
@@ -749,33 +851,35 @@ export async function fetchDynamicRegistration(serverMetadata: IAuthorizationSer
if (!serverMetadata.registration_endpoint) {
throw new Error('Server does not support dynamic registration');
}
const requestBody: IAuthorizationDynamicClientRegistrationRequest = {
client_name: clientName,
client_uri: 'https://code.visualstudio.com',
grant_types: serverMetadata.grant_types_supported
? serverMetadata.grant_types_supported.filter(gt => grantTypesSupported.includes(gt))
: grantTypesSupported,
response_types: ['code'],
redirect_uris: [
'https://insiders.vscode.dev/redirect',
'https://vscode.dev/redirect',
'http://127.0.0.1/',
// Added these for any server that might do
// only exact match on the redirect URI even
// though the spec says it should not care
// about the port.
`http://127.0.0.1:${DEFAULT_AUTH_FLOW_PORT}/`
],
scope: scopes?.join(AUTH_SCOPE_SEPARATOR),
token_endpoint_auth_method: 'none',
application_type: 'native'
};
const response = await fetch(serverMetadata.registration_endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
client_name: clientName,
client_uri: 'https://code.visualstudio.com',
grant_types: serverMetadata.grant_types_supported
? serverMetadata.grant_types_supported.filter(gt => grantTypesSupported.includes(gt))
: grantTypesSupported,
response_types: ['code'],
redirect_uris: [
'https://insiders.vscode.dev/redirect',
'https://vscode.dev/redirect',
'http://127.0.0.1/',
// Added these for any server that might do
// only exact match on the redirect URI even
// though the spec says it should not care
// about the port.
`http://127.0.0.1:${DEFAULT_AUTH_FLOW_PORT}/`
],
scope: scopes?.join(AUTH_SCOPE_SEPARATOR),
token_endpoint_auth_method: 'none',
// https://openid.net/specs/openid-connect-registration-1_0.html
application_type: 'native'
})
body: JSON.stringify(requestBody)
});
if (!response.ok) {
@@ -936,17 +1040,25 @@ export function getClaimsFromJWT(token: string): IAuthorizationJWTClaims {
* Checks if two scope lists are equivalent, regardless of order.
* This is useful for comparing OAuth scopes where the order should not matter.
*
* @param scopes1 First list of scopes to compare
* @param scopes2 Second list of scopes to compare
* @param scopes1 First list of scopes to compare (can be undefined)
* @param scopes2 Second list of scopes to compare (can be undefined)
* @returns true if the scope lists contain the same scopes (order-independent), false otherwise
*
* @example
* ```typescript
* scopesMatch(['read', 'write'], ['write', 'read']) // Returns: true
* scopesMatch(['read'], ['write']) // Returns: false
* scopesMatch(undefined, undefined) // Returns: true
* scopesMatch(['read'], undefined) // Returns: false
* ```
*/
export function scopesMatch(scopes1: readonly string[], scopes2: readonly string[]): boolean {
export function scopesMatch(scopes1: readonly string[] | undefined, scopes2: readonly string[] | undefined): boolean {
if (scopes1 === scopes2) {
return true;
}
if (!scopes1 || !scopes2) {
return false;
}
if (scopes1.length !== scopes2.length) {
return false;
}
+12
View File
@@ -310,6 +310,18 @@ suite('OAuth', () => {
const scopes2 = ['scope2', 'scope1', 'scope1'];
assert.strictEqual(scopesMatch(scopes1, scopes2), true);
});
test('scopesMatch should handle undefined values', () => {
assert.strictEqual(scopesMatch(undefined, undefined), true);
assert.strictEqual(scopesMatch(['read'], undefined), false);
assert.strictEqual(scopesMatch(undefined, ['write']), false);
});
test('scopesMatch should handle mixed undefined and empty arrays', () => {
assert.strictEqual(scopesMatch([], undefined), false);
assert.strictEqual(scopesMatch(undefined, []), false);
assert.strictEqual(scopesMatch([], []), true);
});
});
suite('Utility Functions', () => {
@@ -595,7 +595,7 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable
}
private async _updateRenameCandidates(candidates: ProviderResult<NewSymbolName[]>[], currentName: string, token: CancellationToken) {
const trace = (...args: any[]) => this._trace('_updateRenameCandidates', ...args);
const trace = (...args: unknown[]) => this._trace('_updateRenameCandidates', ...args);
trace('start');
const namesListResults = await raceCancellation(Promise.allSettled(candidates), token);
@@ -335,7 +335,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon
);
const contextMenuGroupId = _descriptor.contextMenuGroupId || null;
const contextMenuOrder = _descriptor.contextMenuOrder || 0;
const run = (_accessor?: ServicesAccessor, ...args: any[]): Promise<void> => {
const run = (_accessor?: ServicesAccessor, ...args: unknown[]): Promise<void> => {
return Promise.resolve(_descriptor.run(this, ...args));
};
@@ -137,7 +137,7 @@ export function addEditorAction(descriptor: IActionDescriptor): IDisposable {
}
const precondition = ContextKeyExpr.deserialize(descriptor.precondition);
const run = (accessor: ServicesAccessor, ...args: any[]): void | Promise<void> => {
const run = (accessor: ServicesAccessor, ...args: unknown[]): void | Promise<void> => {
return EditorCommand.runEditorCommand(accessor, args, precondition, (accessor, editor, args) => Promise.resolve(descriptor.run(editor, ...args)));
};
@@ -68,7 +68,7 @@ class MonacoWebWorkerImpl<T extends object> extends EditorWorkerClient implement
if (typeof prop !== 'string') {
throw new Error(`Not supported`);
}
return (...args: any[]) => {
return (...args: unknown[]) => {
return proxy.$fmr(prop, args);
};
}
+41 -41
View File
@@ -45,11 +45,11 @@ export interface ILogger extends IDisposable {
getLevel(): LogLevel;
setLevel(level: LogLevel): void;
trace(message: string, ...args: any[]): void;
debug(message: string, ...args: any[]): void;
info(message: string, ...args: any[]): void;
warn(message: string, ...args: any[]): void;
error(message: string | Error, ...args: any[]): void;
trace(message: string, ...args: unknown[]): void;
debug(message: string, ...args: unknown[]): void;
info(message: string, ...args: unknown[]): void;
warn(message: string, ...args: unknown[]): void;
error(message: string | Error, ...args: unknown[]): void;
/**
* An operation to flush the contents. Can be synchronous.
@@ -281,11 +281,11 @@ export abstract class AbstractLogger extends Disposable implements ILogger {
return this.checkLogLevel(level);
}
abstract trace(message: string, ...args: any[]): void;
abstract debug(message: string, ...args: any[]): void;
abstract info(message: string, ...args: any[]): void;
abstract warn(message: string, ...args: any[]): void;
abstract error(message: string | Error, ...args: any[]): void;
abstract trace(message: string, ...args: unknown[]): void;
abstract debug(message: string, ...args: unknown[]): void;
abstract info(message: string, ...args: unknown[]): void;
abstract warn(message: string, ...args: unknown[]): void;
abstract error(message: string | Error, ...args: unknown[]): void;
abstract flush(): void;
}
@@ -299,31 +299,31 @@ export abstract class AbstractMessageLogger extends AbstractLogger implements IL
return this.logAlways || super.checkLogLevel(level);
}
trace(message: string, ...args: any[]): void {
trace(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Trace)) {
this.log(LogLevel.Trace, format([message, ...args], true));
}
}
debug(message: string, ...args: any[]): void {
debug(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Debug)) {
this.log(LogLevel.Debug, format([message, ...args]));
}
}
info(message: string, ...args: any[]): void {
info(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Info)) {
this.log(LogLevel.Info, format([message, ...args]));
}
}
warn(message: string, ...args: any[]): void {
warn(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Warning)) {
this.log(LogLevel.Warning, format([message, ...args]));
}
}
error(message: string | Error, ...args: any[]): void {
error(message: string | Error, ...args: unknown[]): void {
if (this.canLog(LogLevel.Error)) {
if (message instanceof Error) {
const array = Array.prototype.slice.call(arguments) as any[];
@@ -351,7 +351,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger {
this.useColors = !isWindows;
}
trace(message: string, ...args: any[]): void {
trace(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Trace)) {
if (this.useColors) {
console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args);
@@ -361,7 +361,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger {
}
}
debug(message: string, ...args: any[]): void {
debug(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Debug)) {
if (this.useColors) {
console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args);
@@ -371,7 +371,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger {
}
}
info(message: string, ...args: any[]): void {
info(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Info)) {
if (this.useColors) {
console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args);
@@ -381,7 +381,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger {
}
}
warn(message: string | Error, ...args: any[]): void {
warn(message: string | Error, ...args: unknown[]): void {
if (this.canLog(LogLevel.Warning)) {
if (this.useColors) {
console.warn(`\x1b[93m[main ${now()}]\x1b[0m`, message, ...args);
@@ -391,7 +391,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger {
}
}
error(message: string, ...args: any[]): void {
error(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Error)) {
if (this.useColors) {
console.error(`\x1b[91m[main ${now()}]\x1b[0m`, message, ...args);
@@ -414,7 +414,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger {
this.setLevel(logLevel);
}
trace(message: string, ...args: any[]): void {
trace(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Trace)) {
if (this.useColors) {
console.log('%cTRACE', 'color: #888', message, ...args);
@@ -424,7 +424,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger {
}
}
debug(message: string, ...args: any[]): void {
debug(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Debug)) {
if (this.useColors) {
console.log('%cDEBUG', 'background: #eee; color: #888', message, ...args);
@@ -434,7 +434,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger {
}
}
info(message: string, ...args: any[]): void {
info(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Info)) {
if (this.useColors) {
console.log('%c INFO', 'color: #33f', message, ...args);
@@ -444,7 +444,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger {
}
}
warn(message: string | Error, ...args: any[]): void {
warn(message: string | Error, ...args: unknown[]): void {
if (this.canLog(LogLevel.Warning)) {
if (this.useColors) {
console.warn('%c WARN', 'color: #993', message, ...args);
@@ -454,7 +454,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger {
}
}
error(message: string, ...args: any[]): void {
error(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Error)) {
if (this.useColors) {
console.error('%c ERR', 'color: #f33', message, ...args);
@@ -477,31 +477,31 @@ export class AdapterLogger extends AbstractLogger implements ILogger {
this.setLevel(logLevel);
}
trace(message: string, ...args: any[]): void {
trace(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Trace)) {
this.adapter.log(LogLevel.Trace, [this.extractMessage(message), ...args]);
}
}
debug(message: string, ...args: any[]): void {
debug(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Debug)) {
this.adapter.log(LogLevel.Debug, [this.extractMessage(message), ...args]);
}
}
info(message: string, ...args: any[]): void {
info(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Info)) {
this.adapter.log(LogLevel.Info, [this.extractMessage(message), ...args]);
}
}
warn(message: string | Error, ...args: any[]): void {
warn(message: string | Error, ...args: unknown[]): void {
if (this.canLog(LogLevel.Warning)) {
this.adapter.log(LogLevel.Warning, [this.extractMessage(message), ...args]);
}
}
error(message: string | Error, ...args: any[]): void {
error(message: string | Error, ...args: unknown[]): void {
if (this.canLog(LogLevel.Error)) {
this.adapter.log(LogLevel.Error, [this.extractMessage(message), ...args]);
}
@@ -536,31 +536,31 @@ export class MultiplexLogger extends AbstractLogger implements ILogger {
super.setLevel(level);
}
trace(message: string, ...args: any[]): void {
trace(message: string, ...args: unknown[]): void {
for (const logger of this.loggers) {
logger.trace(message, ...args);
}
}
debug(message: string, ...args: any[]): void {
debug(message: string, ...args: unknown[]): void {
for (const logger of this.loggers) {
logger.debug(message, ...args);
}
}
info(message: string, ...args: any[]): void {
info(message: string, ...args: unknown[]): void {
for (const logger of this.loggers) {
logger.info(message, ...args);
}
}
warn(message: string, ...args: any[]): void {
warn(message: string, ...args: unknown[]): void {
for (const logger of this.loggers) {
logger.warn(message, ...args);
}
}
error(message: string | Error, ...args: any[]): void {
error(message: string | Error, ...args: unknown[]): void {
for (const logger of this.loggers) {
logger.error(message, ...args);
}
@@ -740,12 +740,12 @@ export class NullLogger implements ILogger {
readonly onDidChangeLogLevel: Event<LogLevel> = new Emitter<LogLevel>().event;
setLevel(level: LogLevel): void { }
getLevel(): LogLevel { return LogLevel.Info; }
trace(message: string, ...args: any[]): void { }
debug(message: string, ...args: any[]): void { }
info(message: string, ...args: any[]): void { }
warn(message: string, ...args: any[]): void { }
error(message: string | Error, ...args: any[]): void { }
critical(message: string | Error, ...args: any[]): void { }
trace(message: string, ...args: unknown[]): void { }
debug(message: string, ...args: unknown[]): void { }
info(message: string, ...args: unknown[]): void { }
warn(message: string, ...args: unknown[]): void { }
error(message: string | Error, ...args: unknown[]): void { }
critical(message: string | Error, ...args: unknown[]): void { }
dispose(): void { }
flush(): void { }
}
+5 -5
View File
@@ -31,23 +31,23 @@ export class LogService extends Disposable implements ILogService {
return this.logger.getLevel();
}
trace(message: string, ...args: any[]): void {
trace(message: string, ...args: unknown[]): void {
this.logger.trace(message, ...args);
}
debug(message: string, ...args: any[]): void {
debug(message: string, ...args: unknown[]): void {
this.logger.debug(message, ...args);
}
info(message: string, ...args: any[]): void {
info(message: string, ...args: unknown[]): void {
this.logger.info(message, ...args);
}
warn(message: string, ...args: any[]): void {
warn(message: string, ...args: unknown[]): void {
this.logger.warn(message, ...args);
}
error(message: string | Error, ...args: any[]): void {
error(message: string | Error, ...args: unknown[]): void {
this.logger.error(message, ...args);
}
@@ -20,31 +20,31 @@ class TestTelemetryLogger extends AbstractLogger implements ILogger {
this.setLevel(logLevel);
}
trace(message: string, ...args: any[]): void {
trace(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Trace)) {
this.logs.push(message + JSON.stringify(args));
}
}
debug(message: string, ...args: any[]): void {
debug(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Debug)) {
this.logs.push(message);
}
}
info(message: string, ...args: any[]): void {
info(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Info)) {
this.logs.push(message);
}
}
warn(message: string | Error, ...args: any[]): void {
warn(message: string | Error, ...args: unknown[]): void {
if (this.canLog(LogLevel.Warning)) {
this.logs.push(message.toString());
}
}
error(message: string, ...args: any[]): void {
error(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Error)) {
this.logs.push(message);
}
@@ -38,11 +38,11 @@ export class TerminalLogService extends Disposable implements ITerminalLogServic
setLevel(level: LogLevel): void { this._logger.setLevel(level); }
flush(): void { this._logger.flush(); }
trace(message: string, ...args: any[]): void { this._logger.trace(this._formatMessage(message), args); }
debug(message: string, ...args: any[]): void { this._logger.debug(this._formatMessage(message), args); }
info(message: string, ...args: any[]): void { this._logger.info(this._formatMessage(message), args); }
warn(message: string, ...args: any[]): void { this._logger.warn(this._formatMessage(message), args); }
error(message: string | Error, ...args: any[]): void {
trace(message: string, ...args: unknown[]): void { this._logger.trace(this._formatMessage(message), args); }
debug(message: string, ...args: unknown[]): void { this._logger.debug(this._formatMessage(message), args); }
info(message: string, ...args: unknown[]): void { this._logger.info(this._formatMessage(message), args); }
warn(message: string, ...args: unknown[]): void { this._logger.warn(this._formatMessage(message), args); }
error(message: string | Error, ...args: unknown[]): void {
if (message instanceof Error) {
this._logger.error(this._formatMessage(''), message, args);
return;
@@ -22,23 +22,23 @@ export class UserDataSyncLogService extends AbstractLogger implements IUserDataS
this.logger = this._register(loggerService.createLogger(joinPath(environmentService.logsHome, `${USER_DATA_SYNC_LOG_ID}.log`), { id: USER_DATA_SYNC_LOG_ID, name: localize('userDataSyncLog', "Settings Sync") }));
}
trace(message: string, ...args: any[]): void {
trace(message: string, ...args: unknown[]): void {
this.logger.trace(message, ...args);
}
debug(message: string, ...args: any[]): void {
debug(message: string, ...args: unknown[]): void {
this.logger.debug(message, ...args);
}
info(message: string, ...args: any[]): void {
info(message: string, ...args: unknown[]): void {
this.logger.info(message, ...args);
}
warn(message: string, ...args: any[]): void {
warn(message: string, ...args: unknown[]): void {
this.logger.warn(message, ...args);
}
error(message: string | Error, ...args: any[]): void {
error(message: string | Error, ...args: unknown[]): void {
this.logger.error(message, ...args);
}
+5 -5
View File
@@ -304,7 +304,7 @@ class ServerLogger extends AbstractLogger {
this.useColors = Boolean(process.stdout.isTTY);
}
trace(message: string, ...args: any[]): void {
trace(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Trace)) {
if (this.useColors) {
console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args);
@@ -314,7 +314,7 @@ class ServerLogger extends AbstractLogger {
}
}
debug(message: string, ...args: any[]): void {
debug(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Debug)) {
if (this.useColors) {
console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args);
@@ -324,7 +324,7 @@ class ServerLogger extends AbstractLogger {
}
}
info(message: string, ...args: any[]): void {
info(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Info)) {
if (this.useColors) {
console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args);
@@ -334,7 +334,7 @@ class ServerLogger extends AbstractLogger {
}
}
warn(message: string | Error, ...args: any[]): void {
warn(message: string | Error, ...args: unknown[]): void {
if (this.canLog(LogLevel.Warning)) {
if (this.useColors) {
console.warn(`\x1b[93m[${now()}]\x1b[0m`, message, ...args);
@@ -344,7 +344,7 @@ class ServerLogger extends AbstractLogger {
}
}
error(message: string, ...args: any[]): void {
error(message: string, ...args: unknown[]): void {
if (this.canLog(LogLevel.Error)) {
if (this.useColors) {
console.error(`\x1b[91m[${now()}]\x1b[0m`, message, ...args);
@@ -178,14 +178,13 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape {
this._servers.get(id)?.pushMessage(message);
}
async $getTokenFromServerMetadata(id: number, authServerComponents: UriComponents, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined, errorOnUserInteraction?: boolean): Promise<string | undefined> {
async $getTokenFromServerMetadata(id: number, authServerComponents: UriComponents, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined, scopes: string[] | undefined, errorOnUserInteraction?: boolean): Promise<string | undefined> {
const server = this._serverDefinitions.get(id);
if (!server) {
return undefined;
}
const authorizationServer = URI.revive(authServerComponents);
const scopesSupported = resourceMetadata?.scopes_supported || serverMetadata.scopes_supported || [];
const resolvedScopes = scopes ?? resourceMetadata?.scopes_supported ?? serverMetadata.scopes_supported ?? [];
let providerId = await this._authenticationService.getOrActivateProviderIdForServer(authorizationServer);
if (!providerId) {
const provider = await this._authenticationService.createDynamicAuthenticationProvider(authorizationServer, serverMetadata, resourceMetadata);
@@ -194,7 +193,7 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape {
}
providerId = provider.id;
}
const sessions = await this._authenticationService.getSessions(providerId, scopesSupported, { authorizationServer: authorizationServer }, true);
const sessions = await this._authenticationService.getSessions(providerId, resolvedScopes, { authorizationServer: authorizationServer }, true);
const accountNamePreference = this.authenticationMcpServersService.getAccountPreference(server.id, providerId);
let matchingAccountPreferenceSession: AuthenticationSession | undefined;
if (accountNamePreference) {
@@ -205,12 +204,12 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape {
if (sessions.length) {
// If we have an existing session preference, use that. If not, we'll return any valid session at the end of this function.
if (matchingAccountPreferenceSession && this.authenticationMCPServerAccessService.isAccessAllowed(providerId, matchingAccountPreferenceSession.account.label, server.id)) {
this.authenticationMCPServerUsageService.addAccountUsage(providerId, matchingAccountPreferenceSession.account.label, scopesSupported, server.id, server.label);
this.authenticationMCPServerUsageService.addAccountUsage(providerId, matchingAccountPreferenceSession.account.label, resolvedScopes, server.id, server.label);
return matchingAccountPreferenceSession.accessToken;
}
// If we only have one account for a single auth provider, lets just check if it's allowed and return it if it is.
if (!provider.supportsMultipleAccounts && this.authenticationMCPServerAccessService.isAccessAllowed(providerId, sessions[0].account.label, server.id)) {
this.authenticationMCPServerUsageService.addAccountUsage(providerId, sessions[0].account.label, scopesSupported, server.id, server.label);
this.authenticationMCPServerUsageService.addAccountUsage(providerId, sessions[0].account.label, resolvedScopes, server.id, server.label);
return sessions[0].accessToken;
}
}
@@ -229,7 +228,7 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape {
throw new UserInteractionRequiredError('authentication');
}
session = provider.supportsMultipleAccounts
? await this.authenticationMcpServersService.selectSession(providerId, server.id, server.label, scopesSupported, sessions)
? await this.authenticationMcpServersService.selectSession(providerId, server.id, server.label, resolvedScopes, sessions)
: sessions[0];
}
else {
@@ -240,7 +239,7 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape {
do {
session = await this._authenticationService.createSession(
providerId,
scopesSupported,
resolvedScopes,
{
activateImmediate: true,
account: accountToCreate,
@@ -255,7 +254,7 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape {
this.authenticationMCPServerAccessService.updateAllowedMcpServers(providerId, session.account.label, [{ id: server.id, name: server.label, allowed: true }]);
this.authenticationMcpServersService.updateAccountPreference(server.id, providerId, session.account);
this.authenticationMCPServerUsageService.addAccountUsage(providerId, session.account.label, scopesSupported, server.id, server.label);
this.authenticationMCPServerUsageService.addAccountUsage(providerId, session.account.label, resolvedScopes, server.id, server.label);
return session.accessToken;
}
@@ -877,9 +877,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerTerminalProfileProvider(id: string, provider: vscode.TerminalProfileProvider): vscode.Disposable {
return extHostTerminalService.registerProfileProvider(extension, id, provider);
},
registerTerminalCompletionProvider(id: string, provider: vscode.TerminalCompletionProvider<vscode.TerminalCompletionItem>, ...triggerCharacters: string[]): vscode.Disposable {
registerTerminalCompletionProvider(provider: vscode.TerminalCompletionProvider<vscode.TerminalCompletionItem>, ...triggerCharacters: string[]): vscode.Disposable {
checkProposedApiEnabled(extension, 'terminalCompletionProvider');
return extHostTerminalService.registerTerminalCompletionProvider(extension, id, provider, ...triggerCharacters);
return extHostTerminalService.registerTerminalCompletionProvider(extension, provider, ...triggerCharacters);
},
registerTerminalQuickFixProvider(id: string, provider: vscode.TerminalQuickFixProvider): vscode.Disposable {
checkProposedApiEnabled(extension, 'terminalQuickFixProvider');
@@ -3041,7 +3041,7 @@ export interface MainThreadMcpShape {
$onDidReceiveMessage(id: number, message: string): void;
$upsertMcpCollection(collection: McpCollectionDefinition.FromExtHost, servers: McpServerDefinition.Serialized[]): void;
$deleteMcpCollection(collectionId: string): void;
$getTokenFromServerMetadata(id: number, authorizationServer: UriComponents, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined, errorOnUserInteraction?: boolean): Promise<string | undefined>;
$getTokenFromServerMetadata(id: number, authorizationServer: UriComponents, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined, scopes: string[] | undefined, errorOnUserInteraction?: boolean): Promise<string | undefined>;
}
export interface MainThreadDataChannelsShape extends IDisposable {
+52 -19
View File
@@ -8,7 +8,7 @@ import { DeferredPromise, raceCancellationError, Sequencer, timeout } from '../.
import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js';
import { CancellationError } from '../../../base/common/errors.js';
import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
import { AUTH_SERVER_METADATA_DISCOVERY_PATH, fetchResourceMetadata, getDefaultMetadataForUrl, IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata, isAuthorizationServerMetadata, OPENID_CONNECT_DISCOVERY_PATH, parseWWWAuthenticateHeader } from '../../../base/common/oauth.js';
import { AUTH_SCOPE_SEPARATOR, AUTH_SERVER_METADATA_DISCOVERY_PATH, fetchResourceMetadata, getDefaultMetadataForUrl, IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata, isAuthorizationServerMetadata, OPENID_CONNECT_DISCOVERY_PATH, parseWWWAuthenticateHeader, scopesMatch } from '../../../base/common/oauth.js';
import { SSEParser } from '../../../base/common/sseParser.js';
import { URI, UriComponents } from '../../../base/common/uri.js';
import { ConfigurationTarget } from '../../../platform/configuration/common/configuration.js';
@@ -215,6 +215,7 @@ export class McpHTTPHandle extends Disposable {
authorizationServer: URI;
serverMetadata: IAuthorizationServerMetadata;
resourceMetadata?: IAuthorizationProtectedResourceMetadata;
scopes?: string[];
};
constructor(
@@ -337,22 +338,11 @@ export class McpHTTPHandle extends Disposable {
private async _populateAuthMetadata(mcpUrl: string, originalResponse: CommonResponse): Promise<void> {
// If there is a resource_metadata challenge, use that to get the oauth server. This is done in 2 steps.
// First, extract the resource_metada challenge from the WWW-Authenticate header (if available)
let resourceMetadataChallenge: string | undefined;
if (originalResponse.headers.has('WWW-Authenticate')) {
const authHeader = originalResponse.headers.get('WWW-Authenticate')!;
const challenges = parseWWWAuthenticateHeader(authHeader);
for (const challenge of challenges) {
if (challenge.scheme === 'Bearer' && challenge.params['resource_metadata']) {
this._log(LogLevel.Debug, `Found resource_metadata challenge in WWW-Authenticate header: ${challenge.params['resource_metadata']}`);
resourceMetadataChallenge = challenge.params['resource_metadata'];
break;
}
}
}
const { resourceMetadataChallenge, scopesChallenge: scopesChallengeFromHeader } = this._parseWWWAuthenticateHeader(originalResponse);
// Second, fetch the resource metadata either from the challenge URL or from well-known URIs
let serverMetadataUrl: string | undefined;
let scopesSupported: string[] | undefined;
let resource: IAuthorizationProtectedResourceMetadata | undefined;
let scopesChallenge = scopesChallengeFromHeader;
try {
const resourceMetadata = await fetchResourceMetadata(mcpUrl, resourceMetadataChallenge, {
sameOriginHeaders: {
@@ -365,7 +355,7 @@ export class McpHTTPHandle extends Disposable {
// Consider using one that has an auth provider first, over the dynamic flow
serverMetadataUrl = resourceMetadata.authorization_servers?.[0];
this._log(LogLevel.Debug, `Using auth server metadata url: ${serverMetadataUrl}`);
scopesSupported = resourceMetadata.scopes_supported;
scopesChallenge ??= resourceMetadata.scopes_supported;
resource = resourceMetadata;
} catch (e) {
this._log(LogLevel.Debug, `Could not fetch resource metadata: ${String(e)}`);
@@ -389,7 +379,8 @@ export class McpHTTPHandle extends Disposable {
this._authMetadata = {
authorizationServer: URI.parse(serverMetadataUrl),
serverMetadata: serverMetadataResponse,
resourceMetadata: resource
resourceMetadata: resource,
scopes: scopesChallenge
};
return;
} catch (e) {
@@ -398,11 +389,11 @@ export class McpHTTPHandle extends Disposable {
// If there's no well-known server metadata, then use the default values based off of the url.
const defaultMetadata = getDefaultMetadataForUrl(new URL(baseUrl));
defaultMetadata.scopes_supported = scopesSupported ?? defaultMetadata.scopes_supported ?? [];
this._authMetadata = {
authorizationServer: URI.parse(baseUrl),
serverMetadata: defaultMetadata,
resourceMetadata: resource
resourceMetadata: resource,
scopes: scopesChallenge
};
this._log(LogLevel.Info, 'Using default auth metadata');
}
@@ -663,7 +654,7 @@ export class McpHTTPHandle extends Disposable {
private async _addAuthHeader(headers: Record<string, string>) {
if (this._authMetadata) {
try {
const token = await this._proxy.$getTokenFromServerMetadata(this._id, this._authMetadata.authorizationServer, this._authMetadata.serverMetadata, this._authMetadata.resourceMetadata, this._errorOnUserInteraction);
const token = await this._proxy.$getTokenFromServerMetadata(this._id, this._authMetadata.authorizationServer, this._authMetadata.serverMetadata, this._authMetadata.resourceMetadata, this._authMetadata.scopes, this._errorOnUserInteraction);
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
@@ -684,6 +675,34 @@ export class McpHTTPHandle extends Disposable {
}
}
private _parseWWWAuthenticateHeader(response: CommonResponse): { resourceMetadataChallenge: string | undefined; scopesChallenge: string[] | undefined } {
let resourceMetadataChallenge: string | undefined;
let scopesChallenge: string[] | undefined;
if (response.headers.has('WWW-Authenticate')) {
const authHeader = response.headers.get('WWW-Authenticate')!;
const challenges = parseWWWAuthenticateHeader(authHeader);
for (const challenge of challenges) {
if (challenge.scheme === 'Bearer') {
if (!resourceMetadataChallenge && challenge.params['resource_metadata']) {
resourceMetadataChallenge = challenge.params['resource_metadata'];
this._log(LogLevel.Debug, `Found resource_metadata challenge in WWW-Authenticate header: ${resourceMetadataChallenge}`);
}
if (!scopesChallenge && challenge.params['scope']) {
const scopes = challenge.params['scope'].split(AUTH_SCOPE_SEPARATOR).filter(s => s.trim().length);
if (scopes.length) {
this._log(LogLevel.Debug, `Found scope challenge in WWW-Authenticate header: ${challenge.params['scope']}`);
scopesChallenge = scopes;
}
}
if (resourceMetadataChallenge && scopesChallenge) {
break;
}
}
}
}
return { resourceMetadataChallenge, scopesChallenge };
}
private async _getErrText(res: CommonResponse) {
try {
return await res.text();
@@ -696,6 +715,7 @@ export class McpHTTPHandle extends Disposable {
* Helper method to perform fetch with 401 authentication retry logic.
* If the initial request returns 401 and we don't have auth metadata,
* it will populate the auth metadata and retry once.
* If we already have auth metadata, check if the scopes changed and update them.
*/
private async _fetchWithAuthRetry(mcpUrl: string, init: MinimalRequestInit, headers: Record<string, string>): Promise<CommonResponse> {
const doFetch = () => this._fetch(mcpUrl, init);
@@ -710,6 +730,19 @@ export class McpHTTPHandle extends Disposable {
init.headers = headers;
res = await doFetch();
}
} else {
// We have auth metadata, but got a 401. Check if the scopes changed.
const { scopesChallenge } = this._parseWWWAuthenticateHeader(res);
if (!scopesMatch(scopesChallenge, this._authMetadata.scopes)) {
this._log(LogLevel.Debug, `Scopes changed from ${JSON.stringify(this._authMetadata.scopes)} to ${JSON.stringify(scopesChallenge)}, updating and retrying`);
this._authMetadata.scopes = scopesChallenge;
await this._addAuthHeader(headers);
if (headers['Authorization']) {
// Update the headers in the init object
init.headers = headers;
res = await doFetch();
}
}
}
}
return res;
+5 -5
View File
@@ -257,23 +257,23 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape {
...this.createExtHostOutputChannel(name, channelPromise, channelDisposables),
get logLevel() { return logLevel; },
onDidChangeLogLevel: onDidChangeLogLevel.event,
trace(value: string, ...args: any[]): void {
trace(value: string, ...args: unknown[]): void {
validate();
channelPromise.then(channel => channel.trace(value, ...args));
},
debug(value: string, ...args: any[]): void {
debug(value: string, ...args: unknown[]): void {
validate();
channelPromise.then(channel => channel.debug(value, ...args));
},
info(value: string, ...args: any[]): void {
info(value: string, ...args: unknown[]): void {
validate();
channelPromise.then(channel => channel.info(value, ...args));
},
warn(value: string, ...args: any[]): void {
warn(value: string, ...args: unknown[]): void {
validate();
channelPromise.then(channel => channel.warn(value, ...args));
},
error(value: Error | string, ...args: any[]): void {
error(value: Error | string, ...args: unknown[]): void {
validate();
channelPromise.then(channel => channel.error(value, ...args));
}
@@ -57,7 +57,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID
getEnvironmentVariableCollection(extension: IExtensionDescription): IEnvironmentVariableCollection;
getTerminalById(id: number): ExtHostTerminal | null;
getTerminalIdByApiObject(apiTerminal: vscode.Terminal): number | null;
registerTerminalCompletionProvider(extension: IExtensionDescription, id: string, provider: vscode.TerminalCompletionProvider<vscode.TerminalCompletionItem>, ...triggerCharacters: string[]): vscode.Disposable;
registerTerminalCompletionProvider(extension: IExtensionDescription, provider: vscode.TerminalCompletionProvider<vscode.TerminalCompletionItem>, ...triggerCharacters: string[]): vscode.Disposable;
}
interface IEnvironmentVariableCollection extends vscode.EnvironmentVariableCollection {
@@ -757,15 +757,15 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
});
}
public registerTerminalCompletionProvider(extension: IExtensionDescription, id: string, provider: vscode.TerminalCompletionProvider<TerminalCompletionItem>, ...triggerCharacters: string[]): vscode.Disposable {
if (this._completionProviders.has(id)) {
throw new Error(`Terminal completion provider "${id}" already registered`);
public registerTerminalCompletionProvider(extension: IExtensionDescription, provider: vscode.TerminalCompletionProvider<TerminalCompletionItem>, ...triggerCharacters: string[]): vscode.Disposable {
if (this._completionProviders.has(extension.identifier.value)) {
throw new Error(`Terminal completion provider "${extension.identifier.value}" already registered`);
}
this._completionProviders.set(id, provider);
this._proxy.$registerCompletionProvider(id, extension.identifier.value, ...triggerCharacters);
this._completionProviders.set(extension.identifier.value, provider);
this._proxy.$registerCompletionProvider(extension.identifier.value, extension.identifier.value, ...triggerCharacters);
return new VSCodeDisposable(() => {
this._completionProviders.delete(id);
this._proxy.$unregisterCompletionProvider(id);
this._completionProviders.delete(extension.identifier.value);
this._proxy.$unregisterCompletionProvider(extension.identifier.value);
});
}
@@ -19,7 +19,7 @@ const emptyCommandService: ICommandService = {
_serviceBrand: undefined,
onWillExecuteCommand: () => Disposable.None,
onDidExecuteCommand: () => Disposable.None,
executeCommand: (commandId: string, ...args: any[]): Promise<any> => {
executeCommand: (commandId: string, ...args: unknown[]): Promise<any> => {
return Promise.resolve(undefined);
}
};
@@ -27,16 +27,16 @@ const emptyCommandService: ICommandService = {
const emptyNotificationService = new class implements INotificationService {
declare readonly _serviceBrand: undefined;
onDidChangeFilter: Event<void> = Event.None;
notify(...args: any[]): never {
notify(...args: unknown[]): never {
throw new Error('not implemented');
}
info(...args: any[]): never {
info(...args: unknown[]): never {
throw new Error('not implemented');
}
warn(...args: any[]): never {
warn(...args: unknown[]): never {
throw new Error('not implemented');
}
error(...args: any[]): never {
error(...args: unknown[]): never {
throw new Error('not implemented');
}
prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle {
@@ -338,7 +338,7 @@ export class ReviewChangesAction extends ChatEditingEditorAction {
});
}
override runChatEditingCommand(_accessor: ServicesAccessor, _session: IChatEditingSession, entry: IModifiedFileEntry, _integration: IModifiedFileEntryEditorIntegration, ..._args: any[]): void {
override runChatEditingCommand(_accessor: ServicesAccessor, _session: IChatEditingSession, entry: IModifiedFileEntry, _integration: IModifiedFileEntryEditorIntegration, ..._args: unknown[]): void {
entry.enableReviewModeUntilSettled();
}
}
@@ -23,23 +23,23 @@ export class EditSessionsLogService extends AbstractLogger implements IEditSessi
this.logger = this._register(loggerService.createLogger(joinPath(environmentService.logsHome, `${editSessionsLogId}.log`), { id: editSessionsLogId, name: localize('cloudChangesLog', "Cloud Changes"), group: windowLogGroup }));
}
trace(message: string, ...args: any[]): void {
trace(message: string, ...args: unknown[]): void {
this.logger.trace(message, ...args);
}
debug(message: string, ...args: any[]): void {
debug(message: string, ...args: unknown[]): void {
this.logger.debug(message, ...args);
}
info(message: string, ...args: any[]): void {
info(message: string, ...args: unknown[]): void {
this.logger.info(message, ...args);
}
warn(message: string, ...args: any[]): void {
warn(message: string, ...args: unknown[]): void {
this.logger.warn(message, ...args);
}
error(message: string | Error, ...args: any[]): void {
error(message: string | Error, ...args: unknown[]): void {
this.logger.error(message, ...args);
}
@@ -105,7 +105,7 @@ export class StartSessionAction extends Action2 {
});
}
private _runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
private _runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: unknown[]) {
const ctrl = InlineChatController.get(editor);
if (!ctrl) {
@@ -146,7 +146,7 @@ export class FocusInlineChat extends EditorAction2 {
});
}
override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: unknown[]) {
InlineChatController.get(editor)?.focus();
}
}
@@ -167,7 +167,7 @@ export class UnstashSessionAction extends EditorAction2 {
});
}
override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: unknown[]) {
const ctrl = InlineChatController1.get(editor);
if (ctrl) {
const session = ctrl.unstashLastSession();
@@ -208,7 +208,7 @@ export abstract class AbstractInline1ChatAction extends EditorAction2 {
});
}
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: unknown[]) {
const editorService = accessor.get(IEditorService);
const logService = accessor.get(ILogService);
@@ -260,7 +260,7 @@ export class ArrowOutUpAction extends AbstractInline1ChatAction {
});
}
runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): void {
runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: unknown[]): void {
ctrl.arrowOut(true);
}
}
@@ -278,7 +278,7 @@ export class ArrowOutDownAction extends AbstractInline1ChatAction {
});
}
runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): void {
runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: unknown[]): void {
ctrl.arrowOut(false);
}
}
@@ -371,7 +371,7 @@ export class RerunAction extends AbstractInline1ChatAction {
});
}
override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): Promise<void> {
override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: unknown[]): Promise<void> {
const chatService = accessor.get(IChatService);
const chatWidgetService = accessor.get(IChatWidgetService);
const model = ctrl.chatWidget.viewModel?.model;
@@ -417,7 +417,7 @@ export class CloseAction extends AbstractInline1ChatAction {
});
}
async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): Promise<void> {
async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: unknown[]): Promise<void> {
ctrl.cancelSession();
}
}
@@ -438,7 +438,7 @@ export class ConfigureInlineChatAction extends AbstractInline1ChatAction {
});
}
async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): Promise<void> {
async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: unknown[]): Promise<void> {
accessor.get(IPreferencesService).openSettings({ query: 'inlineChat' });
}
}
@@ -512,7 +512,7 @@ export class ViewInChatAction extends AbstractInline1ChatAction {
}
});
}
override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]) {
override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: unknown[]) {
return ctrl.viewInChat();
}
}
@@ -577,7 +577,7 @@ abstract class AbstractInline2ChatAction extends EditorAction2 {
});
}
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: unknown[]) {
const editorService = accessor.get(IEditorService);
const logService = accessor.get(ILogService);
@@ -642,7 +642,7 @@ class KeepOrUndoSessionAction extends AbstractInline2ChatAction {
});
}
override async runInlineChatCommand(accessor: ServicesAccessor, _ctrl: InlineChatController2, editor: ICodeEditor, ..._args: any[]): Promise<void> {
override async runInlineChatCommand(accessor: ServicesAccessor, _ctrl: InlineChatController2, editor: ICodeEditor, ..._args: unknown[]): Promise<void> {
const inlineChatSessions = accessor.get(IInlineChatSessionService);
if (!editor.hasModel()) {
return;
@@ -315,7 +315,7 @@ export class InlineChatController1 implements IEditorContribution {
this._log('DISPOSED controller');
}
private _log(message: string | Error, ...more: any[]): void {
private _log(message: string | Error, ...more: unknown[]): void {
if (message instanceof Error) {
this._logService.error(message, ...more);
} else {
@@ -715,7 +715,7 @@ export class InlineChatController1 implements IEditorContribution {
}
if (e.kind === 'move') {
assertType(this._session);
const log: typeof this._log = (msg: string, ...args: any[]) => this._log('state=_showRequest) moving inline chat', msg, ...args);
const log: typeof this._log = (msg: string, ...args: unknown[]) => this._log('state=_showRequest) moving inline chat', msg, ...args);
log('move was requested', e.target, e.range);
@@ -123,7 +123,7 @@ export class ShowInlineChatHintAction extends EditorAction2 {
});
}
override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ...args: [uri: URI, position: IPosition, ...rest: any[]]) {
override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ...args: [uri: URI, position: IPosition, ...rest: unknown[]]) {
if (!editor.hasModel()) {
return;
}
@@ -38,7 +38,7 @@ export class HoldToSpeak extends EditorAction2 {
});
}
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: unknown[]) {
const ctrl = InlineChatController.get(editor);
if (ctrl) {
holdForSpeech(accessor, ctrl, this);
@@ -369,7 +369,7 @@ registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction {
});
}
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined {
return parseMultiCellExecutionArgs(accessor, ...args);
}
@@ -395,7 +395,7 @@ registerAction2(class ExpandCellInputAction extends NotebookMultiCellAction {
});
}
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined {
return parseMultiCellExecutionArgs(accessor, ...args);
}
@@ -465,7 +465,7 @@ registerAction2(class extends NotebookMultiCellAction {
});
}
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined {
return parseMultiCellExecutionArgs(accessor, ...args);
}
@@ -18,7 +18,6 @@ import { ServicesAccessor } from '../../../../../../platform/instantiation/commo
import { KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js';
import { Registry } from '../../../../../../platform/registry/common/platform.js';
import { InlineChatController } from '../../../../inlineChat/browser/inlineChatController.js';
import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from '../../controller/chat/notebookChatContext.js';
import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, findTargetCellEditor } from '../../controller/coreActions.js';
import { CellEditState } from '../../notebookBrowser.js';
import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NOTEBOOK_EDITOR_CURSOR_LINE_BOUNDARY } from '../../../common/notebookCommon.js';
@@ -237,7 +236,7 @@ registerAction2(class extends NotebookAction {
weight: KeybindingWeight.WorkbenchContrib
},
{
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')),
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow },
weight: KeybindingWeight.WorkbenchContrib
}
@@ -269,7 +268,7 @@ registerAction2(class extends NotebookAction {
weight: KeybindingWeight.WorkbenchContrib
},
{
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')),
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow },
weight: KeybindingWeight.WorkbenchContrib
}
@@ -5,9 +5,7 @@
import { Codicon } from '../../../../../../base/common/codicons.js';
import { KeyChord, KeyCode, KeyMod } from '../../../../../../base/common/keyCodes.js';
import { EditorContextKeys } from '../../../../../../editor/common/editorContextKeys.js';
import { localize, localize2 } from '../../../../../../nls.js';
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../../../platform/accessibility/common/accessibility.js';
import { MenuId, MenuRegistry, registerAction2 } from '../../../../../../platform/actions/common/actions.js';
import { ICommandService } from '../../../../../../platform/commands/common/commands.js';
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
@@ -15,14 +13,12 @@ import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/con
import { InputFocusedContextKey } from '../../../../../../platform/contextkey/common/contextkeys.js';
import { ServicesAccessor } from '../../../../../../platform/instantiation/common/instantiation.js';
import { KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js';
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, InlineChatResponseType, MENU_INLINE_CHAT_WIDGET_STATUS } from '../../../../inlineChat/common/inlineChat.js';
import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_HAS_AGENT, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS } from './notebookChatContext.js';
import { NotebookChatController } from './notebookChatController.js';
import { CELL_TITLE_CELL_GROUP_ID, INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getContextFromActiveEditor, getEditorFromArgsOrActivePane } from '../coreActions.js';
import { CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, InlineChatResponseType, MENU_INLINE_CHAT_WIDGET_STATUS } from '../../../../inlineChat/common/inlineChat.js';
import { CTX_NOTEBOOK_CHAT_HAS_AGENT } from './notebookChatContext.js';
import { INotebookActionContext, NotebookAction, getContextFromActiveEditor, getEditorFromArgsOrActivePane } from '../coreActions.js';
import { insertNewCell } from '../insertCellActions.js';
import { CellEditState } from '../../notebookBrowser.js';
import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from '../../../common/notebookCommon.js';
import { IS_COMPOSITE_NOTEBOOK, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_GENERATED_BY_CHAT, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from '../../../common/notebookContextKeys.js';
import { CellKind, NotebookSetting } from '../../../common/notebookCommon.js';
import { NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from '../../../common/notebookContextKeys.js';
import { Iterable } from '../../../../../../base/common/iterator.js';
import { ICodeEditor } from '../../../../../../editor/browser/editorBrowser.js';
import { IEditorService } from '../../../../../services/editor/common/editorService.js';
@@ -30,283 +26,6 @@ import { ChatContextKeys } from '../../../../chat/common/chatContextKeys.js';
import { InlineChatController } from '../../../../inlineChat/browser/inlineChatController.js';
import { EditorAction2 } from '../../../../../../editor/browser/editorExtensions.js';
registerAction2(class extends NotebookAction {
constructor() {
super(
{
id: 'notebook.cell.chat.accept',
title: localize2('notebook.cell.chat.accept', "Make Request"),
icon: Codicon.send,
keybinding: {
when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, NOTEBOOK_CELL_EDITOR_FOCUSED.negate()),
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.Enter
},
menu: {
id: MENU_CELL_CHAT_INPUT,
group: 'navigation',
order: 1,
when: CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST.negate()
},
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) {
NotebookChatController.get(context.notebookEditor)?.acceptInput();
}
});
registerAction2(class extends NotebookCellAction {
constructor() {
super(
{
id: 'notebook.cell.chat.arrowOutUp',
title: localize('arrowUp', 'Cursor Up'),
keybinding: {
when: ContextKeyExpr.and(
CTX_NOTEBOOK_CELL_CHAT_FOCUSED,
CTX_INLINE_CHAT_FOCUSED,
CTX_INLINE_CHAT_INNER_CURSOR_FIRST,
NOTEBOOK_CELL_EDITOR_FOCUSED.negate(),
CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()
),
weight: KeybindingWeight.EditorCore + 7,
primary: KeyMod.CtrlCmd | KeyCode.UpArrow
},
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) {
const editor = context.notebookEditor;
const activeCell = context.cell;
const idx = editor.getCellIndex(activeCell);
if (typeof idx !== 'number') {
return;
}
if (idx < 1 || editor.getLength() === 0) {
// we don't do loop
return;
}
const newCell = editor.cellAt(idx - 1);
const newFocusMode = newCell.cellKind === CellKind.Markup && newCell.getEditState() === CellEditState.Preview ? 'container' : 'editor';
const focusEditorLine = newCell.textBuffer.getLineCount();
await editor.focusNotebookCell(newCell, newFocusMode, { focusEditorLine: focusEditorLine });
}
});
registerAction2(class extends NotebookAction {
constructor() {
super(
{
id: 'notebook.cell.chat.arrowOutDown',
title: localize('arrowDown', 'Cursor Down'),
keybinding: {
when: ContextKeyExpr.and(
CTX_NOTEBOOK_CELL_CHAT_FOCUSED,
CTX_INLINE_CHAT_FOCUSED,
CTX_INLINE_CHAT_INNER_CURSOR_LAST,
NOTEBOOK_CELL_EDITOR_FOCUSED.negate(),
CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()
),
weight: KeybindingWeight.EditorCore + 7,
primary: KeyMod.CtrlCmd | KeyCode.DownArrow
},
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) {
await NotebookChatController.get(context.notebookEditor)?.focusNext();
}
});
registerAction2(class extends NotebookCellAction {
constructor() {
super(
{
id: 'notebook.cell.focusChatWidget',
title: localize('focusChatWidget', 'Focus Chat Widget'),
keybinding: {
when: ContextKeyExpr.and(
NOTEBOOK_EDITOR_FOCUSED,
CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(),
ContextKeyExpr.and(
ContextKeyExpr.has(InputFocusedContextKey),
EditorContextKeys.editorTextFocus,
NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'),
NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'),
),
EditorContextKeys.isEmbeddedDiffEditor.negate()
),
weight: KeybindingWeight.EditorCore + 7,
primary: KeyMod.CtrlCmd | KeyCode.UpArrow
},
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) {
const index = context.notebookEditor.getCellIndex(context.cell);
await NotebookChatController.get(context.notebookEditor)?.focusNearestWidget(index, 'above');
}
});
registerAction2(class extends NotebookCellAction {
constructor() {
super(
{
id: 'notebook.cell.focusNextChatWidget',
title: localize('focusNextChatWidget', 'Focus Next Cell Chat Widget'),
keybinding: {
when: ContextKeyExpr.and(
CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(),
ContextKeyExpr.and(
ContextKeyExpr.has(InputFocusedContextKey),
EditorContextKeys.editorTextFocus,
NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'),
NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'),
),
EditorContextKeys.isEmbeddedDiffEditor.negate()
),
weight: KeybindingWeight.EditorCore + 7,
primary: KeyMod.CtrlCmd | KeyCode.DownArrow
},
f1: false,
precondition: ContextKeyExpr.or(
ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK.negate(), NOTEBOOK_CELL_EDITOR_FOCUSED),
ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK, NOTEBOOK_CELL_EDITOR_FOCUSED.negate()),
)
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) {
const index = context.notebookEditor.getCellIndex(context.cell);
await NotebookChatController.get(context.notebookEditor)?.focusNearestWidget(index, 'below');
}
});
registerAction2(class extends NotebookAction {
constructor() {
super(
{
id: 'notebook.cell.chat.stop',
title: localize2('notebook.cell.chat.stop', "Stop Request"),
icon: Codicon.debugStop,
menu: {
id: MENU_CELL_CHAT_INPUT,
group: 'navigation',
order: 1,
when: CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST
},
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) {
NotebookChatController.get(context.notebookEditor)?.cancelCurrentRequest(false);
}
});
registerAction2(class extends NotebookAction {
constructor() {
super(
{
id: 'notebook.cell.chat.close',
title: localize2('notebook.cell.chat.close', "Close Chat"),
icon: Codicon.close,
menu: {
id: MENU_CELL_CHAT_WIDGET,
group: 'navigation',
order: 2
},
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) {
NotebookChatController.get(context.notebookEditor)?.dismiss(false);
}
});
registerAction2(class extends NotebookAction {
constructor() {
super(
{
id: 'notebook.cell.chat.acceptChanges',
title: localize2('apply1', "Accept Changes"),
shortTitle: localize('apply2', 'Accept'),
icon: Codicon.check,
tooltip: localize('apply3', 'Accept Changes'),
keybinding: [
{
when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, NOTEBOOK_CELL_EDITOR_FOCUSED.negate()),
weight: KeybindingWeight.EditorContrib + 10,
primary: KeyMod.CtrlCmd | KeyCode.Enter,
},
{
when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, NOTEBOOK_CELL_EDITOR_FOCUSED.negate()),
weight: KeybindingWeight.EditorCore + 10,
primary: KeyCode.Escape
},
{
when: ContextKeyExpr.and(
NOTEBOOK_EDITOR_FOCUSED,
ContextKeyExpr.not(InputFocusedContextKey),
NOTEBOOK_CELL_EDITOR_FOCUSED.negate(),
CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('below')
),
primary: KeyMod.CtrlCmd | KeyCode.Enter,
weight: KeybindingWeight.WorkbenchContrib
}
],
menu: [
{
id: MENU_CELL_CHAT_WIDGET_STATUS,
group: '0_main',
order: 0,
when: CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.Messages),
}
],
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) {
NotebookChatController.get(context.notebookEditor)?.acceptSession();
}
});
registerAction2(class extends NotebookAction {
constructor() {
super(
{
id: 'notebook.cell.chat.discard',
title: localize('discard', 'Discard'),
icon: Codicon.discard,
keybinding: {
when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_USER_DID_EDIT.negate(), NOTEBOOK_CELL_EDITOR_FOCUSED.negate()),
weight: KeybindingWeight.EditorContrib,
primary: KeyCode.Escape
},
menu: {
id: MENU_CELL_CHAT_WIDGET_STATUS,
group: '0_main',
order: 1
},
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) {
NotebookChatController.get(context.notebookEditor)?.discard();
}
});
interface IInsertCellWithChatArgs extends INotebookActionContext {
input?: string;
autoSend?: boolean;
@@ -501,175 +220,6 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
)
});
registerAction2(class extends NotebookAction {
constructor() {
super({
id: 'notebook.cell.chat.focus',
title: localize('focusNotebookChat', 'Focus Chat'),
keybinding: [
{
when: ContextKeyExpr.and(
NOTEBOOK_EDITOR_FOCUSED,
ContextKeyExpr.not(InputFocusedContextKey),
CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('above')
),
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
weight: KeybindingWeight.WorkbenchContrib
},
{
when: ContextKeyExpr.and(
NOTEBOOK_EDITOR_FOCUSED,
ContextKeyExpr.not(InputFocusedContextKey),
CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('below')
),
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
weight: KeybindingWeight.WorkbenchContrib
}
],
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
NotebookChatController.get(context.notebookEditor)?.focus();
}
});
registerAction2(class extends NotebookAction {
constructor() {
super({
id: 'notebook.cell.chat.focusNextCell',
title: localize('focusNextCell', 'Focus Next Cell'),
keybinding: [
{
when: ContextKeyExpr.and(
CTX_NOTEBOOK_CELL_CHAT_FOCUSED,
CTX_INLINE_CHAT_FOCUSED,
),
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
weight: KeybindingWeight.WorkbenchContrib
}
],
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
NotebookChatController.get(context.notebookEditor)?.focusNext();
}
});
registerAction2(class extends NotebookAction {
constructor() {
super({
id: 'notebook.cell.chat.focusPreviousCell',
title: localize('focusPreviousCell', 'Focus Previous Cell'),
keybinding: [
{
when: ContextKeyExpr.and(
CTX_NOTEBOOK_CELL_CHAT_FOCUSED,
CTX_INLINE_CHAT_FOCUSED,
),
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
weight: KeybindingWeight.WorkbenchContrib
}
],
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
NotebookChatController.get(context.notebookEditor)?.focusAbove();
}
});
registerAction2(class extends NotebookAction {
constructor() {
super(
{
id: 'notebook.cell.chat.previousFromHistory',
title: localize2('notebook.cell.chat.previousFromHistory', "Previous From History"),
precondition: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED),
keybinding: {
when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED),
weight: KeybindingWeight.EditorCore + 10,
primary: KeyCode.UpArrow,
},
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) {
NotebookChatController.get(context.notebookEditor)?.populateHistory(true);
}
});
registerAction2(class extends NotebookAction {
constructor() {
super(
{
id: 'notebook.cell.chat.nextFromHistory',
title: localize2('notebook.cell.chat.nextFromHistory', "Next From History"),
precondition: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED),
keybinding: {
when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED),
weight: KeybindingWeight.EditorCore + 10,
primary: KeyCode.DownArrow
},
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) {
NotebookChatController.get(context.notebookEditor)?.populateHistory(false);
}
});
registerAction2(class extends NotebookCellAction {
constructor() {
super(
{
id: 'notebook.cell.chat.restore',
title: localize2('notebookActions.restoreCellprompt', "Generate"),
icon: Codicon.sparkle,
menu: {
id: MenuId.NotebookCellTitle,
group: CELL_TITLE_CELL_GROUP_ID,
order: 0,
when: ContextKeyExpr.and(
NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true),
CTX_NOTEBOOK_CHAT_HAS_AGENT,
NOTEBOOK_CELL_GENERATED_BY_CHAT,
ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true)
)
},
f1: false
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) {
const cell = context.cell;
if (!cell) {
return;
}
const notebookEditor = context.notebookEditor;
const controller = NotebookChatController.get(notebookEditor);
if (!controller) {
return;
}
const prompt = controller.getPromptFromCache(cell);
if (prompt) {
controller.restore(cell, prompt);
}
}
});
export class AcceptChangesAndRun extends EditorAction2 {
constructor() {
@@ -4,18 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from '../../../../../../nls.js';
import { MenuId } from '../../../../../../platform/actions/common/actions.js';
import { RawContextKey } from '../../../../../../platform/contextkey/common/contextkey.js';
export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey<boolean>('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused"));
export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey<boolean>('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request"));
export const CTX_NOTEBOOK_CHAT_USER_DID_EDIT = new RawContextKey<boolean>('notebookChatUserDidEdit', false, localize('notebookChatUserDidEdit', "Whether the user did changes ontop of the notebook cell chat"));
export const CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION = new RawContextKey<'above' | 'below' | ''>('notebookChatOuterFocusPosition', '', localize('notebookChatOuterFocusPosition', "Whether the focus of the notebook editor is above or below the cell chat"));
export const MENU_CELL_CHAT_INPUT = MenuId.for('cellChatInput');
export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget');
export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status');
export const MENU_CELL_CHAT_WIDGET_FEEDBACK = MenuId.for('cellChatWidget.feedback');
export const MENU_CELL_CHAT_WIDGET_TOOLBAR = MenuId.for('cellChatWidget.toolbar');
export const CTX_NOTEBOOK_CHAT_HAS_AGENT = new RawContextKey<boolean>('notebookChatAgentRegistered', false, localize('notebookChatAgentRegistered', "Whether a chat agent for notebook is registered"));
@@ -1,949 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Dimension, IFocusTracker, WindowIntervalTimer, getWindow, scheduleAtNextAnimationFrame, trackFocus } from '../../../../../../base/browser/dom.js';
import { CancelablePromise, DeferredPromise, Queue, createCancelablePromise, disposableTimeout } from '../../../../../../base/common/async.js';
import { CancellationToken, CancellationTokenSource } from '../../../../../../base/common/cancellation.js';
import { Emitter } from '../../../../../../base/common/event.js';
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js';
import { LRUCache } from '../../../../../../base/common/map.js';
import { Schemas } from '../../../../../../base/common/network.js';
import { MovingAverage } from '../../../../../../base/common/numbers.js';
import { isEqual } from '../../../../../../base/common/resources.js';
import { StopWatch } from '../../../../../../base/common/stopwatch.js';
import { assertType } from '../../../../../../base/common/types.js';
import { URI } from '../../../../../../base/common/uri.js';
import { IActiveCodeEditor } from '../../../../../../editor/browser/editorBrowser.js';
import { CodeEditorWidget } from '../../../../../../editor/browser/widget/codeEditor/codeEditorWidget.js';
import { ISingleEditOperation } from '../../../../../../editor/common/core/editOperation.js';
import { Position } from '../../../../../../editor/common/core/position.js';
import { Selection } from '../../../../../../editor/common/core/selection.js';
import { TextEdit } from '../../../../../../editor/common/languages.js';
import { ILanguageService } from '../../../../../../editor/common/languages/language.js';
import { ICursorStateComputer, ITextModel } from '../../../../../../editor/common/model.js';
import { IEditorWorkerService } from '../../../../../../editor/common/services/editorWorker.js';
import { IModelService } from '../../../../../../editor/common/services/model.js';
import { localize } from '../../../../../../nls.js';
import { IContextKey, IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js';
import { ChatModel, IChatModel } from '../../../../chat/common/chatModel.js';
import { IChatService } from '../../../../chat/common/chatService.js';
import { countWords } from '../../../../chat/common/chatWordCounter.js';
import { ChatAgentLocation } from '../../../../chat/common/constants.js';
import { ProgressingEditsOptions } from '../../../../inlineChat/browser/inlineChatStrategies.js';
import { InlineChatWidget } from '../../../../inlineChat/browser/inlineChatWidget.js';
import { asProgressiveEdit, performAsyncTextEdit } from '../../../../inlineChat/browser/utils.js';
import { CellKind } from '../../../common/notebookCommon.js';
import { INotebookExecutionStateService, NotebookExecutionType } from '../../../common/notebookExecutionStateService.js';
import { ICellViewModel, INotebookEditor, INotebookEditorContribution, INotebookViewZone } from '../../notebookBrowser.js';
import { registerNotebookContribution } from '../../notebookEditorExtensions.js';
import { insertCell, runDeleteAction } from '../cellOperations.js';
import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_WIDGET_STATUS } from './notebookChatContext.js';
class NotebookChatWidget extends Disposable implements INotebookViewZone {
set afterModelPosition(afterModelPosition: number) {
this.notebookViewZone.afterModelPosition = afterModelPosition;
}
get afterModelPosition(): number {
return this.notebookViewZone.afterModelPosition;
}
set heightInPx(heightInPx: number) {
this.notebookViewZone.heightInPx = heightInPx;
}
get heightInPx(): number {
return this.notebookViewZone.heightInPx;
}
private _editingCell: ICellViewModel | null = null;
get editingCell() {
return this._editingCell;
}
constructor(
private readonly _notebookEditor: INotebookEditor,
readonly id: string,
readonly notebookViewZone: INotebookViewZone,
readonly domNode: HTMLElement,
readonly widgetContainer: HTMLElement,
readonly inlineChatWidget: InlineChatWidget,
readonly parentEditor: CodeEditorWidget,
private readonly _languageService: ILanguageService,
) {
super();
const updateHeight = () => {
if (this.heightInPx === inlineChatWidget.contentHeight) {
return;
}
this.heightInPx = inlineChatWidget.contentHeight;
this._notebookEditor.changeViewZones(accessor => {
accessor.layoutZone(id);
});
this._layoutWidget(inlineChatWidget, widgetContainer);
};
this._register(inlineChatWidget.onDidChangeHeight(() => {
updateHeight();
}));
this._register(inlineChatWidget.chatWidget.onDidChangeHeight(() => {
updateHeight();
}));
this.heightInPx = inlineChatWidget.contentHeight;
this._layoutWidget(inlineChatWidget, widgetContainer);
}
layout() {
this._layoutWidget(this.inlineChatWidget, this.widgetContainer);
}
restoreEditingCell(initEditingCell: ICellViewModel) {
this._editingCell = initEditingCell;
const decorationIds = this._notebookEditor.deltaCellDecorations([], [{
handle: this._editingCell.handle,
options: { className: 'nb-chatGenerationHighlight', outputClassName: 'nb-chatGenerationHighlight' }
}]);
this._register(toDisposable(() => {
this._notebookEditor.deltaCellDecorations(decorationIds, []);
}));
}
hasFocus() {
return this.inlineChatWidget.hasFocus();
}
focus() {
this.updateNotebookEditorFocusNSelections();
this.inlineChatWidget.focus();
}
updateNotebookEditorFocusNSelections() {
this._notebookEditor.focusContainer(true);
this._notebookEditor.setFocus({ start: this.afterModelPosition, end: this.afterModelPosition });
this._notebookEditor.setSelections([{
start: this.afterModelPosition,
end: this.afterModelPosition
}]);
}
getEditingCell() {
return this._editingCell;
}
async getOrCreateEditingCell(): Promise<{ cell: ICellViewModel; editor: IActiveCodeEditor } | undefined> {
if (this._editingCell) {
const codeEditor = this._notebookEditor.codeEditors.find(ce => ce[0] === this._editingCell)?.[1];
if (codeEditor?.hasModel()) {
return {
cell: this._editingCell,
editor: codeEditor
};
} else {
return undefined;
}
}
if (!this._notebookEditor.hasModel()) {
return undefined;
}
const widgetHasFocus = this.inlineChatWidget.hasFocus();
this._editingCell = insertCell(this._languageService, this._notebookEditor, this.afterModelPosition, CellKind.Code, 'above');
if (!this._editingCell) {
return undefined;
}
await this._notebookEditor.revealFirstLineIfOutsideViewport(this._editingCell);
// update decoration
const decorationIds = this._notebookEditor.deltaCellDecorations([], [{
handle: this._editingCell.handle,
options: { className: 'nb-chatGenerationHighlight', outputClassName: 'nb-chatGenerationHighlight' }
}]);
this._register(toDisposable(() => {
this._notebookEditor.deltaCellDecorations(decorationIds, []);
}));
if (widgetHasFocus) {
this.focus();
}
const codeEditor = this._notebookEditor.codeEditors.find(ce => ce[0] === this._editingCell)?.[1];
if (codeEditor?.hasModel()) {
return {
cell: this._editingCell,
editor: codeEditor
};
}
return undefined;
}
async discardChange() {
if (this._notebookEditor.hasModel() && this._editingCell) {
// remove the cell from the notebook
runDeleteAction(this._notebookEditor, this._editingCell);
}
}
private _layoutWidget(inlineChatWidget: InlineChatWidget, widgetContainer: HTMLElement) {
const layoutConfiguration = this._notebookEditor.notebookOptions.getLayoutConfiguration();
const rightMargin = layoutConfiguration.cellRightMargin;
const leftMargin = this._notebookEditor.notebookOptions.getCellEditorContainerLeftMargin();
const maxWidth = 640;
const width = Math.min(maxWidth, this._notebookEditor.getLayoutInfo().width - leftMargin - rightMargin);
inlineChatWidget.layout(new Dimension(width, this.heightInPx));
inlineChatWidget.domNode.style.width = `${width}px`;
widgetContainer.style.left = `${leftMargin}px`;
}
override dispose() {
this._notebookEditor.changeViewZones(accessor => {
accessor.removeZone(this.id);
});
this.domNode.remove();
super.dispose();
}
}
export interface INotebookCellTextModelLike { uri: URI; viewType: string }
class NotebookCellTextModelLikeId {
static str(k: INotebookCellTextModelLike): string {
return `${k.viewType}/${k.uri.toString()}`;
}
static obj(s: string): INotebookCellTextModelLike {
const idx = s.indexOf('/');
return {
viewType: s.substring(0, idx),
uri: URI.parse(s.substring(idx + 1))
};
}
}
export class NotebookChatController extends Disposable implements INotebookEditorContribution {
static id: string = 'workbench.notebook.chatController';
static counter: number = 0;
public static get(editor: INotebookEditor): NotebookChatController | null {
return editor.getContribution<NotebookChatController>(NotebookChatController.id);
}
// History
private static _storageKey = 'inline-chat-history';
private static _promptHistory: string[] = [];
private _historyOffset: number = -1;
private _historyCandidate: string = '';
private _historyUpdate: (prompt: string) => void;
private _promptCache = new LRUCache<string, string>(1000, 0.7);
private readonly _onDidChangePromptCache = this._register(new Emitter<{ cell: URI }>());
readonly onDidChangePromptCache = this._onDidChangePromptCache.event;
private _strategy: EditStrategy | undefined;
private _sessionCtor: CancelablePromise<void> | undefined;
private _activeRequestCts?: CancellationTokenSource;
private readonly _ctxHasActiveRequest: IContextKey<boolean>;
private readonly _ctxCellWidgetFocused: IContextKey<boolean>;
private readonly _ctxUserDidEdit: IContextKey<boolean>;
private readonly _ctxOuterFocusPosition: IContextKey<'above' | 'below' | ''>;
private readonly _userEditingDisposables = this._register(new DisposableStore());
private readonly _widgetDisposableStore = this._register(new DisposableStore());
private _focusTracker: IFocusTracker | undefined;
private _widget: NotebookChatWidget | undefined;
private readonly _model: MutableDisposable<ChatModel> = this._register(new MutableDisposable());
constructor(
private readonly _notebookEditor: INotebookEditor,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
@IModelService private readonly _modelService: IModelService,
@ILanguageService private readonly _languageService: ILanguageService,
@INotebookExecutionStateService private _executionStateService: INotebookExecutionStateService,
@IStorageService private readonly _storageService: IStorageService,
@IChatService private readonly _chatService: IChatService
) {
super();
this._ctxHasActiveRequest = CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST.bindTo(this._contextKeyService);
this._ctxCellWidgetFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService);
this._ctxUserDidEdit = CTX_NOTEBOOK_CHAT_USER_DID_EDIT.bindTo(this._contextKeyService);
this._ctxOuterFocusPosition = CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.bindTo(this._contextKeyService);
this._registerFocusTracker();
NotebookChatController._promptHistory = JSON.parse(this._storageService.get(NotebookChatController._storageKey, StorageScope.PROFILE, '[]'));
this._historyUpdate = (prompt: string) => {
const idx = NotebookChatController._promptHistory.indexOf(prompt);
if (idx >= 0) {
NotebookChatController._promptHistory.splice(idx, 1);
}
NotebookChatController._promptHistory.unshift(prompt);
this._historyOffset = -1;
this._historyCandidate = '';
this._storageService.store(NotebookChatController._storageKey, JSON.stringify(NotebookChatController._promptHistory), StorageScope.PROFILE, StorageTarget.USER);
};
}
private _registerFocusTracker() {
this._register(this._notebookEditor.onDidChangeFocus(() => {
if (!this._widget) {
this._ctxOuterFocusPosition.set('');
return;
}
const widgetIndex = this._widget.afterModelPosition;
const focus = this._notebookEditor.getFocus().start;
if (focus + 1 === widgetIndex) {
this._ctxOuterFocusPosition.set('above');
} else if (focus === widgetIndex) {
this._ctxOuterFocusPosition.set('below');
} else {
this._ctxOuterFocusPosition.set('');
}
}));
}
run(index: number, input: string | undefined, autoSend: boolean | undefined): void {
if (this._widget) {
if (this._widget.afterModelPosition !== index) {
const window = getWindow(this._widget.domNode);
this._disposeWidget();
scheduleAtNextAnimationFrame(window, () => {
this._createWidget(index, input, autoSend, undefined);
});
}
return;
}
this._createWidget(index, input, autoSend, undefined);
// TODO: reveal widget to the center if it's out of the viewport
}
restore(editingCell: ICellViewModel, input: string) {
if (!this._notebookEditor.hasModel()) {
return;
}
const index = this._notebookEditor.textModel.cells.indexOf(editingCell.model);
if (index < 0) {
return;
}
if (this._widget) {
if (this._widget.afterModelPosition !== index) {
this._disposeWidget();
const window = getWindow(this._widget.domNode);
scheduleAtNextAnimationFrame(window, () => {
this._createWidget(index, input, false, editingCell);
});
}
return;
}
this._createWidget(index, input, false, editingCell);
}
private _disposeWidget() {
this._widget?.dispose();
this._widget = undefined;
this._widgetDisposableStore.clear();
this._historyOffset = -1;
this._historyCandidate = '';
}
private _createWidget(index: number, input: string | undefined, autoSend: boolean | undefined, initEditingCell: ICellViewModel | undefined) {
if (!this._notebookEditor.hasModel()) {
return;
}
// Clear the widget if it's already there
this._widgetDisposableStore.clear();
const viewZoneContainer = document.createElement('div');
viewZoneContainer.classList.add('monaco-editor');
const widgetContainer = document.createElement('div');
widgetContainer.style.position = 'absolute';
viewZoneContainer.appendChild(widgetContainer);
this._focusTracker = this._widgetDisposableStore.add(trackFocus(viewZoneContainer));
this._widgetDisposableStore.add(this._focusTracker.onDidFocus(() => {
this._updateNotebookEditorFocusNSelections();
}));
const fakeParentEditorElement = document.createElement('div');
const fakeParentEditor = this._widgetDisposableStore.add(this._instantiationService.createInstance(
CodeEditorWidget,
fakeParentEditorElement,
{
},
{ isSimpleWidget: true }
));
const inputBoxFragment = `notebook-chat-input-${NotebookChatController.counter++}`;
const notebookUri = this._notebookEditor.textModel.uri;
const inputUri = notebookUri.with({ scheme: Schemas.untitled, fragment: inputBoxFragment });
const result: ITextModel = this._modelService.createModel('', null, inputUri, false);
fakeParentEditor.setModel(result);
const inlineChatWidget = this._widgetDisposableStore.add(this._instantiationService.createInstance(
InlineChatWidget,
{
location: ChatAgentLocation.Notebook,
resolveData: () => {
const sessionInputUri = this.getSessionInputUri();
if (!sessionInputUri) {
return undefined;
}
return {
type: ChatAgentLocation.Notebook,
sessionInputUri
};
}
},
{
statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS,
chatWidgetViewOptions: {
rendererOptions: {
renderTextEditsAsSummary: (uri) => {
return isEqual(uri, this._widget?.parentEditor.getModel()?.uri)
|| isEqual(uri, this._notebookEditor.textModel?.uri);
}
},
menus: {
telemetrySource: 'notebook-generate-cell'
}
}
}
));
inlineChatWidget.placeholder = localize('default.placeholder', "Ask or edit in context");
inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect"));
widgetContainer.appendChild(inlineChatWidget.domNode);
this._notebookEditor.changeViewZones(accessor => {
const notebookViewZone = {
afterModelPosition: index,
heightInPx: 80,
domNode: viewZoneContainer
};
const id = accessor.addZone(notebookViewZone);
this._scrollWidgetIntoView(index);
this._widget = new NotebookChatWidget(
this._notebookEditor,
id,
notebookViewZone,
viewZoneContainer,
widgetContainer,
inlineChatWidget,
fakeParentEditor,
this._languageService
);
if (initEditingCell) {
this._widget.restoreEditingCell(initEditingCell);
this._updateUserEditingState();
}
this._ctxCellWidgetFocused.set(true);
disposableTimeout(() => {
this._focusWidget();
}, 0, this._store);
this._sessionCtor = createCancelablePromise<void>(async token => {
await this._startSession(token);
assertType(this._model.value);
const model = this._model.value;
this._widget?.inlineChatWidget.setChatModel(model);
if (fakeParentEditor.hasModel()) {
if (this._widget) {
this._focusWidget();
}
if (this._widget && input) {
this._widget.inlineChatWidget.value = input;
if (autoSend) {
this.acceptInput();
}
}
}
});
});
}
private async _startSession(token: CancellationToken) {
if (!this._model.value) {
this._model.value = this._chatService.startSession(ChatAgentLocation.EditorInline, token);
if (!this._model.value) {
throw new Error('Failed to start chat session');
}
}
this._strategy = new EditStrategy();
}
private _scrollWidgetIntoView(index: number) {
if (index === 0 || this._notebookEditor.getLength() === 0) {
// the cell is at the beginning of the notebook
this._notebookEditor.revealOffsetInCenterIfOutsideViewport(0);
} else {
// the cell is at the end of the notebook
const previousCell = this._notebookEditor.cellAt(Math.min(index - 1, this._notebookEditor.getLength() - 1));
if (previousCell) {
const cellTop = this._notebookEditor.getAbsoluteTopOfElement(previousCell);
const cellHeight = this._notebookEditor.getHeightOfElement(previousCell);
this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight + 48 /** center of the dialog */);
}
}
}
private _focusWidget() {
if (!this._widget) {
return;
}
this._updateNotebookEditorFocusNSelections();
this._widget.focus();
}
private _updateNotebookEditorFocusNSelections() {
if (!this._widget) {
return;
}
this._widget.updateNotebookEditorFocusNSelections();
}
hasSession(chatModel: IChatModel) {
return this._model.value === chatModel;
}
getSessionInputUri() {
return this._widget?.parentEditor.getModel()?.uri;
}
async acceptInput() {
assertType(this._widget);
await this._sessionCtor;
assertType(this._model.value);
assertType(this._strategy);
const lastInput = this._widget.inlineChatWidget.value;
this._historyUpdate(lastInput);
const editor = this._widget.parentEditor;
const textModel = editor.getModel();
if (!editor.hasModel() || !textModel) {
return;
}
if (this._widget.editingCell && this._widget.editingCell.textBuffer.getLength() > 0) {
// it already contains some text, clear it
const ref = await this._widget.editingCell.resolveTextModel();
ref.setValue('');
}
const editingCellIndex = this._widget.editingCell ? this._notebookEditor.getCellIndex(this._widget.editingCell) : undefined;
if (editingCellIndex !== undefined) {
this._notebookEditor.setSelections([{
start: editingCellIndex,
end: editingCellIndex + 1
}]);
} else {
// Update selection to the widget index
this._notebookEditor.setSelections([{
start: this._widget.afterModelPosition,
end: this._widget.afterModelPosition
}]);
}
this._ctxHasActiveRequest.set(true);
this._activeRequestCts?.cancel();
this._activeRequestCts = new CancellationTokenSource();
const store = new DisposableStore();
try {
this._ctxHasActiveRequest.set(true);
const progressiveEditsQueue = new Queue();
const progressiveEditsClock = StopWatch.create();
const progressiveEditsAvgDuration = new MovingAverage();
const progressiveEditsCts = new CancellationTokenSource(this._activeRequestCts.token);
const responsePromise = new DeferredPromise<void>();
const response = await this._widget.inlineChatWidget.chatWidget.acceptInput();
if (response) {
let lastLength = 0;
store.add(response.onDidChange(e => {
if (response.isCanceled) {
progressiveEditsCts.cancel();
responsePromise.complete();
return;
}
if (response.isComplete) {
responsePromise.complete();
return;
}
const edits = response.response.value.map(part => {
if (part.kind === 'textEditGroup'
// && isEqual(part.uri, this._session?.textModelN.uri)
) {
return part.edits;
} else {
return [];
}
}).flat();
const newEdits = edits.slice(lastLength);
// console.log('NEW edits', newEdits, edits);
if (newEdits.length === 0) {
return; // NO change
}
lastLength = edits.length;
progressiveEditsAvgDuration.update(progressiveEditsClock.elapsed());
progressiveEditsClock.reset();
progressiveEditsQueue.queue(async () => {
for (const edits of newEdits) {
await this._makeChanges(edits, {
duration: progressiveEditsAvgDuration.value,
token: progressiveEditsCts.token
});
}
});
}));
}
await responsePromise.p;
await progressiveEditsQueue.whenIdle();
this._userEditingDisposables.clear();
// monitor user edits
const editingCell = this._widget.getEditingCell();
if (editingCell) {
this._userEditingDisposables.add(editingCell.model.onDidChangeContent(() => this._updateUserEditingState()));
this._userEditingDisposables.add(editingCell.model.onDidChangeLanguage(() => this._updateUserEditingState()));
this._userEditingDisposables.add(editingCell.model.onDidChangeMetadata(() => this._updateUserEditingState()));
this._userEditingDisposables.add(editingCell.model.onDidChangeInternalMetadata(() => this._updateUserEditingState()));
this._userEditingDisposables.add(editingCell.model.onDidChangeOutputs(() => this._updateUserEditingState()));
this._userEditingDisposables.add(this._executionStateService.onDidChangeExecution(e => {
if (e.type === NotebookExecutionType.cell && e.affectsCell(editingCell.uri)) {
this._updateUserEditingState();
}
}));
}
} catch (e) {
} finally {
store.dispose();
this._ctxHasActiveRequest.set(false);
this._widget.inlineChatWidget.updateInfo('');
this._widget.inlineChatWidget.updateToolbar(true);
}
}
private async _makeChanges(edits: TextEdit[], opts: ProgressingEditsOptions | undefined) {
assertType(this._strategy);
assertType(this._widget);
const editingCell = await this._widget.getOrCreateEditingCell();
if (!editingCell) {
return;
}
const editor = editingCell.editor;
const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(editor.getModel().uri, edits);
// this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, edits, moreMinimalEdits);
if (moreMinimalEdits?.length === 0) {
// nothing left to do
return;
}
const actualEdits = !opts && moreMinimalEdits ? moreMinimalEdits : edits;
const editOperations = actualEdits.map(TextEdit.asEditOperation);
try {
if (opts) {
await this._strategy.makeProgressiveChanges(editor, editOperations, opts);
} else {
await this._strategy.makeChanges(editor, editOperations);
}
} finally {
}
}
private _updateUserEditingState() {
this._ctxUserDidEdit.set(true);
}
async acceptSession() {
assertType(this._model);
assertType(this._strategy);
const editor = this._widget?.parentEditor;
if (!editor?.hasModel()) {
return;
}
const editingCell = this._widget?.getEditingCell();
if (editingCell && this._notebookEditor.hasModel()) {
const cellId = NotebookCellTextModelLikeId.str({ uri: editingCell.uri, viewType: this._notebookEditor.textModel.viewType });
if (this._widget?.inlineChatWidget.value) {
this._promptCache.set(cellId, this._widget.inlineChatWidget.value);
}
this._onDidChangePromptCache.fire({ cell: editingCell.uri });
}
try {
this._model.clear();
} catch (_err) { }
this.dismiss(false);
}
async focusAbove() {
if (!this._widget) {
return;
}
const index = this._widget.afterModelPosition;
const prev = index - 1;
if (prev < 0) {
return;
}
const cell = this._notebookEditor.cellAt(prev);
if (!cell) {
return;
}
await this._notebookEditor.focusNotebookCell(cell, 'editor');
}
async focusNext() {
if (!this._widget) {
return;
}
const index = this._widget.afterModelPosition;
const cell = this._notebookEditor.cellAt(index);
if (!cell) {
return;
}
await this._notebookEditor.focusNotebookCell(cell, 'editor');
}
hasFocus() {
return this._widget?.hasFocus() ?? false;
}
focus() {
this._focusWidget();
}
focusNearestWidget(index: number, direction: 'above' | 'below') {
switch (direction) {
case 'above':
if (this._widget?.afterModelPosition === index) {
this._focusWidget();
}
break;
case 'below':
if (this._widget?.afterModelPosition === index + 1) {
this._focusWidget();
}
break;
default:
break;
}
}
populateHistory(up: boolean) {
if (!this._widget) {
return;
}
const len = NotebookChatController._promptHistory.length;
if (len === 0) {
return;
}
if (this._historyOffset === -1) {
// remember the current value
this._historyCandidate = this._widget.inlineChatWidget.value;
}
const newIdx = this._historyOffset + (up ? 1 : -1);
if (newIdx >= len) {
// reached the end
return;
}
let entry: string;
if (newIdx < 0) {
entry = this._historyCandidate;
this._historyOffset = -1;
} else {
entry = NotebookChatController._promptHistory[newIdx];
this._historyOffset = newIdx;
}
this._widget.inlineChatWidget.value = entry;
this._widget.inlineChatWidget.selectAll();
}
async cancelCurrentRequest(discard: boolean) {
this._activeRequestCts?.cancel();
}
getEditingCell() {
return this._widget?.getEditingCell();
}
discard() {
this._activeRequestCts?.cancel();
this._widget?.discardChange();
this.dismiss(true);
}
dismiss(discard: boolean) {
const widget = this._widget;
const widgetIndex = widget?.afterModelPosition;
const currentFocus = this._notebookEditor.getFocus();
const isWidgetFocused = currentFocus.start === widgetIndex && currentFocus.end === widgetIndex;
if (widget && isWidgetFocused) {
// change focus only when the widget is focused
const editingCell = widget.getEditingCell();
const shouldFocusEditingCell = editingCell && !discard;
const shouldFocusTopCell = widgetIndex === 0 && this._notebookEditor.getLength() > 0;
const shouldFocusAboveCell = widgetIndex !== 0 && this._notebookEditor.cellAt(widgetIndex - 1);
if (shouldFocusEditingCell) {
this._notebookEditor.focusNotebookCell(editingCell, 'container');
} else if (shouldFocusTopCell) {
this._notebookEditor.focusNotebookCell(this._notebookEditor.cellAt(0)!, 'container');
} else if (shouldFocusAboveCell) {
this._notebookEditor.focusNotebookCell(this._notebookEditor.cellAt(widgetIndex - 1)!, 'container');
}
}
this._ctxCellWidgetFocused.set(false);
this._ctxUserDidEdit.set(false);
this._sessionCtor?.cancel();
this._sessionCtor = undefined;
this._model.clear();
this._widget?.dispose();
this._widget = undefined;
this._widgetDisposableStore.clear();
}
// check if a cell is generated by prompt by checking prompt cache
isCellGeneratedByChat(cell: ICellViewModel) {
if (!this._notebookEditor.hasModel()) {
// no model attached yet
return false;
}
const cellId = NotebookCellTextModelLikeId.str({ uri: cell.uri, viewType: this._notebookEditor.textModel.viewType });
return this._promptCache.has(cellId);
}
// get prompt from cache
getPromptFromCache(cell: ICellViewModel) {
if (!this._notebookEditor.hasModel()) {
// no model attached yet
return undefined;
}
const cellId = NotebookCellTextModelLikeId.str({ uri: cell.uri, viewType: this._notebookEditor.textModel.viewType });
return this._promptCache.get(cellId);
}
public override dispose(): void {
this.dismiss(false);
super.dispose();
}
}
export class EditStrategy {
private _editCount: number = 0;
constructor() {
}
async makeProgressiveChanges(editor: IActiveCodeEditor, edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise<void> {
// push undo stop before first edit
if (++this._editCount === 1) {
editor.pushUndoStop();
}
const durationInSec = opts.duration / 1000;
for (const edit of edits) {
const wordCount = countWords(edit.text ?? '');
const speed = wordCount / durationInSec;
// console.log({ durationInSec, wordCount, speed: wordCount / durationInSec });
await performAsyncTextEdit(editor.getModel(), asProgressiveEdit(new WindowIntervalTimer(), edit, speed, opts.token));
}
}
async makeChanges(editor: IActiveCodeEditor, edits: ISingleEditOperation[]): Promise<void> {
const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => {
let last: Position | null = null;
for (const edit of undoEdits) {
last = !last || last.isBefore(edit.range.getEndPosition()) ? edit.range.getEndPosition() : last;
// this._inlineDiffDecorations.collectEditOperation(edit);
}
return last && [Selection.fromPositions(last)];
};
// push undo stop before first edit
if (++this._editCount === 1) {
editor.pushUndoStop();
}
editor.executeEdits('inline-chat-live', edits, cursorStateComputerAndInlineDiffCollection);
}
}
registerNotebookContribution(NotebookChatController.id, NotebookChatController);
@@ -207,7 +207,7 @@ export abstract class NotebookMultiCellAction extends Action2 {
super(desc);
}
parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined {
return undefined;
}
@@ -18,7 +18,6 @@ import { ServicesAccessor } from '../../../../../platform/instantiation/common/i
import { IDebugService } from '../../../debug/common/debug.js';
import { CTX_INLINE_CHAT_FOCUSED } from '../../../inlineChat/common/inlineChat.js';
import { insertCell } from './cellOperations.js';
import { NotebookChatController } from './chat/notebookChatController.js';
import { CELL_TITLE_CELL_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, NotebookMultiCellAction, cellExecutionArgs, getContextFromActiveEditor, getContextFromUri, parseMultiCellExecutionArgs } from './coreActions.js';
import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, IActiveNotebookEditor, ICellViewModel, IFocusNotebookCellOptions, ScrollToRevealBehavior } from '../notebookBrowser.js';
import * as icons from '../notebookIcons.js';
@@ -285,7 +284,7 @@ registerAction2(class ExecuteCell extends NotebookMultiCellAction {
});
}
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined {
return parseMultiCellExecutionArgs(accessor, ...args);
}
@@ -297,21 +296,6 @@ registerAction2(class ExecuteCell extends NotebookMultiCellAction {
await context.notebookEditor.focusNotebookCell(context.cell, 'container', { skipReveal: true });
}
const chatController = NotebookChatController.get(context.notebookEditor);
const editingCell = chatController?.getEditingCell();
if (chatController?.hasFocus() && editingCell) {
const group = editorGroupsService.activeGroup;
if (group) {
if (group.activeEditor) {
group.pinEditor(group.activeEditor);
}
}
await context.notebookEditor.executeNotebookCells([editingCell]);
return;
}
await runCell(editorGroupsService, context, editorService);
}
});
@@ -342,7 +326,7 @@ registerAction2(class ExecuteAboveCells extends NotebookMultiCellAction {
});
}
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined {
return parseMultiCellExecutionArgs(accessor, ...args);
}
@@ -389,7 +373,7 @@ registerAction2(class ExecuteCellAndBelow extends NotebookMultiCellAction {
});
}
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined {
return parseMultiCellExecutionArgs(accessor, ...args);
}
@@ -424,7 +408,7 @@ registerAction2(class ExecuteCellFocusContainer extends NotebookMultiCellAction
});
}
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined {
return parseMultiCellExecutionArgs(accessor, ...args);
}
@@ -502,7 +486,7 @@ registerAction2(class CancelExecuteCell extends NotebookMultiCellAction {
});
}
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined {
return parseMultiCellExecutionArgs(accessor, ...args);
}
@@ -17,7 +17,6 @@ import { INotebookActionContext, NotebookAction } from './coreActions.js';
import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE } from '../../common/notebookContextKeys.js';
import { CellViewModel } from '../viewModel/notebookViewModelImpl.js';
import { CellKind, NotebookSetting } from '../../common/notebookCommon.js';
import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from './chat/notebookChatContext.js';
import { INotebookKernelHistoryService } from '../../common/notebookKernelService.js';
const INSERT_CODE_CELL_ABOVE_COMMAND_ID = 'notebook.cell.insertCodeCellAbove';
@@ -114,7 +113,7 @@ registerAction2(class InsertCodeCellBelowAction extends InsertCellCommand {
title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below"),
keybinding: {
primary: KeyMod.CtrlCmd | KeyCode.Enter,
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, InputFocusedContext.toNegated(), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')),
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, InputFocusedContext.toNegated()),
weight: KeybindingWeight.WorkbenchContrib
},
menu: {
@@ -7,14 +7,13 @@ import { Disposable, DisposableStore } from '../../../../../../base/common/lifec
import { autorun } from '../../../../../../base/common/observable.js';
import { IContextKey, IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js';
import { NotebookChatController } from '../../controller/chat/notebookChatController.js';
import { CellEditState, CellFocusMode, ICellViewModel, INotebookEditorDelegate } from '../../notebookBrowser.js';
import { CellViewModelStateChangeEvent } from '../../notebookViewEvents.js';
import { CellContentPart } from '../cellPart.js';
import { CodeCellViewModel } from '../../viewModel/codeCellViewModel.js';
import { MarkupCellViewModel } from '../../viewModel/markupCellViewModel.js';
import { NotebookCellExecutionState } from '../../../common/notebookCommon.js';
import { NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RESOURCE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CELL_GENERATED_BY_CHAT, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS } from '../../../common/notebookContextKeys.js';
import { NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RESOURCE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS } from '../../../common/notebookContextKeys.js';
import { INotebookExecutionStateService, NotebookExecutionType } from '../../../common/notebookExecutionStateService.js';
export class CellContextKeyPart extends CellContentPart {
@@ -47,7 +46,6 @@ export class CellContextKeyManager extends Disposable {
private cellOutputCollapsed!: IContextKey<boolean>;
private cellLineNumbers!: IContextKey<'on' | 'off' | 'inherit'>;
private cellResource!: IContextKey<string>;
private cellGeneratedByChat!: IContextKey<boolean>;
private cellHasErrorDiagnostics!: IContextKey<boolean>;
private markdownEditMode!: IContextKey<boolean>;
@@ -74,7 +72,6 @@ export class CellContextKeyManager extends Disposable {
this.cellContentCollapsed = NOTEBOOK_CELL_INPUT_COLLAPSED.bindTo(this._contextKeyService);
this.cellOutputCollapsed = NOTEBOOK_CELL_OUTPUT_COLLAPSED.bindTo(this._contextKeyService);
this.cellLineNumbers = NOTEBOOK_CELL_LINE_NUMBERS.bindTo(this._contextKeyService);
this.cellGeneratedByChat = NOTEBOOK_CELL_GENERATED_BY_CHAT.bindTo(this._contextKeyService);
this.cellResource = NOTEBOOK_CELL_RESOURCE.bindTo(this._contextKeyService);
this.cellHasErrorDiagnostics = NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS.bindTo(this._contextKeyService);
@@ -121,21 +118,10 @@ export class CellContextKeyManager extends Disposable {
this.updateForEditState();
this.updateForCollapseState();
this.updateForOutputs();
this.updateForChat();
this.cellLineNumbers.set(this.element!.lineNumbers);
this.cellResource.set(this.element!.uri.toString());
});
const chatController = NotebookChatController.get(this.notebookEditor);
if (chatController) {
this.elementDisposables.add(chatController.onDidChangePromptCache(e => {
if (e.cell.toString() === this.element!.uri.toString()) {
this.updateForChat();
}
}));
}
}
private onDidChangeState(e: CellViewModelStateChangeEvent) {
@@ -236,15 +222,4 @@ export class CellContextKeyManager extends Disposable {
this.cellHasOutputs.set(false);
}
}
private updateForChat() {
const chatController = NotebookChatController.get(this.notebookEditor);
if (!chatController || !this.element) {
this.cellGeneratedByChat.set(false);
return;
}
this.cellGeneratedByChat.set(chatController.isCellGeneratedByChat(this.element));
}
}
@@ -55,7 +55,6 @@ export const NOTEBOOK_CELL_OUTPUT_MIMETYPE = new RawContextKey<string>('notebook
export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellInputIsCollapsed', false);
export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellOutputIsCollapsed', false);
export const NOTEBOOK_CELL_RESOURCE = new RawContextKey<string>('notebookCellResource', '');
export const NOTEBOOK_CELL_GENERATED_BY_CHAT = new RawContextKey<boolean>('notebookCellGenerateByChat', false);
export const NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS = new RawContextKey<boolean>('notebookCellHasErrorDiagnostics', false);
export const NOTEBOOK_CELL_OUTPUT_MIME_TYPE_LIST_FOR_CHAT = new RawContextKey<string[]>('notebookCellOutputMimeTypeListForChat', []);
@@ -1264,7 +1264,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
for (const folder of this.workspaceContextService.getWorkspace().folders) {
const commandId = `_workbench.openFolderSettings.${folder.uri.toString()}`;
if (!CommandsRegistry.getCommand(commandId)) {
CommandsRegistry.registerCommand(commandId, (accessor: ServicesAccessor, ...args: any[]) => {
CommandsRegistry.registerCommand(commandId, (accessor: ServicesAccessor, ...args: unknown[]) => {
const groupId = getEditorGroupFromArguments(accessor, args)?.id;
if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.FOLDER) {
return this.preferencesService.openWorkspaceSettings({ jsonEditor: false, groupId });
@@ -1296,7 +1296,7 @@ class TimelinePaneCommands extends Disposable {
}));
this._register(CommandsRegistry.registerCommand('timeline.toggleFollowActiveEditor',
(accessor: ServicesAccessor, ...args: any[]) => pane.followActiveEditor = !pane.followActiveEditor
(accessor: ServicesAccessor, ...args: unknown[]) => pane.followActiveEditor = !pane.followActiveEditor
));
this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({
@@ -133,7 +133,6 @@ declare module 'vscode' {
export namespace window {
/**
* Register a completion provider for terminals.
* @param id The unique identifier of the terminal provider, used as a settings key and shown in the information hover of the suggest widget.
* @param provider The completion provider.
* @returns A {@link Disposable} that unregisters this provider when being disposed.
*
@@ -146,7 +145,7 @@ declare module 'vscode' {
* }
* });
*/
export function registerTerminalCompletionProvider<T extends TerminalCompletionItem>(id: string, provider: TerminalCompletionProvider<T>, ...triggerCharacters: string[]): Disposable;
export function registerTerminalCompletionProvider<T extends TerminalCompletionItem>(provider: TerminalCompletionProvider<T>, ...triggerCharacters: string[]): Disposable;
}
/**