mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-12 20:12:23 +01:00
Merge branch 'main' into dev/mjbvz/foolish-quelea
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
disturl="https://electronjs.org/headers"
|
||||
target="39.6.0"
|
||||
ms_build_id="13312042"
|
||||
ms_build_id="13330601"
|
||||
runtime="electron"
|
||||
ignore-scripts=false
|
||||
build_from_source="true"
|
||||
|
||||
Vendored
+1
-1
@@ -70,7 +70,7 @@
|
||||
"test/smoke/out/**": true,
|
||||
"test/automation/out/**": true,
|
||||
"test/integration/browser/out/**": true,
|
||||
"src/vs/sessions/**": true
|
||||
// "src/vs/sessions/**": true
|
||||
},
|
||||
// --- Search ---
|
||||
"search.exclude": {
|
||||
|
||||
Vendored
+3
-2
@@ -73,10 +73,11 @@
|
||||
"owner": "typescript",
|
||||
"applyTo": "closedDocuments",
|
||||
"fileLocation": [
|
||||
"absolute"
|
||||
"relative",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"pattern": {
|
||||
"regexp": "Error: ([^(]+)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\): (.*)$",
|
||||
"regexp": "\\] ([^(]+)\\((\\d+,\\d+)\\): (.*)$",
|
||||
"file": 1,
|
||||
"location": 2,
|
||||
"message": 3
|
||||
|
||||
@@ -8,6 +8,7 @@ import { EventEmitter } from 'events';
|
||||
EventEmitter.defaultMaxListeners = 100;
|
||||
|
||||
import es from 'event-stream';
|
||||
import fancyLog from 'fancy-log';
|
||||
import glob from 'glob';
|
||||
import gulp from 'gulp';
|
||||
import filter from 'gulp-filter';
|
||||
@@ -27,6 +28,25 @@ import watcher from './lib/watch/index.ts';
|
||||
const root = path.dirname(import.meta.dirname);
|
||||
const commit = getVersion(root);
|
||||
|
||||
// Tracks active extension compilations to emit aggregate
|
||||
// "Starting compilation" / "Finished compilation" messages
|
||||
// that the problem matcher in tasks.json relies on.
|
||||
let activeExtensionCompilations = 0;
|
||||
|
||||
function onExtensionCompilationStart(): void {
|
||||
if (activeExtensionCompilations === 0) {
|
||||
fancyLog('Starting compilation');
|
||||
}
|
||||
activeExtensionCompilations++;
|
||||
}
|
||||
|
||||
function onExtensionCompilationEnd(): void {
|
||||
activeExtensionCompilations--;
|
||||
if (activeExtensionCompilations === 0) {
|
||||
fancyLog('Finished compilation');
|
||||
}
|
||||
}
|
||||
|
||||
// To save 250ms for each gulp startup, we are caching the result here
|
||||
// const compilations = glob.sync('**/tsconfig.json', {
|
||||
// cwd: extensionsPath,
|
||||
@@ -175,7 +195,25 @@ const tasks = compilations.map(function (tsconfigFile) {
|
||||
const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'], { dot: true }));
|
||||
const watchInput = watcher(src, { ...srcOpts, ...{ readDelay: 200 } });
|
||||
const watchNonTs = watchInput.pipe(filter(['**', '!**/*.ts'], { dot: true })).pipe(gulp.dest(out));
|
||||
const tsgoStream = watchInput.pipe(util.debounce(() => createTsgoStream(absolutePath, { taskName: 'extensions' }, () => rewriteTsgoSourceMappingUrlsIfNeeded(false, out, baseUrl)), 200));
|
||||
const tsgoStream = watchInput.pipe(util.debounce(() => {
|
||||
onExtensionCompilationStart();
|
||||
const stream = createTsgoStream(absolutePath, { taskName: 'extensions' }, () => rewriteTsgoSourceMappingUrlsIfNeeded(false, out, baseUrl));
|
||||
// Wrap in a result stream that always emits 'end' (even on
|
||||
// error) so the debounce resets to idle and can process future
|
||||
// file changes. Errors from tsgo (e.g. type errors causing a
|
||||
// non-zero exit code) are already reported by spawnTsgo's
|
||||
// runReporter, so swallowing the stream error is safe.
|
||||
const result = es.through();
|
||||
stream.on('end', () => {
|
||||
onExtensionCompilationEnd();
|
||||
result.emit('end');
|
||||
});
|
||||
stream.on('error', () => {
|
||||
onExtensionCompilationEnd();
|
||||
result.emit('end');
|
||||
});
|
||||
return result;
|
||||
}, 200));
|
||||
const watchStream = es.merge(nonts.pipe(gulp.dest(out)), watchNonTs, tsgoStream);
|
||||
|
||||
return watchStream;
|
||||
|
||||
@@ -368,8 +368,18 @@ function generateExtensionPointNames() {
|
||||
}, function () {
|
||||
collectedNames.sort();
|
||||
const content = JSON.stringify(collectedNames, undefined, '\t') + '\n';
|
||||
const filePath = 'vs/workbench/services/extensions/common/extensionPoints.json';
|
||||
try {
|
||||
const existing = fs.readFileSync(path.join('src', filePath), 'utf-8');
|
||||
if (existing.replace(/\r\n/g, '\n') === content) {
|
||||
this.emit('end');
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// File doesn't exist yet, emit it
|
||||
}
|
||||
this.emit('data', new File({
|
||||
path: 'vs/workbench/services/extensions/common/extensionPoints.json',
|
||||
path: filePath,
|
||||
contents: Buffer.from(content)
|
||||
}));
|
||||
this.emit('end');
|
||||
|
||||
@@ -441,7 +441,7 @@ interface IExtensionManifest {
|
||||
/**
|
||||
* Loosely based on `getExtensionKind` from `src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts`
|
||||
*/
|
||||
function isWebExtension(manifest: IExtensionManifest): boolean {
|
||||
export function isWebExtension(manifest: IExtensionManifest): boolean {
|
||||
if (Boolean(manifest.browser)) {
|
||||
return true;
|
||||
}
|
||||
@@ -578,11 +578,11 @@ export function packageMarketplaceExtensionsStream(forWeb: boolean): Stream {
|
||||
}
|
||||
|
||||
export interface IScannedBuiltinExtension {
|
||||
extensionPath: string;
|
||||
packageJSON: any;
|
||||
packageNLS?: any;
|
||||
readmePath?: string;
|
||||
changelogPath?: string;
|
||||
readonly extensionPath: string;
|
||||
readonly packageJSON: unknown;
|
||||
readonly packageNLS: unknown | undefined;
|
||||
readonly readmePath: string | undefined;
|
||||
readonly changelogPath: string | undefined;
|
||||
}
|
||||
|
||||
export function scanBuiltinExtensions(extensionsRoot: string, exclude: string[] = []): IScannedBuiltinExtension[] {
|
||||
|
||||
@@ -211,10 +211,23 @@ function scanDirectory(dir: string): string[] {
|
||||
return names;
|
||||
}
|
||||
|
||||
function normalize(s: string): string {
|
||||
return s.replace(/\r\n/g, '\n');
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
const names = scanDirectory(path.join(srcDir, 'vs', 'workbench'));
|
||||
names.sort();
|
||||
const output = JSON.stringify(names, undefined, '\t') + '\n';
|
||||
try {
|
||||
const existing = fs.readFileSync(outputPath, 'utf-8');
|
||||
if (normalize(existing) === normalize(output)) {
|
||||
console.log(`No changes to ${path.relative(rootDir, outputPath)}`);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// File doesn't exist yet, write it
|
||||
}
|
||||
fs.writeFileSync(outputPath, output, 'utf-8');
|
||||
console.log(`Wrote ${names.length} extension points to ${path.relative(rootDir, outputPath)}`);
|
||||
}
|
||||
|
||||
+27
-16
@@ -12,13 +12,17 @@ import * as path from 'path';
|
||||
const root = path.dirname(path.dirname(import.meta.dirname));
|
||||
const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
||||
const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
|
||||
const timestampRegex = /^\[\d{2}:\d{2}:\d{2}\]\s*/;
|
||||
|
||||
export function spawnTsgo(projectPath: string, config: { taskName: string; noEmit?: boolean }, onComplete?: () => Promise<void> | void): Promise<void> {
|
||||
function runReporter(stdError: string) {
|
||||
const matches = (stdError || '').match(/error \w+: (.+)?/g);
|
||||
fancyLog(`Finished ${ansiColors.green(config.taskName)} ${projectPath} with ${matches ? matches.length : 0} errors.`);
|
||||
for (const match of matches || []) {
|
||||
fancyLog.error(match);
|
||||
function runReporter(output: string) {
|
||||
const lines = (output || '').split('\n');
|
||||
const errorLines = lines.filter(line => /error \w+:/.test(line));
|
||||
if (errorLines.length > 0) {
|
||||
fancyLog(`Finished ${ansiColors.green(config.taskName)} ${projectPath} with ${errorLines.length} errors.`);
|
||||
for (const line of errorLines) {
|
||||
fancyLog(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,21 +38,28 @@ export function spawnTsgo(projectPath: string, config: { taskName: string; noEmi
|
||||
shell: true
|
||||
});
|
||||
|
||||
const handleData = (data: Buffer) => {
|
||||
const lines = data.toString()
|
||||
.split(/\r?\n/)
|
||||
.map(line => line.replace(ansiRegex, '').trim())
|
||||
.filter(line => line.length > 0)
|
||||
.filter(line => !/Starting compilation|File change detected|Compilation complete/i.test(line));
|
||||
let stdoutData = '';
|
||||
let stderrData = '';
|
||||
|
||||
runReporter(lines.join('\n'));
|
||||
};
|
||||
|
||||
child.stdout?.on('data', handleData);
|
||||
child.stderr?.on('data', handleData);
|
||||
child.stdout?.on('data', (data: Buffer) => {
|
||||
stdoutData += data.toString();
|
||||
});
|
||||
child.stderr?.on('data', (data: Buffer) => {
|
||||
stderrData += data.toString();
|
||||
});
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
child.on('exit', code => {
|
||||
const allOutput = stdoutData + '\n' + stderrData;
|
||||
const lines = allOutput
|
||||
.split(/\r?\n/)
|
||||
.map(line => line.replace(ansiRegex, '').trim())
|
||||
.map(line => line.replace(timestampRegex, ''))
|
||||
.filter(line => line.length > 0)
|
||||
.filter(line => !/Starting compilation|File change detected|Compilation complete/i.test(line));
|
||||
|
||||
runReporter(lines.join('\n'));
|
||||
|
||||
if (code === 0) {
|
||||
Promise.resolve(onComplete?.()).then(() => resolve(), reject);
|
||||
} else {
|
||||
|
||||
+27
-16
@@ -15,6 +15,7 @@ import { getVersion } from '../lib/getVersion.ts';
|
||||
import product from '../../product.json' with { type: 'json' };
|
||||
import packageJson from '../../package.json' with { type: 'json' };
|
||||
import { useEsbuildTranspile } from '../buildConfig.ts';
|
||||
import { isWebExtension, type IScannedBuiltinExtension } from '../lib/extensions.ts';
|
||||
|
||||
const globAsync = promisify(glob);
|
||||
|
||||
@@ -378,33 +379,43 @@ async function cleanDir(dir: string): Promise<void> {
|
||||
* Scan for built-in extensions in the given directory.
|
||||
* Returns an array of extension entries for the builtinExtensionsScannerService.
|
||||
*/
|
||||
function scanBuiltinExtensions(extensionsRoot: string): Array<{ extensionPath: string; packageJSON: unknown }> {
|
||||
const result: Array<{ extensionPath: string; packageJSON: unknown }> = [];
|
||||
function scanBuiltinExtensions(extensionsRoot: string): Array<IScannedBuiltinExtension> {
|
||||
const scannedExtensions: Array<IScannedBuiltinExtension> = [];
|
||||
const extensionsPath = path.join(REPO_ROOT, extensionsRoot);
|
||||
|
||||
if (!fs.existsSync(extensionsPath)) {
|
||||
return result;
|
||||
return scannedExtensions;
|
||||
}
|
||||
|
||||
for (const entry of fs.readdirSync(extensionsPath, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) {
|
||||
for (const extensionFolder of fs.readdirSync(extensionsPath)) {
|
||||
const packageJSONPath = path.join(extensionsPath, extensionFolder, 'package.json');
|
||||
if (!fs.existsSync(packageJSONPath)) {
|
||||
continue;
|
||||
}
|
||||
const packageJsonPath = path.join(extensionsPath, entry.name, 'package.json');
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
try {
|
||||
const packageJSON = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
result.push({
|
||||
extensionPath: entry.name,
|
||||
packageJSON
|
||||
});
|
||||
} catch (e) {
|
||||
// Skip invalid extensions
|
||||
try {
|
||||
const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath, 'utf8'));
|
||||
if (!isWebExtension(packageJSON)) {
|
||||
continue;
|
||||
}
|
||||
const children = fs.readdirSync(path.join(extensionsPath, extensionFolder));
|
||||
const packageNLSPath = children.filter(child => child === 'package.nls.json')[0];
|
||||
const packageNLS = packageNLSPath ? JSON.parse(fs.readFileSync(path.join(extensionsPath, extensionFolder, packageNLSPath), 'utf8')) : undefined;
|
||||
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
|
||||
const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
|
||||
|
||||
scannedExtensions.push({
|
||||
extensionPath: extensionFolder,
|
||||
packageJSON,
|
||||
packageNLS,
|
||||
readmePath: readme ? path.join(extensionFolder, readme) : undefined,
|
||||
changelogPath: changelog ? path.join(extensionFolder, changelog) : undefined,
|
||||
});
|
||||
} catch (e) {
|
||||
// Skip invalid extensions
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return scannedExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Generated
+3
-3
@@ -1069,9 +1069,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.7",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
|
||||
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
|
||||
"version": "7.5.9",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz",
|
||||
"integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
|
||||
Generated
+24
-21
@@ -307,17 +307,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-xml": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.4.4.tgz",
|
||||
"integrity": "sha512-J4FYAqakGXcbfeZjwjMzjNcpcH4E+JtEBv+xcV1yL0Ydn/6wbQfeFKTCHh9wttAi0lmajHw7yBbHPRG+YHckZQ==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.5.0.tgz",
|
||||
"integrity": "sha512-D/sdlJBMJfx7gqoj66PKVmhDDaU6TKA49ptcolxdas29X7AfvLTmfAGLjAcIMBK7UZ2o4lygHIqVckOlQU3xWw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"tslib": "^2.6.2"
|
||||
"fast-xml-parser": "^5.0.7",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/cosmos": {
|
||||
@@ -5358,23 +5358,19 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz",
|
||||
"integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==",
|
||||
"version": "5.3.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz",
|
||||
"integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/naturalintelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"strnum": "^1.0.5"
|
||||
"strnum": "^2.1.2"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
@@ -8933,10 +8929,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
|
||||
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz",
|
||||
"integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/structured-source": {
|
||||
@@ -9433,10 +9435,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
|
||||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
|
||||
"dev": true
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"foreground": "#bfbfbf",
|
||||
"disabledForeground": "#666666",
|
||||
"errorForeground": "#f48771",
|
||||
"descriptionForeground": "#888888",
|
||||
"icon.foreground": "#888888",
|
||||
"descriptionForeground": "#8C8C8C",
|
||||
"icon.foreground": "#8C8C8C",
|
||||
"focusBorder": "#3994BCB3",
|
||||
"textBlockQuote.background": "#242526",
|
||||
"textBlockQuote.border": "#2A2B2CFF",
|
||||
@@ -16,7 +16,7 @@
|
||||
"textLink.foreground": "#48A0C7",
|
||||
"textLink.activeForeground": "#53A5CA",
|
||||
"textPreformat.background": "#262626",
|
||||
"textPreformat.foreground": "#888888",
|
||||
"textPreformat.foreground": "#8C8C8C",
|
||||
"textSeparator.foreground": "#2a2a2aFF",
|
||||
"button.background": "#3994BCF2",
|
||||
"button.foreground": "#FFFFFF",
|
||||
@@ -69,7 +69,7 @@
|
||||
"list.warningForeground": "#e5ba7d",
|
||||
"activityBar.background": "#191A1B",
|
||||
"activityBar.foreground": "#bfbfbf",
|
||||
"activityBar.inactiveForeground": "#888888",
|
||||
"activityBar.inactiveForeground": "#8C8C8C",
|
||||
"activityBar.border": "#2A2B2CFF",
|
||||
"activityBar.activeBorder": "#bfbfbf",
|
||||
"activityBar.activeFocusBorder": "#3994BCB3",
|
||||
@@ -86,7 +86,7 @@
|
||||
"titleBar.activeBackground": "#191A1B",
|
||||
"titleBar.activeForeground": "#bfbfbf",
|
||||
"titleBar.inactiveBackground": "#191A1B",
|
||||
"titleBar.inactiveForeground": "#888888",
|
||||
"titleBar.inactiveForeground": "#8C8C8C",
|
||||
"titleBar.border": "#2A2B2CFF",
|
||||
"menubar.selectionBackground": "#242526",
|
||||
"menubar.selectionForeground": "#bfbfbf",
|
||||
@@ -120,11 +120,11 @@
|
||||
"editor.lineHighlightBackground": "#242526",
|
||||
"editor.rangeHighlightBackground": "#242526",
|
||||
"editorLink.activeForeground": "#3a94bc",
|
||||
"editorWhitespace.foreground": "#8888884D",
|
||||
"editorWhitespace.foreground": "#8C8C8C4D",
|
||||
"editorIndentGuide.background": "#8384854D",
|
||||
"editorIndentGuide.activeBackground": "#838485",
|
||||
"editorRuler.foreground": "#848484",
|
||||
"editorCodeLens.foreground": "#888888",
|
||||
"editorCodeLens.foreground": "#8C8C8C",
|
||||
"editorBracketMatch.background": "#3994BC55",
|
||||
"editorBracketMatch.border": "#2A2B2CFF",
|
||||
"editorWidget.background": "#202122",
|
||||
@@ -143,12 +143,12 @@
|
||||
"peekViewEditor.matchHighlightBackground": "#3994BC33",
|
||||
"peekViewResult.background": "#191A1B",
|
||||
"peekViewResult.fileForeground": "#bfbfbf",
|
||||
"peekViewResult.lineForeground": "#888888",
|
||||
"peekViewResult.lineForeground": "#8C8C8C",
|
||||
"peekViewResult.matchHighlightBackground": "#3994BC33",
|
||||
"peekViewResult.selectionBackground": "#3994BC26",
|
||||
"peekViewResult.selectionForeground": "#bfbfbf",
|
||||
"peekViewTitle.background": "#242526",
|
||||
"peekViewTitleDescription.foreground": "#888888",
|
||||
"peekViewTitleDescription.foreground": "#8C8C8C",
|
||||
"peekViewTitleLabel.foreground": "#bfbfbf",
|
||||
"editorGutter.background": "#121314",
|
||||
"editorGutter.addedBackground": "#72C892",
|
||||
@@ -166,15 +166,15 @@
|
||||
"panel.border": "#2A2B2CFF",
|
||||
"panelTitle.activeBorder": "#3994BC",
|
||||
"panelTitle.activeForeground": "#bfbfbf",
|
||||
"panelTitle.inactiveForeground": "#888888",
|
||||
"panelTitle.inactiveForeground": "#8C8C8C",
|
||||
"statusBar.background": "#191A1B",
|
||||
"statusBar.foreground": "#888888",
|
||||
"statusBar.foreground": "#8C8C8C",
|
||||
"statusBar.border": "#2A2B2CFF",
|
||||
"statusBar.focusBorder": "#3994BCB3",
|
||||
"statusBar.debuggingBackground": "#3994BC",
|
||||
"statusBar.debuggingForeground": "#FFFFFF",
|
||||
"statusBar.noFolderBackground": "#191A1B",
|
||||
"statusBar.noFolderForeground": "#888888",
|
||||
"statusBar.noFolderForeground": "#8C8C8C",
|
||||
"statusBarItem.activeBackground": "#4B4C4D",
|
||||
"statusBarItem.hoverBackground": "#262728",
|
||||
"statusBarItem.focusBorder": "#3994BCB3",
|
||||
@@ -184,7 +184,7 @@
|
||||
"tab.activeBackground": "#121314",
|
||||
"tab.activeForeground": "#bfbfbf",
|
||||
"tab.inactiveBackground": "#191A1B",
|
||||
"tab.inactiveForeground": "#888888",
|
||||
"tab.inactiveForeground": "#8C8C8C",
|
||||
"tab.border": "#2A2B2CFF",
|
||||
"tab.lastPinnedBorder": "#2A2B2CFF",
|
||||
"tab.activeBorder": "#121314",
|
||||
@@ -192,12 +192,12 @@
|
||||
"tab.hoverBackground": "#262728",
|
||||
"tab.hoverForeground": "#bfbfbf",
|
||||
"tab.unfocusedActiveBackground": "#121314",
|
||||
"tab.unfocusedActiveForeground": "#888888",
|
||||
"tab.unfocusedActiveForeground": "#8C8C8C",
|
||||
"tab.unfocusedInactiveBackground": "#191A1B",
|
||||
"tab.unfocusedInactiveForeground": "#444444",
|
||||
"editorGroupHeader.tabsBackground": "#191A1B",
|
||||
"editorGroupHeader.tabsBorder": "#2A2B2CFF",
|
||||
"breadcrumb.foreground": "#888888",
|
||||
"breadcrumb.foreground": "#8C8C8C",
|
||||
"breadcrumb.background": "#121314",
|
||||
"breadcrumb.focusForeground": "#bfbfbf",
|
||||
"breadcrumb.activeSelectionForeground": "#bfbfbf",
|
||||
@@ -612,7 +612,7 @@
|
||||
"markup.fenced_code"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#888888"
|
||||
"foreground": "#8C8C8C"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,24 +7,25 @@
|
||||
"foreground": "#202020",
|
||||
"disabledForeground": "#BBBBBB",
|
||||
"errorForeground": "#ad0707",
|
||||
"descriptionForeground": "#555555",
|
||||
"icon.foreground": "#666666",
|
||||
"descriptionForeground": "#606060",
|
||||
"icon.foreground": "#606060",
|
||||
"focusBorder": "#0069CCFF",
|
||||
"textBlockQuote.background": "#EDEDED",
|
||||
"textBlockQuote.background": "#EAEAEA",
|
||||
"textBlockQuote.border": "#F2F3F4FF",
|
||||
"textCodeBlock.background": "#EDEDED",
|
||||
"textCodeBlock.background": "#EAEAEA",
|
||||
"textLink.foreground": "#0069CC",
|
||||
"textLink.activeForeground": "#0069CC",
|
||||
"textPreformat.foreground": "#666666",
|
||||
"textPreformat.foreground": "#606060",
|
||||
"textPreformat.background": "#ECECEC",
|
||||
"textSeparator.foreground": "#EEEEEEFF",
|
||||
"button.background": "#0069CC",
|
||||
"button.foreground": "#FFFFFF",
|
||||
"button.hoverBackground": "#0063C1",
|
||||
"button.border": "#EEEEF1",
|
||||
"button.secondaryBackground": "#EDEDED",
|
||||
"button.secondaryBackground": "#EAEAEA",
|
||||
"button.secondaryForeground": "#202020",
|
||||
"button.secondaryHoverBackground": "#EEEEEE",
|
||||
"checkbox.background": "#EDEDED",
|
||||
"button.secondaryHoverBackground": "#F2F3F4",
|
||||
"checkbox.background": "#EAEAEA",
|
||||
"checkbox.border": "#D8D8D8",
|
||||
"checkbox.foreground": "#202020",
|
||||
"dropdown.background": "#FFFFFF",
|
||||
@@ -61,11 +62,11 @@
|
||||
"badge.background": "#0069CC",
|
||||
"badge.foreground": "#FFFFFF",
|
||||
"progressBar.background": "#0069CC",
|
||||
"list.activeSelectionBackground": "#0069CC44",
|
||||
"list.activeSelectionBackground": "#0069CC1A",
|
||||
"list.activeSelectionForeground": "#202020",
|
||||
"list.inactiveSelectionBackground": "#E0E0E0",
|
||||
"list.inactiveSelectionBackground": "#DADADA99",
|
||||
"list.inactiveSelectionForeground": "#202020",
|
||||
"list.hoverBackground": "#EEEEEE",
|
||||
"list.hoverBackground": "#DADADA4f",
|
||||
"list.hoverForeground": "#202020",
|
||||
"list.dropBackground": "#0069CC15",
|
||||
"list.focusBackground": "#0069CC1A",
|
||||
@@ -77,7 +78,7 @@
|
||||
"list.warningForeground": "#667309",
|
||||
"activityBar.background": "#FAFAFD",
|
||||
"activityBar.foreground": "#202020",
|
||||
"activityBar.inactiveForeground": "#666666",
|
||||
"activityBar.inactiveForeground": "#606060",
|
||||
"activityBar.border": "#F2F3F4FF",
|
||||
"activityBar.activeBorder": "#000000",
|
||||
"activityBar.activeFocusBorder": "#0069CCFF",
|
||||
@@ -94,9 +95,9 @@
|
||||
"titleBar.activeBackground": "#FAFAFD",
|
||||
"titleBar.activeForeground": "#424242",
|
||||
"titleBar.inactiveBackground": "#FAFAFD",
|
||||
"titleBar.inactiveForeground": "#666666",
|
||||
"titleBar.inactiveForeground": "#606060",
|
||||
"titleBar.border": "#F2F3F4FF",
|
||||
"menubar.selectionBackground": "#EDEDED",
|
||||
"menubar.selectionBackground": "#EAEAEA",
|
||||
"menubar.selectionForeground": "#202020",
|
||||
"menu.background": "#FAFAFD",
|
||||
"menu.foreground": "#202020",
|
||||
@@ -111,7 +112,7 @@
|
||||
"commandCenter.border": "#D8D8D8",
|
||||
"editor.background": "#FFFFFF",
|
||||
"editor.foreground": "#202020",
|
||||
"editorLineNumber.foreground": "#666666",
|
||||
"editorLineNumber.foreground": "#606060",
|
||||
"editorLineNumber.activeForeground": "#202020",
|
||||
"editorCursor.foreground": "#202020",
|
||||
"editor.selectionBackground": "#0069CC1A",
|
||||
@@ -121,16 +122,16 @@
|
||||
"editor.wordHighlightStrongBackground": "#0069CC26",
|
||||
"editor.findMatchBackground": "#0069CC40",
|
||||
"editor.findMatchHighlightBackground": "#0069CC1A",
|
||||
"editor.findRangeHighlightBackground": "#EDEDED",
|
||||
"editor.hoverHighlightBackground": "#EDEDED",
|
||||
"editor.lineHighlightBackground": "#EDEDED40",
|
||||
"editor.rangeHighlightBackground": "#EDEDED",
|
||||
"editor.findRangeHighlightBackground": "#EAEAEA",
|
||||
"editor.hoverHighlightBackground": "#EAEAEA",
|
||||
"editor.lineHighlightBackground": "#EAEAEA40",
|
||||
"editor.rangeHighlightBackground": "#EAEAEA",
|
||||
"editorLink.activeForeground": "#0069CC",
|
||||
"editorWhitespace.foreground": "#66666640",
|
||||
"editorWhitespace.foreground": "#60606040",
|
||||
"editorIndentGuide.background": "#F7F7F740",
|
||||
"editorIndentGuide.activeBackground": "#EEEEEE",
|
||||
"editorRuler.foreground": "#F7F7F7",
|
||||
"editorCodeLens.foreground": "#666666",
|
||||
"editorCodeLens.foreground": "#606060",
|
||||
"editorBracketMatch.background": "#0069CC40",
|
||||
"editorBracketMatch.border": "#F2F3F4FF",
|
||||
"editorWidget.background": "#F0F0F3",
|
||||
@@ -148,12 +149,12 @@
|
||||
"peekViewEditor.matchHighlightBackground": "#0069CC33",
|
||||
"peekViewResult.background": "#F0F0F3",
|
||||
"peekViewResult.fileForeground": "#202020",
|
||||
"peekViewResult.lineForeground": "#666666",
|
||||
"peekViewResult.lineForeground": "#606060",
|
||||
"peekViewResult.matchHighlightBackground": "#0069CC33",
|
||||
"peekViewResult.selectionBackground": "#0069CC26",
|
||||
"peekViewResult.selectionForeground": "#202020",
|
||||
"peekViewTitle.background": "#F0F0F3",
|
||||
"peekViewTitleDescription.foreground": "#666666",
|
||||
"peekViewTitleDescription.foreground": "#606060",
|
||||
"peekViewTitleLabel.foreground": "#202020",
|
||||
"editorGutter.addedBackground": "#587c0c",
|
||||
"editorGutter.deletedBackground": "#ad0707",
|
||||
@@ -171,17 +172,17 @@
|
||||
"panel.border": "#F2F3F4FF",
|
||||
"panelTitle.activeBorder": "#000000",
|
||||
"panelTitle.activeForeground": "#202020",
|
||||
"panelTitle.inactiveForeground": "#666666",
|
||||
"panelTitle.inactiveForeground": "#606060",
|
||||
"statusBar.background": "#FAFAFD",
|
||||
"statusBar.foreground": "#666666",
|
||||
"statusBar.foreground": "#606060",
|
||||
"statusBar.border": "#F2F3F4FF",
|
||||
"statusBar.focusBorder": "#0069CCFF",
|
||||
"statusBar.debuggingBackground": "#0069CC",
|
||||
"statusBar.debuggingForeground": "#FFFFFF",
|
||||
"statusBar.noFolderBackground": "#F0F0F3",
|
||||
"statusBar.noFolderForeground": "#666666",
|
||||
"statusBar.noFolderForeground": "#606060",
|
||||
"statusBarItem.activeBackground": "#EEEEEE",
|
||||
"statusBarItem.hoverBackground": "#EEEEEE",
|
||||
"statusBarItem.hoverBackground": "#DADADA4f",
|
||||
"statusBarItem.focusBorder": "#0069CCFF",
|
||||
"statusBarItem.prominentBackground": "#0069CCDD",
|
||||
"statusBarItem.prominentForeground": "#FFFFFF",
|
||||
@@ -190,30 +191,30 @@
|
||||
"tab.activeBackground": "#FFFFFF",
|
||||
"tab.activeForeground": "#202020",
|
||||
"tab.inactiveBackground": "#FAFAFD",
|
||||
"tab.inactiveForeground": "#666666",
|
||||
"tab.inactiveForeground": "#606060",
|
||||
"tab.border": "#F2F3F4FF",
|
||||
"tab.lastPinnedBorder": "#F2F3F4FF",
|
||||
"tab.activeBorder": "#FAFAFD",
|
||||
"tab.activeBorderTop": "#000000",
|
||||
"tab.hoverBackground": "#EEEEEE",
|
||||
"tab.hoverBackground": "#DADADA4f",
|
||||
"tab.hoverForeground": "#202020",
|
||||
"tab.unfocusedActiveBackground": "#FAFAFD",
|
||||
"tab.unfocusedActiveForeground": "#666666",
|
||||
"tab.unfocusedActiveForeground": "#606060",
|
||||
"tab.unfocusedInactiveBackground": "#FAFAFD",
|
||||
"tab.unfocusedInactiveForeground": "#BBBBBB",
|
||||
"editorGroupHeader.tabsBackground": "#FAFAFD",
|
||||
"editorGroupHeader.tabsBorder": "#F2F3F4FF",
|
||||
"breadcrumb.foreground": "#666666",
|
||||
"breadcrumb.foreground": "#606060",
|
||||
"breadcrumb.background": "#FFFFFF",
|
||||
"breadcrumb.focusForeground": "#202020",
|
||||
"breadcrumb.activeSelectionForeground": "#202020",
|
||||
"breadcrumbPicker.background": "#F0F0F3",
|
||||
"notificationCenter.border": "#F2F3F4FF",
|
||||
"notificationCenterHeader.foreground": "#202020",
|
||||
"notificationCenterHeader.background": "#F0F0F3",
|
||||
"notificationCenterHeader.background": "#FAFAFD",
|
||||
"notificationToast.border": "#F2F3F4FF",
|
||||
"notifications.foreground": "#202020",
|
||||
"notifications.background": "#F0F0F3",
|
||||
"notifications.background": "#FAFAFD",
|
||||
"notifications.border": "#F2F3F4FF",
|
||||
"notificationLink.foreground": "#0069CC",
|
||||
"notificationsWarningIcon.foreground": "#B69500",
|
||||
@@ -258,6 +259,7 @@
|
||||
"quickInputTitle.background": "#F0F0F3",
|
||||
"chat.requestBubbleBackground": "#EEF4FB",
|
||||
"chat.requestBubbleHoverBackground": "#E6EDFA",
|
||||
"chat.thinkingShimmer": "#999999",
|
||||
"editorCommentsWidget.rangeBackground": "#EEF4FB",
|
||||
"editorCommentsWidget.rangeActiveBackground": "#E6EDFA",
|
||||
"charts.foreground": "#202020",
|
||||
@@ -617,7 +619,7 @@
|
||||
"markup.fenced_code"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#666666"
|
||||
"foreground": "#606060"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -61,11 +61,16 @@
|
||||
}
|
||||
|
||||
/* Ensure iframe containers in pane-body render above sidebar z-index */
|
||||
.monaco-workbench > div[data-keybinding-context],
|
||||
.monaco-workbench > div[data-keybinding-context] {
|
||||
z-index: 50 !important;
|
||||
}
|
||||
|
||||
/* Ensure in-editor pane iframes render below sidebar z-index */
|
||||
.monaco-workbench > div[data-parent-flow-to-element-id] {
|
||||
z-index: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
/* Ensure webview containers render above sidebar z-index */
|
||||
.monaco-workbench .part.sidebar .webview,
|
||||
.monaco-workbench .part.sidebar .webview-container,
|
||||
@@ -200,7 +205,7 @@
|
||||
background-color: color-mix(in srgb, var(--vscode-list-hoverBackground) 95%, black) !important;
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-entry .quick-input-list-separator {
|
||||
.monaco-workbench .quick-input-list .quick-input-list-entry .quick-input-list-separator {
|
||||
height: 16px;
|
||||
margin-top: 2px;
|
||||
display: flex;
|
||||
@@ -208,14 +213,18 @@
|
||||
font-size: 11px;
|
||||
padding: 0 4px 1px 4px;
|
||||
border-radius: var(--vscode-cornerRadius-small) !important;
|
||||
background: color-mix(in srgb, var(--vscode-badge-background) 50%, transparent) !important;
|
||||
background: color-mix(in srgb, var(--vscode-badge-background) 70%, transparent) !important;
|
||||
color: var(--vscode-badge-foreground) !important;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.monaco-list-row.focused .quick-input-list-entry .quick-input-list-separator,
|
||||
.monaco-list-row.selected .quick-input-list-entry .quick-input-list-separator,
|
||||
.monaco-list-row:hover .quick-input-list-entry .quick-input-list-separator {
|
||||
.monaco-workbench.vs-dark .quick-input-list .quick-input-list-entry .quick-input-list-separator {
|
||||
background: color-mix(in srgb, var(--vscode-badge-background) 50%, transparent) !important;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-list-row.focused .quick-input-list-entry .quick-input-list-separator,
|
||||
.monaco-workbench .monaco-list-row.selected .quick-input-list-entry .quick-input-list-separator,
|
||||
.monaco-workbench .monaco-list-row:hover .quick-input-list-entry .quick-input-list-separator {
|
||||
background: transparent !important;
|
||||
color: inherit !important;
|
||||
padding: 0;
|
||||
@@ -437,6 +446,15 @@
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.monaco-workbench .settings-editor > .settings-header > .search-container > .search-container-widgets > .settings-count-widget {
|
||||
border-radius: var(--radius-sm);
|
||||
background: color-mix(in srgb, var(--vscode-badge-background) 70%, transparent) !important;
|
||||
}
|
||||
|
||||
.monaco-workbench.vs-dark .settings-editor > .settings-header > .search-container > .search-container-widgets > .settings-count-widget {
|
||||
background: color-mix(in srgb, var(--vscode-badge-background) 50%, transparent) !important;
|
||||
}
|
||||
|
||||
/* Welcome Tiles */
|
||||
.monaco-workbench .part.editor .welcomePageContainer .tile {
|
||||
box-shadow: var(--shadow-md);
|
||||
|
||||
@@ -156,7 +156,8 @@
|
||||
{
|
||||
"scope": "markup.italic",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
"fontStyle": "italic",
|
||||
"foreground": "#C586C0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -122,7 +122,8 @@
|
||||
{
|
||||
"scope": "markup.italic",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
"fontStyle": "italic",
|
||||
"foreground": "#800080"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -159,7 +159,8 @@
|
||||
{
|
||||
"scope": "markup.italic",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
"fontStyle": "italic",
|
||||
"foreground": "#800080"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1809,42 +1809,42 @@
|
||||
"c": "_",
|
||||
"t": "text.html.markdown meta.paragraph.markdown markup.italic.markdown punctuation.definition.italic.markdown",
|
||||
"r": {
|
||||
"dark_plus": "default: #D4D4D4",
|
||||
"light_plus": "default: #000000",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"dark_plus": "markup.italic: #C586C0",
|
||||
"light_plus": "markup.italic: #800080",
|
||||
"dark_vs": "markup.italic: #C586C0",
|
||||
"light_vs": "markup.italic: #800080",
|
||||
"hc_black": "default: #FFFFFF",
|
||||
"dark_modern": "default: #CCCCCC",
|
||||
"hc_light": "default: #292929",
|
||||
"light_modern": "default: #3B3B3B"
|
||||
"dark_modern": "markup.italic: #C586C0",
|
||||
"hc_light": "markup.italic: #800080",
|
||||
"light_modern": "markup.italic: #800080"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": "italics",
|
||||
"t": "text.html.markdown meta.paragraph.markdown markup.italic.markdown",
|
||||
"r": {
|
||||
"dark_plus": "default: #D4D4D4",
|
||||
"light_plus": "default: #000000",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"dark_plus": "markup.italic: #C586C0",
|
||||
"light_plus": "markup.italic: #800080",
|
||||
"dark_vs": "markup.italic: #C586C0",
|
||||
"light_vs": "markup.italic: #800080",
|
||||
"hc_black": "default: #FFFFFF",
|
||||
"dark_modern": "default: #CCCCCC",
|
||||
"hc_light": "default: #292929",
|
||||
"light_modern": "default: #3B3B3B"
|
||||
"dark_modern": "markup.italic: #C586C0",
|
||||
"hc_light": "markup.italic: #800080",
|
||||
"light_modern": "markup.italic: #800080"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": "_",
|
||||
"t": "text.html.markdown meta.paragraph.markdown markup.italic.markdown punctuation.definition.italic.markdown",
|
||||
"r": {
|
||||
"dark_plus": "default: #D4D4D4",
|
||||
"light_plus": "default: #000000",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"dark_plus": "markup.italic: #C586C0",
|
||||
"light_plus": "markup.italic: #800080",
|
||||
"dark_vs": "markup.italic: #C586C0",
|
||||
"light_vs": "markup.italic: #800080",
|
||||
"hc_black": "default: #FFFFFF",
|
||||
"dark_modern": "default: #CCCCCC",
|
||||
"hc_light": "default: #292929",
|
||||
"light_modern": "default: #3B3B3B"
|
||||
"dark_modern": "markup.italic: #C586C0",
|
||||
"hc_light": "markup.italic: #800080",
|
||||
"light_modern": "markup.italic: #800080"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
"c": "*italics*",
|
||||
"t": "source.rst markup.italic",
|
||||
"r": {
|
||||
"dark_plus": "default: #D4D4D4",
|
||||
"light_plus": "default: #000000",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"dark_plus": "markup.italic: #C586C0",
|
||||
"light_plus": "markup.italic: #800080",
|
||||
"dark_vs": "markup.italic: #C586C0",
|
||||
"light_vs": "markup.italic: #800080",
|
||||
"hc_black": "default: #FFFFFF",
|
||||
"dark_modern": "default: #CCCCCC",
|
||||
"hc_light": "default: #292929",
|
||||
"light_modern": "default: #3B3B3B"
|
||||
"dark_modern": "markup.italic: #C586C0",
|
||||
"hc_light": "markup.italic: #800080",
|
||||
"light_modern": "markup.italic: #800080"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
Generated
+52
-52
@@ -30,16 +30,16 @@
|
||||
"@vscode/windows-mutex": "^0.5.0",
|
||||
"@vscode/windows-process-tree": "^0.6.0",
|
||||
"@vscode/windows-registry": "^1.1.0",
|
||||
"@xterm/addon-clipboard": "^0.3.0-beta.152",
|
||||
"@xterm/addon-image": "^0.10.0-beta.152",
|
||||
"@xterm/addon-ligatures": "^0.11.0-beta.152",
|
||||
"@xterm/addon-progress": "^0.3.0-beta.152",
|
||||
"@xterm/addon-search": "^0.17.0-beta.152",
|
||||
"@xterm/addon-serialize": "^0.15.0-beta.152",
|
||||
"@xterm/addon-unicode11": "^0.10.0-beta.152",
|
||||
"@xterm/addon-webgl": "^0.20.0-beta.151",
|
||||
"@xterm/headless": "^6.1.0-beta.152",
|
||||
"@xterm/xterm": "^6.1.0-beta.152",
|
||||
"@xterm/addon-clipboard": "^0.3.0-beta.165",
|
||||
"@xterm/addon-image": "^0.10.0-beta.165",
|
||||
"@xterm/addon-ligatures": "^0.11.0-beta.165",
|
||||
"@xterm/addon-progress": "^0.3.0-beta.165",
|
||||
"@xterm/addon-search": "^0.17.0-beta.165",
|
||||
"@xterm/addon-serialize": "^0.15.0-beta.165",
|
||||
"@xterm/addon-unicode11": "^0.10.0-beta.165",
|
||||
"@xterm/addon-webgl": "^0.20.0-beta.164",
|
||||
"@xterm/headless": "^6.1.0-beta.165",
|
||||
"@xterm/xterm": "^6.1.0-beta.165",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"jschardet": "3.1.4",
|
||||
@@ -150,7 +150,7 @@
|
||||
"source-map": "0.6.1",
|
||||
"source-map-support": "^0.3.2",
|
||||
"style-loader": "^3.3.2",
|
||||
"tar": "^7.5.7",
|
||||
"tar": "^7.5.9",
|
||||
"ts-loader": "^9.5.1",
|
||||
"tsec": "0.2.7",
|
||||
"tslib": "^2.6.3",
|
||||
@@ -3909,30 +3909,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-clipboard": {
|
||||
"version": "0.3.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.152.tgz",
|
||||
"integrity": "sha512-D+wFHTTNj1qzlSL1h15tgFh6JgK/SSaotkohtaKykkKFmkdGrtJq8PpINaFipRDrZXX0d9eOD+wrMfz6IG+5Yw==",
|
||||
"version": "0.3.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.165.tgz",
|
||||
"integrity": "sha512-48GUTZg7sKB7tQvtC7FcH22GxxO0cIUVM4hw068Oi3cJnxDLLPQDicPv70fFG7zysGxxEKE7A39GMtHhwFI75Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-base64": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-image": {
|
||||
"version": "0.10.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.152.tgz",
|
||||
"integrity": "sha512-pyQ/hQr3O0gY1La+6ZXdh0tI/+6MmNo2eFPNyWzB21J02xMu6nc30+B/H9VlPSR3AXHno5U67AWra5Y4FrE+5A==",
|
||||
"version": "0.10.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.165.tgz",
|
||||
"integrity": "sha512-DwYvKRgytc1OYoJVwA/doOTT92K8asgvnt3FzsHt5D+XgniwdvM5nwjxv95p6UXv0kEOxQWFy3sNJl/4g/5pew==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-ligatures": {
|
||||
"version": "0.11.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.152.tgz",
|
||||
"integrity": "sha512-DglTaxmWHolTfryequU/7+Q4bjpDywt7UDsE3SdbC7O/9fa1qaOZMVlxKtRBtMBBzX5PXa+Ha4qAaMS2psr3UQ==",
|
||||
"version": "0.11.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.165.tgz",
|
||||
"integrity": "sha512-3nuPBH4ZrGYF+yj/tBB/+YaLRnn8qqbR9J9OcvM6aeDfboEeaFAYIpmdqjh+2Rl2JFTIgZoiS3dKLWaUUpk0Tw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0",
|
||||
@@ -3942,7 +3942,7 @@
|
||||
"node": ">8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-ligatures/node_modules/lru-cache": {
|
||||
@@ -3964,63 +3964,63 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@xterm/addon-progress": {
|
||||
"version": "0.3.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.152.tgz",
|
||||
"integrity": "sha512-H3qNwUaTNDRm51s8IzcYRinnQBSf7QDXWkcyAuDlprDJlR5BFhmGr9hpMV/KlCo2s6nhWrFjiwkd642DJ7McMg==",
|
||||
"version": "0.3.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.165.tgz",
|
||||
"integrity": "sha512-Jl+dhHkFBUafrXCECI/EepcGV1GYuU1X/0oXkPYu/VYfbmkjQSAidmfBAEyS+4+AUK5Lkf6yLdb1N13tZVexyg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-search": {
|
||||
"version": "0.17.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.152.tgz",
|
||||
"integrity": "sha512-0T/xDg0yh3PlS9HWOioIrNGP0OfUp4MtBJ7M2sfR+h23KKa4gl1ec7S1TsGU4gsvEMBKG1TB6jReX4vlKGYc4A==",
|
||||
"version": "0.17.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.165.tgz",
|
||||
"integrity": "sha512-3KjonTDJl/8M6jI5nTJITVT+Z528d/5CgqRmn6IV+sDgRfr3W84RZNDsaxXsLoc0GDsxQIB74/FmnNykUQ5Yew==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-serialize": {
|
||||
"version": "0.15.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.152.tgz",
|
||||
"integrity": "sha512-GnhwKg0dkpAI1gZmm9L69Xjseal5pKwXFaMUxzm+Viajcp/PdqK1pEBJX5RndToNF0Ti3xu4e6BFO7dqY/J9TA==",
|
||||
"version": "0.15.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.165.tgz",
|
||||
"integrity": "sha512-NjXE+of4NJagrtHlzePBuWQ8a9pBFhhmQuvOhPj9W3CSi+VanuMoM/oRaT1TbR3efHk2JdCsKVDJScEzY8kdjw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-unicode11": {
|
||||
"version": "0.10.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.152.tgz",
|
||||
"integrity": "sha512-2HSpMjbckGAmU/56CTGuEbCZJZHxUlfNJ2uzR4akZyVVLEmavC4thHVSGT7Ei1zzpHZsAg0y4WMbcp4wzpPv3g==",
|
||||
"version": "0.10.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.165.tgz",
|
||||
"integrity": "sha512-a6myeixOXDYeuOj0GK+/LWXbXXWanFVMvQRUMgC7wmUNGgSZiyJ8NPWzhAq6Vib4jSQ02pd+ux4ZtWs5kyvFLg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-webgl": {
|
||||
"version": "0.20.0-beta.151",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.151.tgz",
|
||||
"integrity": "sha512-3ogsmZKPKc8n9Mjik4jTmNYT2Nbboe/zqcjDNG7RONO3w/tUyoKQshYCMBxxGMNLDwvh3BQ/D9/6JvdNWA1ShA==",
|
||||
"version": "0.20.0-beta.164",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.164.tgz",
|
||||
"integrity": "sha512-wXTi281yTWY1iAmRh21N6AhcEMopTjIm4xsdDdNmS5LbhxNuhVKNNIGKm5Zhd/G9fpn/vrfC4yZ6KA0lI/ZAxg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/headless": {
|
||||
"version": "6.1.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.152.tgz",
|
||||
"integrity": "sha512-Hkt+KPuifM8kqDKtbHq1uIhqdZMQazKTl9zaqjcWY3Vogx7+JVr6F+eN89KHnrvhUUOmhAM0JQAIRv1O+upfUw==",
|
||||
"version": "6.1.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.165.tgz",
|
||||
"integrity": "sha512-GjAqUhEiY7gb12+yIItptgMKUwHMa7o39HpezD7sfNjYLjmvWQcB02jqUdVMsvjjAKTe2YJMXp3RkApeXdMRVg==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"addons/*"
|
||||
]
|
||||
},
|
||||
"node_modules/@xterm/xterm": {
|
||||
"version": "6.1.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.152.tgz",
|
||||
"integrity": "sha512-XHJ5ab19V6tmcHmBE7k9IYjXSwTxUd0c7oKLa5J+ZO0+aiXE8UKh9OEDw1oyl5ZQhw9gn71cGEo4TpB58KhfoQ==",
|
||||
"version": "6.1.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.165.tgz",
|
||||
"integrity": "sha512-OUszO4HSmGPEw3EhboyIcNLQKJQKCDsYHv9kYFcaiK3biuNjGP0VAPVUJOLbf3V9fa1GLUUq+t985blqvTApoA==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"addons/*"
|
||||
@@ -16389,9 +16389,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.7",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
|
||||
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
|
||||
"version": "7.5.9",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz",
|
||||
"integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
|
||||
+13
-13
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.110.0",
|
||||
"distro": "16be71799bd7ef33ea9b0206fb548ce74a47daa4",
|
||||
"distro": "85914d5a600261a53306190177be48aa8f0cdfb4",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
@@ -95,16 +95,16 @@
|
||||
"@vscode/windows-mutex": "^0.5.0",
|
||||
"@vscode/windows-process-tree": "^0.6.0",
|
||||
"@vscode/windows-registry": "^1.1.0",
|
||||
"@xterm/addon-clipboard": "^0.3.0-beta.152",
|
||||
"@xterm/addon-image": "^0.10.0-beta.152",
|
||||
"@xterm/addon-ligatures": "^0.11.0-beta.152",
|
||||
"@xterm/addon-progress": "^0.3.0-beta.152",
|
||||
"@xterm/addon-search": "^0.17.0-beta.152",
|
||||
"@xterm/addon-serialize": "^0.15.0-beta.152",
|
||||
"@xterm/addon-unicode11": "^0.10.0-beta.152",
|
||||
"@xterm/addon-webgl": "^0.20.0-beta.151",
|
||||
"@xterm/headless": "^6.1.0-beta.152",
|
||||
"@xterm/xterm": "^6.1.0-beta.152",
|
||||
"@xterm/addon-clipboard": "^0.3.0-beta.165",
|
||||
"@xterm/addon-image": "^0.10.0-beta.165",
|
||||
"@xterm/addon-ligatures": "^0.11.0-beta.165",
|
||||
"@xterm/addon-progress": "^0.3.0-beta.165",
|
||||
"@xterm/addon-search": "^0.17.0-beta.165",
|
||||
"@xterm/addon-serialize": "^0.15.0-beta.165",
|
||||
"@xterm/addon-unicode11": "^0.10.0-beta.165",
|
||||
"@xterm/addon-webgl": "^0.20.0-beta.164",
|
||||
"@xterm/headless": "^6.1.0-beta.165",
|
||||
"@xterm/xterm": "^6.1.0-beta.165",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"jschardet": "3.1.4",
|
||||
@@ -215,7 +215,7 @@
|
||||
"source-map": "0.6.1",
|
||||
"source-map-support": "^0.3.2",
|
||||
"style-loader": "^3.3.2",
|
||||
"tar": "^7.5.7",
|
||||
"tar": "^7.5.9",
|
||||
"ts-loader": "^9.5.1",
|
||||
"tsec": "0.2.7",
|
||||
"tslib": "^2.6.3",
|
||||
@@ -244,4 +244,4 @@
|
||||
"optionalDependencies": {
|
||||
"windows-foreground-love": "0.6.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+48
-48
@@ -22,16 +22,16 @@
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
"@vscode/windows-process-tree": "^0.6.0",
|
||||
"@vscode/windows-registry": "^1.1.0",
|
||||
"@xterm/addon-clipboard": "^0.3.0-beta.152",
|
||||
"@xterm/addon-image": "^0.10.0-beta.152",
|
||||
"@xterm/addon-ligatures": "^0.11.0-beta.152",
|
||||
"@xterm/addon-progress": "^0.3.0-beta.152",
|
||||
"@xterm/addon-search": "^0.17.0-beta.152",
|
||||
"@xterm/addon-serialize": "^0.15.0-beta.152",
|
||||
"@xterm/addon-unicode11": "^0.10.0-beta.152",
|
||||
"@xterm/addon-webgl": "^0.20.0-beta.151",
|
||||
"@xterm/headless": "^6.1.0-beta.152",
|
||||
"@xterm/xterm": "^6.1.0-beta.152",
|
||||
"@xterm/addon-clipboard": "^0.3.0-beta.165",
|
||||
"@xterm/addon-image": "^0.10.0-beta.165",
|
||||
"@xterm/addon-ligatures": "^0.11.0-beta.165",
|
||||
"@xterm/addon-progress": "^0.3.0-beta.165",
|
||||
"@xterm/addon-search": "^0.17.0-beta.165",
|
||||
"@xterm/addon-serialize": "^0.15.0-beta.165",
|
||||
"@xterm/addon-unicode11": "^0.10.0-beta.165",
|
||||
"@xterm/addon-webgl": "^0.20.0-beta.164",
|
||||
"@xterm/headless": "^6.1.0-beta.165",
|
||||
"@xterm/xterm": "^6.1.0-beta.165",
|
||||
"cookie": "^0.7.0",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
@@ -577,30 +577,30 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@xterm/addon-clipboard": {
|
||||
"version": "0.3.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.152.tgz",
|
||||
"integrity": "sha512-D+wFHTTNj1qzlSL1h15tgFh6JgK/SSaotkohtaKykkKFmkdGrtJq8PpINaFipRDrZXX0d9eOD+wrMfz6IG+5Yw==",
|
||||
"version": "0.3.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.165.tgz",
|
||||
"integrity": "sha512-48GUTZg7sKB7tQvtC7FcH22GxxO0cIUVM4hw068Oi3cJnxDLLPQDicPv70fFG7zysGxxEKE7A39GMtHhwFI75Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-base64": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-image": {
|
||||
"version": "0.10.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.152.tgz",
|
||||
"integrity": "sha512-pyQ/hQr3O0gY1La+6ZXdh0tI/+6MmNo2eFPNyWzB21J02xMu6nc30+B/H9VlPSR3AXHno5U67AWra5Y4FrE+5A==",
|
||||
"version": "0.10.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.165.tgz",
|
||||
"integrity": "sha512-DwYvKRgytc1OYoJVwA/doOTT92K8asgvnt3FzsHt5D+XgniwdvM5nwjxv95p6UXv0kEOxQWFy3sNJl/4g/5pew==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-ligatures": {
|
||||
"version": "0.11.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.152.tgz",
|
||||
"integrity": "sha512-DglTaxmWHolTfryequU/7+Q4bjpDywt7UDsE3SdbC7O/9fa1qaOZMVlxKtRBtMBBzX5PXa+Ha4qAaMS2psr3UQ==",
|
||||
"version": "0.11.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.165.tgz",
|
||||
"integrity": "sha512-3nuPBH4ZrGYF+yj/tBB/+YaLRnn8qqbR9J9OcvM6aeDfboEeaFAYIpmdqjh+2Rl2JFTIgZoiS3dKLWaUUpk0Tw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0",
|
||||
@@ -610,67 +610,67 @@
|
||||
"node": ">8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-progress": {
|
||||
"version": "0.3.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.152.tgz",
|
||||
"integrity": "sha512-H3qNwUaTNDRm51s8IzcYRinnQBSf7QDXWkcyAuDlprDJlR5BFhmGr9hpMV/KlCo2s6nhWrFjiwkd642DJ7McMg==",
|
||||
"version": "0.3.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.165.tgz",
|
||||
"integrity": "sha512-Jl+dhHkFBUafrXCECI/EepcGV1GYuU1X/0oXkPYu/VYfbmkjQSAidmfBAEyS+4+AUK5Lkf6yLdb1N13tZVexyg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-search": {
|
||||
"version": "0.17.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.152.tgz",
|
||||
"integrity": "sha512-0T/xDg0yh3PlS9HWOioIrNGP0OfUp4MtBJ7M2sfR+h23KKa4gl1ec7S1TsGU4gsvEMBKG1TB6jReX4vlKGYc4A==",
|
||||
"version": "0.17.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.165.tgz",
|
||||
"integrity": "sha512-3KjonTDJl/8M6jI5nTJITVT+Z528d/5CgqRmn6IV+sDgRfr3W84RZNDsaxXsLoc0GDsxQIB74/FmnNykUQ5Yew==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-serialize": {
|
||||
"version": "0.15.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.152.tgz",
|
||||
"integrity": "sha512-GnhwKg0dkpAI1gZmm9L69Xjseal5pKwXFaMUxzm+Viajcp/PdqK1pEBJX5RndToNF0Ti3xu4e6BFO7dqY/J9TA==",
|
||||
"version": "0.15.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.165.tgz",
|
||||
"integrity": "sha512-NjXE+of4NJagrtHlzePBuWQ8a9pBFhhmQuvOhPj9W3CSi+VanuMoM/oRaT1TbR3efHk2JdCsKVDJScEzY8kdjw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-unicode11": {
|
||||
"version": "0.10.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.152.tgz",
|
||||
"integrity": "sha512-2HSpMjbckGAmU/56CTGuEbCZJZHxUlfNJ2uzR4akZyVVLEmavC4thHVSGT7Ei1zzpHZsAg0y4WMbcp4wzpPv3g==",
|
||||
"version": "0.10.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.165.tgz",
|
||||
"integrity": "sha512-a6myeixOXDYeuOj0GK+/LWXbXXWanFVMvQRUMgC7wmUNGgSZiyJ8NPWzhAq6Vib4jSQ02pd+ux4ZtWs5kyvFLg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-webgl": {
|
||||
"version": "0.20.0-beta.151",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.151.tgz",
|
||||
"integrity": "sha512-3ogsmZKPKc8n9Mjik4jTmNYT2Nbboe/zqcjDNG7RONO3w/tUyoKQshYCMBxxGMNLDwvh3BQ/D9/6JvdNWA1ShA==",
|
||||
"version": "0.20.0-beta.164",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.164.tgz",
|
||||
"integrity": "sha512-wXTi281yTWY1iAmRh21N6AhcEMopTjIm4xsdDdNmS5LbhxNuhVKNNIGKm5Zhd/G9fpn/vrfC4yZ6KA0lI/ZAxg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/headless": {
|
||||
"version": "6.1.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.152.tgz",
|
||||
"integrity": "sha512-Hkt+KPuifM8kqDKtbHq1uIhqdZMQazKTl9zaqjcWY3Vogx7+JVr6F+eN89KHnrvhUUOmhAM0JQAIRv1O+upfUw==",
|
||||
"version": "6.1.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.165.tgz",
|
||||
"integrity": "sha512-GjAqUhEiY7gb12+yIItptgMKUwHMa7o39HpezD7sfNjYLjmvWQcB02jqUdVMsvjjAKTe2YJMXp3RkApeXdMRVg==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"addons/*"
|
||||
]
|
||||
},
|
||||
"node_modules/@xterm/xterm": {
|
||||
"version": "6.1.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.152.tgz",
|
||||
"integrity": "sha512-XHJ5ab19V6tmcHmBE7k9IYjXSwTxUd0c7oKLa5J+ZO0+aiXE8UKh9OEDw1oyl5ZQhw9gn71cGEo4TpB58KhfoQ==",
|
||||
"version": "6.1.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.165.tgz",
|
||||
"integrity": "sha512-OUszO4HSmGPEw3EhboyIcNLQKJQKCDsYHv9kYFcaiK3biuNjGP0VAPVUJOLbf3V9fa1GLUUq+t985blqvTApoA==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"addons/*"
|
||||
|
||||
+10
-10
@@ -17,16 +17,16 @@
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
"@vscode/windows-process-tree": "^0.6.0",
|
||||
"@vscode/windows-registry": "^1.1.0",
|
||||
"@xterm/addon-clipboard": "^0.3.0-beta.152",
|
||||
"@xterm/addon-image": "^0.10.0-beta.152",
|
||||
"@xterm/addon-ligatures": "^0.11.0-beta.152",
|
||||
"@xterm/addon-progress": "^0.3.0-beta.152",
|
||||
"@xterm/addon-search": "^0.17.0-beta.152",
|
||||
"@xterm/addon-serialize": "^0.15.0-beta.152",
|
||||
"@xterm/addon-unicode11": "^0.10.0-beta.152",
|
||||
"@xterm/addon-webgl": "^0.20.0-beta.151",
|
||||
"@xterm/headless": "^6.1.0-beta.152",
|
||||
"@xterm/xterm": "^6.1.0-beta.152",
|
||||
"@xterm/addon-clipboard": "^0.3.0-beta.165",
|
||||
"@xterm/addon-image": "^0.10.0-beta.165",
|
||||
"@xterm/addon-ligatures": "^0.11.0-beta.165",
|
||||
"@xterm/addon-progress": "^0.3.0-beta.165",
|
||||
"@xterm/addon-search": "^0.17.0-beta.165",
|
||||
"@xterm/addon-serialize": "^0.15.0-beta.165",
|
||||
"@xterm/addon-unicode11": "^0.10.0-beta.165",
|
||||
"@xterm/addon-webgl": "^0.20.0-beta.164",
|
||||
"@xterm/headless": "^6.1.0-beta.165",
|
||||
"@xterm/xterm": "^6.1.0-beta.165",
|
||||
"cookie": "^0.7.0",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
|
||||
Generated
+44
-44
@@ -14,15 +14,15 @@
|
||||
"@vscode/iconv-lite-umd": "0.7.1",
|
||||
"@vscode/tree-sitter-wasm": "^0.3.0",
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
"@xterm/addon-clipboard": "^0.3.0-beta.152",
|
||||
"@xterm/addon-image": "^0.10.0-beta.152",
|
||||
"@xterm/addon-ligatures": "^0.11.0-beta.152",
|
||||
"@xterm/addon-progress": "^0.3.0-beta.152",
|
||||
"@xterm/addon-search": "^0.17.0-beta.152",
|
||||
"@xterm/addon-serialize": "^0.15.0-beta.152",
|
||||
"@xterm/addon-unicode11": "^0.10.0-beta.152",
|
||||
"@xterm/addon-webgl": "^0.20.0-beta.151",
|
||||
"@xterm/xterm": "^6.1.0-beta.152",
|
||||
"@xterm/addon-clipboard": "^0.3.0-beta.165",
|
||||
"@xterm/addon-image": "^0.10.0-beta.165",
|
||||
"@xterm/addon-ligatures": "^0.11.0-beta.165",
|
||||
"@xterm/addon-progress": "^0.3.0-beta.165",
|
||||
"@xterm/addon-search": "^0.17.0-beta.165",
|
||||
"@xterm/addon-serialize": "^0.15.0-beta.165",
|
||||
"@xterm/addon-unicode11": "^0.10.0-beta.165",
|
||||
"@xterm/addon-webgl": "^0.20.0-beta.164",
|
||||
"@xterm/xterm": "^6.1.0-beta.165",
|
||||
"jschardet": "3.1.4",
|
||||
"katex": "^0.16.22",
|
||||
"tas-client": "0.3.1",
|
||||
@@ -99,30 +99,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-clipboard": {
|
||||
"version": "0.3.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.152.tgz",
|
||||
"integrity": "sha512-D+wFHTTNj1qzlSL1h15tgFh6JgK/SSaotkohtaKykkKFmkdGrtJq8PpINaFipRDrZXX0d9eOD+wrMfz6IG+5Yw==",
|
||||
"version": "0.3.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.165.tgz",
|
||||
"integrity": "sha512-48GUTZg7sKB7tQvtC7FcH22GxxO0cIUVM4hw068Oi3cJnxDLLPQDicPv70fFG7zysGxxEKE7A39GMtHhwFI75Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-base64": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-image": {
|
||||
"version": "0.10.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.152.tgz",
|
||||
"integrity": "sha512-pyQ/hQr3O0gY1La+6ZXdh0tI/+6MmNo2eFPNyWzB21J02xMu6nc30+B/H9VlPSR3AXHno5U67AWra5Y4FrE+5A==",
|
||||
"version": "0.10.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.165.tgz",
|
||||
"integrity": "sha512-DwYvKRgytc1OYoJVwA/doOTT92K8asgvnt3FzsHt5D+XgniwdvM5nwjxv95p6UXv0kEOxQWFy3sNJl/4g/5pew==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-ligatures": {
|
||||
"version": "0.11.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.152.tgz",
|
||||
"integrity": "sha512-DglTaxmWHolTfryequU/7+Q4bjpDywt7UDsE3SdbC7O/9fa1qaOZMVlxKtRBtMBBzX5PXa+Ha4qAaMS2psr3UQ==",
|
||||
"version": "0.11.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.165.tgz",
|
||||
"integrity": "sha512-3nuPBH4ZrGYF+yj/tBB/+YaLRnn8qqbR9J9OcvM6aeDfboEeaFAYIpmdqjh+2Rl2JFTIgZoiS3dKLWaUUpk0Tw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0",
|
||||
@@ -132,58 +132,58 @@
|
||||
"node": ">8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-progress": {
|
||||
"version": "0.3.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.152.tgz",
|
||||
"integrity": "sha512-H3qNwUaTNDRm51s8IzcYRinnQBSf7QDXWkcyAuDlprDJlR5BFhmGr9hpMV/KlCo2s6nhWrFjiwkd642DJ7McMg==",
|
||||
"version": "0.3.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.165.tgz",
|
||||
"integrity": "sha512-Jl+dhHkFBUafrXCECI/EepcGV1GYuU1X/0oXkPYu/VYfbmkjQSAidmfBAEyS+4+AUK5Lkf6yLdb1N13tZVexyg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-search": {
|
||||
"version": "0.17.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.152.tgz",
|
||||
"integrity": "sha512-0T/xDg0yh3PlS9HWOioIrNGP0OfUp4MtBJ7M2sfR+h23KKa4gl1ec7S1TsGU4gsvEMBKG1TB6jReX4vlKGYc4A==",
|
||||
"version": "0.17.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.165.tgz",
|
||||
"integrity": "sha512-3KjonTDJl/8M6jI5nTJITVT+Z528d/5CgqRmn6IV+sDgRfr3W84RZNDsaxXsLoc0GDsxQIB74/FmnNykUQ5Yew==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-serialize": {
|
||||
"version": "0.15.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.152.tgz",
|
||||
"integrity": "sha512-GnhwKg0dkpAI1gZmm9L69Xjseal5pKwXFaMUxzm+Viajcp/PdqK1pEBJX5RndToNF0Ti3xu4e6BFO7dqY/J9TA==",
|
||||
"version": "0.15.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.165.tgz",
|
||||
"integrity": "sha512-NjXE+of4NJagrtHlzePBuWQ8a9pBFhhmQuvOhPj9W3CSi+VanuMoM/oRaT1TbR3efHk2JdCsKVDJScEzY8kdjw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-unicode11": {
|
||||
"version": "0.10.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.152.tgz",
|
||||
"integrity": "sha512-2HSpMjbckGAmU/56CTGuEbCZJZHxUlfNJ2uzR4akZyVVLEmavC4thHVSGT7Ei1zzpHZsAg0y4WMbcp4wzpPv3g==",
|
||||
"version": "0.10.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.165.tgz",
|
||||
"integrity": "sha512-a6myeixOXDYeuOj0GK+/LWXbXXWanFVMvQRUMgC7wmUNGgSZiyJ8NPWzhAq6Vib4jSQ02pd+ux4ZtWs5kyvFLg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-webgl": {
|
||||
"version": "0.20.0-beta.151",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.151.tgz",
|
||||
"integrity": "sha512-3ogsmZKPKc8n9Mjik4jTmNYT2Nbboe/zqcjDNG7RONO3w/tUyoKQshYCMBxxGMNLDwvh3BQ/D9/6JvdNWA1ShA==",
|
||||
"version": "0.20.0-beta.164",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.164.tgz",
|
||||
"integrity": "sha512-wXTi281yTWY1iAmRh21N6AhcEMopTjIm4xsdDdNmS5LbhxNuhVKNNIGKm5Zhd/G9fpn/vrfC4yZ6KA0lI/ZAxg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^6.1.0-beta.152"
|
||||
"@xterm/xterm": "^6.1.0-beta.165"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/xterm": {
|
||||
"version": "6.1.0-beta.152",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.152.tgz",
|
||||
"integrity": "sha512-XHJ5ab19V6tmcHmBE7k9IYjXSwTxUd0c7oKLa5J+ZO0+aiXE8UKh9OEDw1oyl5ZQhw9gn71cGEo4TpB58KhfoQ==",
|
||||
"version": "6.1.0-beta.165",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.165.tgz",
|
||||
"integrity": "sha512-OUszO4HSmGPEw3EhboyIcNLQKJQKCDsYHv9kYFcaiK3biuNjGP0VAPVUJOLbf3V9fa1GLUUq+t985blqvTApoA==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"addons/*"
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
"@vscode/iconv-lite-umd": "0.7.1",
|
||||
"@vscode/tree-sitter-wasm": "^0.3.0",
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
"@xterm/addon-clipboard": "^0.3.0-beta.152",
|
||||
"@xterm/addon-image": "^0.10.0-beta.152",
|
||||
"@xterm/addon-ligatures": "^0.11.0-beta.152",
|
||||
"@xterm/addon-progress": "^0.3.0-beta.152",
|
||||
"@xterm/addon-search": "^0.17.0-beta.152",
|
||||
"@xterm/addon-serialize": "^0.15.0-beta.152",
|
||||
"@xterm/addon-unicode11": "^0.10.0-beta.152",
|
||||
"@xterm/addon-webgl": "^0.20.0-beta.151",
|
||||
"@xterm/xterm": "^6.1.0-beta.152",
|
||||
"@xterm/addon-clipboard": "^0.3.0-beta.165",
|
||||
"@xterm/addon-image": "^0.10.0-beta.165",
|
||||
"@xterm/addon-ligatures": "^0.11.0-beta.165",
|
||||
"@xterm/addon-progress": "^0.3.0-beta.165",
|
||||
"@xterm/addon-search": "^0.17.0-beta.165",
|
||||
"@xterm/addon-serialize": "^0.15.0-beta.165",
|
||||
"@xterm/addon-unicode11": "^0.10.0-beta.165",
|
||||
"@xterm/addon-webgl": "^0.20.0-beta.164",
|
||||
"@xterm/xterm": "^6.1.0-beta.165",
|
||||
"jschardet": "3.1.4",
|
||||
"katex": "^0.16.22",
|
||||
"tas-client": "0.3.1",
|
||||
|
||||
+1494
-792
File diff suppressed because it is too large
Load Diff
+1025
-1067
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,9 @@ import { IEncryptionMainService } from '../../platform/encryption/common/encrypt
|
||||
import { EncryptionMainService } from '../../platform/encryption/electron-main/encryptionMainService.js';
|
||||
import { NativeBrowserElementsMainService, INativeBrowserElementsMainService } from '../../platform/browserElements/electron-main/nativeBrowserElementsMainService.js';
|
||||
import { ipcBrowserViewChannelName } from '../../platform/browserView/common/browserView.js';
|
||||
import { ipcBrowserViewGroupChannelName } from '../../platform/browserView/common/browserViewGroup.js';
|
||||
import { BrowserViewMainService, IBrowserViewMainService } from '../../platform/browserView/electron-main/browserViewMainService.js';
|
||||
import { BrowserViewGroupMainService, IBrowserViewGroupMainService } from '../../platform/browserView/electron-main/browserViewGroupMainService.js';
|
||||
import { BrowserViewCDPProxyServer, IBrowserViewCDPProxyServer } from '../../platform/browserView/electron-main/browserViewCDPProxyServer.js';
|
||||
import { NativeParsedArgs } from '../../platform/environment/common/argv.js';
|
||||
import { IEnvironmentMainService } from '../../platform/environment/electron-main/environmentMainService.js';
|
||||
@@ -1043,6 +1045,7 @@ export class CodeApplication extends Disposable {
|
||||
// Browser View
|
||||
services.set(IBrowserViewCDPProxyServer, new SyncDescriptor(BrowserViewCDPProxyServer, undefined, true));
|
||||
services.set(IBrowserViewMainService, new SyncDescriptor(BrowserViewMainService, undefined, false /* proxied to other processes */));
|
||||
services.set(IBrowserViewGroupMainService, new SyncDescriptor(BrowserViewGroupMainService, undefined, false /* proxied to other processes */));
|
||||
|
||||
// Keyboard Layout
|
||||
services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService));
|
||||
@@ -1206,6 +1209,11 @@ export class CodeApplication extends Disposable {
|
||||
mainProcessElectronServer.registerChannel(ipcBrowserViewChannelName, browserViewChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel(ipcBrowserViewChannelName, browserViewChannel));
|
||||
|
||||
// Browser View Group
|
||||
const browserViewGroupChannel = ProxyChannel.fromService(accessor.get(IBrowserViewGroupMainService), disposables);
|
||||
mainProcessElectronServer.registerChannel(ipcBrowserViewGroupChannelName, browserViewGroupChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel(ipcBrowserViewGroupChannelName, browserViewGroupChannel));
|
||||
|
||||
// Signing
|
||||
const signChannel = ProxyChannel.fromService(accessor.get(ISignService), disposables);
|
||||
mainProcessElectronServer.registerChannel('sign', signChannel);
|
||||
|
||||
@@ -136,6 +136,7 @@ import { IMeteredConnectionService } from '../../../platform/meteredConnection/c
|
||||
import { MeteredConnectionChannelClient, METERED_CONNECTION_CHANNEL } from '../../../platform/meteredConnection/common/meteredConnectionIpc.js';
|
||||
import { IPlaywrightService } from '../../../platform/browserView/common/playwrightService.js';
|
||||
import { PlaywrightService } from '../../../platform/browserView/node/playwrightService.js';
|
||||
import { IBrowserViewGroupRemoteService, BrowserViewGroupRemoteService } from '../../../platform/browserView/node/browserViewGroupRemoteService.js';
|
||||
|
||||
class SharedProcessMain extends Disposable implements IClientConnectionFilter {
|
||||
|
||||
@@ -404,6 +405,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter {
|
||||
services.set(ISharedWebContentExtractorService, new SyncDescriptor(SharedWebContentExtractorService));
|
||||
|
||||
// Playwright
|
||||
services.set(IBrowserViewGroupRemoteService, new SyncDescriptor(BrowserViewGroupRemoteService));
|
||||
services.set(IPlaywrightService, new SyncDescriptor(PlaywrightService));
|
||||
|
||||
return new InstantiationService(services);
|
||||
|
||||
@@ -513,6 +513,8 @@ export class InlineEditItem extends InlineSuggestionItemBase {
|
||||
let inlineEditModelVersion = this._inlineEditModelVersion;
|
||||
let newAction: InlineSuggestionAction | undefined;
|
||||
|
||||
const updatedTarget = TextModelValueReference.snapshot(textModel);
|
||||
|
||||
if (this.action?.kind === 'edit') { // TODO What about rename?
|
||||
edits = edits.map(innerEdit => innerEdit.applyTextModelChanges(textModelChanges));
|
||||
|
||||
@@ -546,7 +548,7 @@ export class InlineEditItem extends InlineSuggestionItemBase {
|
||||
snippetInfo: this.snippetInfo,
|
||||
stringEdit: newEdit,
|
||||
alternativeAction: this.action.alternativeAction,
|
||||
target: this.originalTextRef,
|
||||
target: updatedTarget,
|
||||
};
|
||||
} else if (this.action?.kind === 'jumpTo') {
|
||||
const jumpToOffset = this.action.offset;
|
||||
@@ -560,7 +562,7 @@ export class InlineEditItem extends InlineSuggestionItemBase {
|
||||
kind: 'jumpTo',
|
||||
position: newJumpToPosition,
|
||||
offset: newJumpToOffset,
|
||||
target: this.originalTextRef,
|
||||
target: updatedTarget,
|
||||
};
|
||||
} else {
|
||||
newAction = undefined;
|
||||
@@ -582,7 +584,7 @@ export class InlineEditItem extends InlineSuggestionItemBase {
|
||||
newDisplayLocation,
|
||||
lastChangePartOfInlineEdit,
|
||||
inlineEditModelVersion,
|
||||
this.originalTextRef,
|
||||
updatedTarget,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+28
-13
@@ -36,7 +36,7 @@ export class GutterIndicatorMenuContent {
|
||||
constructor(
|
||||
private readonly _editorObs: ObservableCodeEditor,
|
||||
private readonly _data: InlineSuggestionGutterMenuData,
|
||||
private readonly _close: (focusEditor: boolean) => void,
|
||||
private readonly _close: (focusEditor: boolean, commandId?: string) => void,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@@ -59,12 +59,36 @@ export class GutterIndicatorMenuContent {
|
||||
isActive: activeElement.map(v => v === options.id),
|
||||
onHoverChange: v => activeElement.set(v ? options.id : undefined, undefined),
|
||||
onAction: () => {
|
||||
this._close(true);
|
||||
return this._commandService.executeCommand(typeof options.commandId === 'string' ? options.commandId : options.commandId.get(), ...(options.commandArgs ?? []));
|
||||
const commandId = typeof options.commandId === 'string' ? options.commandId : options.commandId.get();
|
||||
this._close(true, commandId);
|
||||
return this._commandService.executeCommand(commandId, ...(options.commandArgs ?? []));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const extensionCommandGroups = this._data.extensionCommands.map(group =>
|
||||
group.map((c, idx) => option(createOptionArgs({
|
||||
id: c.command.id + '_' + idx,
|
||||
title: c.command.title,
|
||||
icon: c.icon ?? Codicon.symbolEvent,
|
||||
commandId: c.command.id,
|
||||
commandArgs: c.command.arguments
|
||||
})))
|
||||
);
|
||||
|
||||
const extensionCommandNodes: ChildNode = [];
|
||||
for (const group of extensionCommandGroups) {
|
||||
if (group.length > 0) {
|
||||
extensionCommandNodes.push(separator());
|
||||
extensionCommandNodes.push(...group);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._data.extensionCommandsOnly) {
|
||||
// drop leading separator
|
||||
return hoverContent(extensionCommandNodes.slice(1));
|
||||
}
|
||||
|
||||
const title = header(this._data.displayName);
|
||||
|
||||
const gotoAndAccept = option(createOptionArgs({
|
||||
@@ -88,14 +112,6 @@ export class GutterIndicatorMenuContent {
|
||||
commandId: inlineSuggestCommitAlternativeActionId,
|
||||
})) : undefined;
|
||||
|
||||
const extensionCommands = this._data.extensionCommands.map((c, idx) => option(createOptionArgs({
|
||||
id: c.command.id + '_' + idx,
|
||||
title: c.command.title,
|
||||
icon: c.icon ?? Codicon.symbolEvent,
|
||||
commandId: c.command.id,
|
||||
commandArgs: c.command.arguments
|
||||
})));
|
||||
|
||||
const showModelEnabled = false;
|
||||
const modelOptions = showModelEnabled ? this._data.modelInfo?.models.map((m: { id: string; name: string }) => option({
|
||||
title: m.name,
|
||||
@@ -160,11 +176,10 @@ export class GutterIndicatorMenuContent {
|
||||
toggleCollapsedMode,
|
||||
modelOptions.length ? separator() : undefined,
|
||||
...modelOptions,
|
||||
extensionCommands.length ? separator() : undefined,
|
||||
snooze,
|
||||
settings,
|
||||
|
||||
...extensionCommands,
|
||||
...extensionCommandNodes,
|
||||
|
||||
actionBarFooter ? separator() : undefined,
|
||||
actionBarFooter
|
||||
|
||||
+14
-3
@@ -36,6 +36,7 @@ import { InlineSuggestAlternativeAction } from '../../../model/InlineSuggestAlte
|
||||
import { asCssVariable } from '../../../../../../../platform/theme/common/colorUtils.js';
|
||||
import { ThemeIcon } from '../../../../../../../base/common/themables.js';
|
||||
import { IUserInteractionService } from '../../../../../../../platform/userInteraction/browser/userInteractionService.js';
|
||||
import { Event, Emitter } from '../../../../../../../base/common/event.js';
|
||||
|
||||
/**
|
||||
* Customization options for the gutter indicator appearance and behavior.
|
||||
@@ -58,10 +59,11 @@ export class InlineEditsGutterIndicatorData {
|
||||
export class InlineSuggestionGutterMenuData {
|
||||
public static fromInlineSuggestion(suggestion: InlineSuggestionItem): InlineSuggestionGutterMenuData {
|
||||
const alternativeAction = suggestion.action?.kind === 'edit' ? suggestion.action.alternativeAction : undefined;
|
||||
const commands = suggestion.source.inlineSuggestions.commands ?? [];
|
||||
return new InlineSuggestionGutterMenuData(
|
||||
suggestion.gutterMenuLinkAction,
|
||||
suggestion.source.provider.displayName ?? localize('inlineSuggestion', "Inline Suggestion"),
|
||||
suggestion.source.inlineSuggestions.commands ?? [],
|
||||
commands.length > 0 ? [commands] : [],
|
||||
alternativeAction,
|
||||
suggestion.source.provider.modelInfo,
|
||||
suggestion.source.provider.setModelId?.bind(suggestion.source.provider),
|
||||
@@ -71,10 +73,11 @@ export class InlineSuggestionGutterMenuData {
|
||||
constructor(
|
||||
readonly action: Command | undefined,
|
||||
readonly displayName: string,
|
||||
readonly extensionCommands: InlineCompletionCommand[],
|
||||
readonly extensionCommands: InlineCompletionCommand[][],
|
||||
readonly alternativeAction: InlineSuggestAlternativeAction | undefined,
|
||||
readonly modelInfo: IInlineCompletionModelInfo | undefined,
|
||||
readonly setModelId: ((modelId: string) => Promise<void>) | undefined,
|
||||
readonly extensionCommandsOnly: boolean = false,
|
||||
) { }
|
||||
}
|
||||
|
||||
@@ -97,6 +100,10 @@ const CODICON_SIZE_PX = 16;
|
||||
const CODICON_PADDING_PX = 2;
|
||||
|
||||
export class InlineEditsGutterIndicator extends Disposable {
|
||||
|
||||
private readonly _onDidCloseWithCommand = this._register(new Emitter<string>());
|
||||
readonly onDidCloseWithCommand: Event<string> = this._onDidCloseWithCommand.event;
|
||||
|
||||
constructor(
|
||||
private readonly _editorObs: ObservableCodeEditor,
|
||||
private readonly _data: IObservable<InlineEditsGutterIndicatorData | undefined>,
|
||||
@@ -472,10 +479,13 @@ export class InlineEditsGutterIndicator extends Disposable {
|
||||
GutterIndicatorMenuContent,
|
||||
this._editorObs,
|
||||
data.gutterMenuData,
|
||||
(focusEditor) => {
|
||||
(focusEditor, commandId) => {
|
||||
if (focusEditor) {
|
||||
this._editorObs.editor.focus();
|
||||
}
|
||||
if (commandId) {
|
||||
this._onDidCloseWithCommand.fire(commandId);
|
||||
}
|
||||
h?.dispose();
|
||||
},
|
||||
).toDisposableLiveElement());
|
||||
@@ -584,6 +594,7 @@ export class InlineEditsGutterIndicator extends Disposable {
|
||||
width: layout.map(l => l.iconRect.width),
|
||||
position: 'relative',
|
||||
right: layout.map(l => l.iconDirection === 'top' ? '1px' : '0'),
|
||||
color: this._data.map(d => d?.customization?.icon?.color ? asCssVariable(d.customization.icon.color.id) : undefined),
|
||||
}
|
||||
}, [
|
||||
layout.map((l, reader) => withStyles(renderIcon(l.icon.read(reader)), { fontSize: toPx(Math.min(l.iconRect.width - CODICON_PADDING_PX, CODICON_SIZE_PX)) })),
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
.monaco-keybinding-key {
|
||||
font-size: 13px;
|
||||
opacity: 0.7;
|
||||
padding: 0;
|
||||
border: none;
|
||||
margin: 0;
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
&.active {
|
||||
@@ -270,4 +274,3 @@
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as dom from '../../../base/browser/dom.js';
|
||||
import { ActionBar } from '../../../base/browser/ui/actionbar/actionbar.js';
|
||||
import { Button } from '../../../base/browser/ui/button/button.js';
|
||||
import { KeybindingLabel } from '../../../base/browser/ui/keybindingLabel/keybindingLabel.js';
|
||||
import { IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from '../../../base/browser/ui/list/list.js';
|
||||
import { IListAccessibilityProvider, List } from '../../../base/browser/ui/list/listWidget.js';
|
||||
@@ -18,7 +19,7 @@ import './actionWidget.css';
|
||||
import { localize } from '../../../nls.js';
|
||||
import { IContextViewService } from '../../contextview/browser/contextView.js';
|
||||
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
|
||||
import { defaultListStyles } from '../../theme/browser/defaultStyles.js';
|
||||
import { defaultButtonStyles, defaultListStyles } from '../../theme/browser/defaultStyles.js';
|
||||
import { asCssVariable } from '../../theme/common/colorRegistry.js';
|
||||
import { ILayoutService } from '../../layout/browser/layoutService.js';
|
||||
import { IHoverService } from '../../hover/browser/hover.js';
|
||||
@@ -67,16 +68,42 @@ export interface IActionListItem<T> {
|
||||
* Optional toolbar actions shown when the item is focused or hovered.
|
||||
*/
|
||||
readonly toolbarActions?: IAction[];
|
||||
/**
|
||||
* Optional section identifier. Items with the same section belong to the same
|
||||
* collapsible group. Only meaningful when the ActionList is created with
|
||||
* collapsible sections.
|
||||
*/
|
||||
readonly section?: string;
|
||||
/**
|
||||
* When true, clicking this item toggles the section's collapsed state
|
||||
* instead of selecting it.
|
||||
*/
|
||||
readonly isSectionToggle?: boolean;
|
||||
/**
|
||||
* Optional CSS class name to add to the row container.
|
||||
*/
|
||||
readonly className?: string;
|
||||
/**
|
||||
* Optional badge text to display after the label (e.g., "New").
|
||||
*/
|
||||
readonly badge?: string;
|
||||
/**
|
||||
* When set, the description is rendered as a primary button.
|
||||
* The callback is invoked when the button is clicked.
|
||||
*/
|
||||
readonly descriptionButton?: { readonly label: string; readonly onDidClick: () => void };
|
||||
}
|
||||
|
||||
interface IActionMenuTemplateData {
|
||||
readonly container: HTMLElement;
|
||||
readonly icon: HTMLElement;
|
||||
readonly text: HTMLElement;
|
||||
readonly badge: HTMLElement;
|
||||
readonly description?: HTMLElement;
|
||||
readonly keybinding: KeybindingLabel;
|
||||
readonly toolbar: HTMLElement;
|
||||
readonly elementDisposables: DisposableStore;
|
||||
previousClassName?: string;
|
||||
}
|
||||
|
||||
export const enum ActionListItemKind {
|
||||
@@ -159,6 +186,10 @@ class ActionItemRenderer<T> implements IListRenderer<IActionListItem<T>, IAction
|
||||
text.className = 'title';
|
||||
container.append(text);
|
||||
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'action-item-badge';
|
||||
container.append(badge);
|
||||
|
||||
const description = document.createElement('span');
|
||||
description.className = 'description';
|
||||
container.append(description);
|
||||
@@ -171,7 +202,7 @@ class ActionItemRenderer<T> implements IListRenderer<IActionListItem<T>, IAction
|
||||
|
||||
const elementDisposables = new DisposableStore();
|
||||
|
||||
return { container, icon, text, description, keybinding, toolbar, elementDisposables };
|
||||
return { container, icon, text, badge, description, keybinding, toolbar, elementDisposables };
|
||||
}
|
||||
|
||||
renderElement(element: IActionListItem<T>, _index: number, data: IActionMenuTemplateData): void {
|
||||
@@ -194,10 +225,40 @@ class ActionItemRenderer<T> implements IListRenderer<IActionListItem<T>, IAction
|
||||
|
||||
dom.setVisibility(!element.hideIcon, data.icon);
|
||||
|
||||
// Apply optional className - clean up previous to avoid stale classes
|
||||
// from virtualized row reuse
|
||||
if (data.previousClassName) {
|
||||
data.container.classList.remove(data.previousClassName);
|
||||
}
|
||||
data.container.classList.toggle('action-list-custom', !!element.className);
|
||||
if (element.className) {
|
||||
data.container.classList.add(element.className);
|
||||
}
|
||||
data.previousClassName = element.className;
|
||||
|
||||
data.text.textContent = stripNewlines(element.label);
|
||||
|
||||
// Render optional badge
|
||||
if (element.badge) {
|
||||
data.badge.textContent = element.badge;
|
||||
data.badge.style.display = '';
|
||||
} else {
|
||||
data.badge.textContent = '';
|
||||
data.badge.style.display = 'none';
|
||||
}
|
||||
|
||||
// if there is a keybinding, prioritize over description for now
|
||||
if (element.keybinding) {
|
||||
if (element.descriptionButton) {
|
||||
data.description!.textContent = '';
|
||||
data.description!.style.display = 'inline';
|
||||
const button = new Button(data.description!, { ...defaultButtonStyles, small: true });
|
||||
button.label = element.descriptionButton.label;
|
||||
data.elementDisposables.add(button.onDidClick(e => {
|
||||
e?.stopPropagation();
|
||||
element.descriptionButton!.onDidClick();
|
||||
}));
|
||||
data.elementDisposables.add(button);
|
||||
} else if (element.keybinding) {
|
||||
data.description!.textContent = element.keybinding.getLabel();
|
||||
data.description!.style.display = 'inline';
|
||||
data.description!.style.letterSpacing = '0.5px';
|
||||
@@ -261,6 +322,26 @@ function getKeyboardNavigationLabel<T>(item: IActionListItem<T>): string | undef
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for configuring the action list.
|
||||
*/
|
||||
export interface IActionListOptions {
|
||||
/**
|
||||
* When true, shows a filter input at the bottom of the list.
|
||||
*/
|
||||
readonly showFilter?: boolean;
|
||||
|
||||
/**
|
||||
* Section IDs that should be collapsed by default.
|
||||
*/
|
||||
readonly collapsedByDefault?: ReadonlySet<string>;
|
||||
|
||||
/**
|
||||
* Minimum width for the action list.
|
||||
*/
|
||||
readonly minWidth?: number;
|
||||
}
|
||||
|
||||
export class ActionList<T> extends Disposable {
|
||||
|
||||
public readonly domNode: HTMLElement;
|
||||
@@ -277,12 +358,20 @@ export class ActionList<T> extends Disposable {
|
||||
|
||||
private _hover = this._register(new MutableDisposable<IHoverWidget>());
|
||||
|
||||
private readonly _collapsedSections = new Set<string>();
|
||||
private _filterText = '';
|
||||
private readonly _filterInput: HTMLInputElement | undefined;
|
||||
private readonly _filterContainer: HTMLElement | undefined;
|
||||
private _lastMinWidth = 0;
|
||||
private _hasLaidOut = false;
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
preview: boolean,
|
||||
items: readonly IActionListItem<T>[],
|
||||
private readonly _delegate: IActionListDelegate<T>,
|
||||
accessibilityProvider: Partial<IListAccessibilityProvider<IActionListItem<T>>> | undefined,
|
||||
private readonly _options: IActionListOptions | undefined,
|
||||
@IContextViewService private readonly _contextViewService: IContextViewService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||
@ILayoutService private readonly _layoutService: ILayoutService,
|
||||
@@ -291,6 +380,14 @@ export class ActionList<T> extends Disposable {
|
||||
super();
|
||||
this.domNode = document.createElement('div');
|
||||
this.domNode.classList.add('actionList');
|
||||
|
||||
// Initialize collapsed sections
|
||||
if (this._options?.collapsedByDefault) {
|
||||
for (const section of this._options.collapsedByDefault) {
|
||||
this._collapsedSections.add(section);
|
||||
}
|
||||
}
|
||||
|
||||
const virtualDelegate: IListVirtualDelegate<IActionListItem<T>> = {
|
||||
getHeight: element => {
|
||||
switch (element.kind) {
|
||||
@@ -312,7 +409,7 @@ export class ActionList<T> extends Disposable {
|
||||
new SeparatorRenderer(),
|
||||
], {
|
||||
keyboardSupport: false,
|
||||
typeNavigationEnabled: true,
|
||||
typeNavigationEnabled: !this._options?.showFilter,
|
||||
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel },
|
||||
accessibilityProvider: {
|
||||
getAriaLabel: element => {
|
||||
@@ -352,13 +449,151 @@ export class ActionList<T> extends Disposable {
|
||||
this._register(this._list.onDidChangeSelection(e => this.onListSelection(e)));
|
||||
|
||||
this._allMenuItems = items;
|
||||
this._list.splice(0, this._list.length, this._allMenuItems);
|
||||
|
||||
// Create filter input
|
||||
if (this._options?.showFilter) {
|
||||
this._filterContainer = document.createElement('div');
|
||||
this._filterContainer.className = 'action-list-filter';
|
||||
|
||||
this._filterInput = document.createElement('input');
|
||||
this._filterInput.type = 'text';
|
||||
this._filterInput.className = 'action-list-filter-input';
|
||||
this._filterInput.placeholder = localize('actionList.filter.placeholder', "Search...");
|
||||
this._filterInput.setAttribute('aria-label', localize('actionList.filter.ariaLabel', "Filter items"));
|
||||
this._filterContainer.appendChild(this._filterInput);
|
||||
|
||||
this._register(dom.addDisposableListener(this._filterInput, 'input', () => {
|
||||
this._filterText = this._filterInput!.value;
|
||||
this._applyFilter();
|
||||
}));
|
||||
|
||||
// Keyboard navigation from filter input
|
||||
this._register(dom.addDisposableListener(this._filterInput, 'keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
this._list.domFocus();
|
||||
const lastIndex = this._list.length - 1;
|
||||
if (lastIndex >= 0) {
|
||||
this._list.focusLast(undefined, this.focusCondition);
|
||||
}
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
this._list.domFocus();
|
||||
this.focusNext();
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.acceptSelected();
|
||||
} else if (e.key === 'Escape') {
|
||||
if (this._filterText) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this._filterInput!.value = '';
|
||||
this._filterText = '';
|
||||
this._applyFilter();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
this._applyFilter();
|
||||
|
||||
if (this._list.length) {
|
||||
this.focusNext();
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleSection(section: string): void {
|
||||
if (this._collapsedSections.has(section)) {
|
||||
this._collapsedSections.delete(section);
|
||||
} else {
|
||||
this._collapsedSections.add(section);
|
||||
}
|
||||
this._applyFilter();
|
||||
}
|
||||
|
||||
private _applyFilter(): void {
|
||||
const filterLower = this._filterText.toLowerCase();
|
||||
const isFiltering = filterLower.length > 0;
|
||||
const visible: IActionListItem<T>[] = [];
|
||||
|
||||
for (const item of this._allMenuItems) {
|
||||
if (item.kind === ActionListItemKind.Header) {
|
||||
if (isFiltering) {
|
||||
// When filtering, skip all headers
|
||||
continue;
|
||||
}
|
||||
visible.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.kind === ActionListItemKind.Separator) {
|
||||
if (isFiltering) {
|
||||
continue;
|
||||
}
|
||||
visible.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Action item
|
||||
if (isFiltering) {
|
||||
// When filtering, skip section toggle items and only match content
|
||||
if (item.isSectionToggle) {
|
||||
continue;
|
||||
}
|
||||
// Match against label and description
|
||||
const label = (item.label ?? '').toLowerCase();
|
||||
const desc = (item.description ?? '').toLowerCase();
|
||||
if (label.includes(filterLower) || desc.includes(filterLower)) {
|
||||
visible.push(item);
|
||||
}
|
||||
} else {
|
||||
// Update icon for section toggle items based on collapsed state
|
||||
if (item.isSectionToggle && item.section) {
|
||||
const collapsed = this._collapsedSections.has(item.section);
|
||||
visible.push({
|
||||
...item,
|
||||
group: { ...item.group!, icon: collapsed ? Codicon.chevronRight : Codicon.chevronDown },
|
||||
});
|
||||
continue;
|
||||
}
|
||||
// Not filtering - check collapsed sections
|
||||
if (item.section && this._collapsedSections.has(item.section)) {
|
||||
continue;
|
||||
}
|
||||
visible.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Capture whether the filter input currently has focus before splice
|
||||
// which may cause DOM changes that shift focus.
|
||||
const filterInputHasFocus = this._filterInput && dom.isActiveElement(this._filterInput);
|
||||
|
||||
this._list.splice(0, this._list.length, visible);
|
||||
|
||||
// Re-layout to adjust height after items changed
|
||||
if (this._hasLaidOut) {
|
||||
this.layout(this._lastMinWidth);
|
||||
// Restore focus after splice destroyed DOM elements,
|
||||
// otherwise the blur handler in ActionWidgetService closes the widget.
|
||||
// Keep focus on the filter input if the user is typing a filter.
|
||||
if (filterInputHasFocus) {
|
||||
this._filterInput!.focus();
|
||||
} else {
|
||||
this._list.domFocus();
|
||||
}
|
||||
// Reposition the context view so the widget grows in the correct direction
|
||||
this._contextViewService.layout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filter container element, if filter is enabled.
|
||||
* The caller is responsible for appending it to the widget DOM.
|
||||
*/
|
||||
get filterContainer(): HTMLElement | undefined {
|
||||
return this._filterContainer;
|
||||
}
|
||||
|
||||
private focusCondition(element: IActionListItem<unknown>): boolean {
|
||||
return !element.disabled && element.kind === ActionListItemKind.Action;
|
||||
}
|
||||
@@ -371,39 +606,57 @@ export class ActionList<T> extends Disposable {
|
||||
}
|
||||
|
||||
layout(minWidth: number): number {
|
||||
// Updating list height, depending on how many separators and headers there are.
|
||||
const numHeaders = this._allMenuItems.filter(item => item.kind === 'header').length;
|
||||
const numSeparators = this._allMenuItems.filter(item => item.kind === 'separator').length;
|
||||
const itemsHeight = this._allMenuItems.length * this._actionLineHeight;
|
||||
const heightWithHeaders = itemsHeight + numHeaders * this._headerLineHeight - numHeaders * this._actionLineHeight;
|
||||
const heightWithSeparators = heightWithHeaders + numSeparators * this._separatorLineHeight - numSeparators * this._actionLineHeight;
|
||||
this._list.layout(heightWithSeparators);
|
||||
let maxWidth = minWidth;
|
||||
this._hasLaidOut = true;
|
||||
this._lastMinWidth = minWidth;
|
||||
// Compute height based on currently visible items in the list
|
||||
const visibleCount = this._list.length;
|
||||
let listHeight = 0;
|
||||
for (let i = 0; i < visibleCount; i++) {
|
||||
const element = this._list.element(i);
|
||||
switch (element.kind) {
|
||||
case ActionListItemKind.Header:
|
||||
listHeight += this._headerLineHeight;
|
||||
break;
|
||||
case ActionListItemKind.Separator:
|
||||
listHeight += this._separatorLineHeight;
|
||||
break;
|
||||
default:
|
||||
listHeight += this._actionLineHeight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._allMenuItems.length >= 50) {
|
||||
maxWidth = 380;
|
||||
this._list.layout(listHeight);
|
||||
const effectiveMinWidth = Math.max(minWidth, this._options?.minWidth ?? 0);
|
||||
let maxWidth = effectiveMinWidth;
|
||||
|
||||
if (visibleCount >= 50) {
|
||||
maxWidth = Math.max(380, effectiveMinWidth);
|
||||
} else {
|
||||
// For finding width dynamically (not using resize observer)
|
||||
const itemWidths: number[] = this._allMenuItems.map((_, index): number => {
|
||||
const element = this._getRowElement(index);
|
||||
const itemWidths: number[] = [];
|
||||
for (let i = 0; i < visibleCount; i++) {
|
||||
const element = this._getRowElement(i);
|
||||
if (element) {
|
||||
element.style.width = 'auto';
|
||||
const width = element.getBoundingClientRect().width;
|
||||
element.style.width = '';
|
||||
return width;
|
||||
itemWidths.push(width);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
// resize observer - can be used in the future since list widget supports dynamic height but not width
|
||||
maxWidth = Math.max(...itemWidths, minWidth);
|
||||
maxWidth = Math.max(...itemWidths, effectiveMinWidth);
|
||||
}
|
||||
|
||||
const filterHeight = this._filterContainer ? 36 : 0;
|
||||
const maxVhPrecentage = 0.7;
|
||||
const height = Math.min(heightWithSeparators, this._layoutService.getContainer(dom.getWindow(this.domNode)).clientHeight * maxVhPrecentage);
|
||||
this._list.layout(height, maxWidth);
|
||||
const maxHeight = this._layoutService.getContainer(dom.getWindow(this.domNode)).clientHeight * maxVhPrecentage;
|
||||
const height = Math.min(listHeight + filterHeight, maxHeight);
|
||||
const listFinalHeight = height - filterHeight;
|
||||
this._list.layout(listFinalHeight, maxWidth);
|
||||
|
||||
this.domNode.style.height = `${height}px`;
|
||||
this.domNode.style.height = `${listFinalHeight}px`;
|
||||
|
||||
this._list.domFocus();
|
||||
return maxWidth;
|
||||
@@ -447,6 +700,10 @@ export class ActionList<T> extends Disposable {
|
||||
}
|
||||
|
||||
const element = e.elements[0];
|
||||
if (element.isSectionToggle) {
|
||||
this._list.setSelection([]);
|
||||
return;
|
||||
}
|
||||
if (element.item && this.focusCondition(element)) {
|
||||
this._delegate.onSelect(element.item, e.browserEvent instanceof PreviewSelectedEvent);
|
||||
} else {
|
||||
@@ -526,6 +783,11 @@ export class ActionList<T> extends Disposable {
|
||||
}
|
||||
|
||||
private onListClick(e: IListMouseEvent<IActionListItem<T>>): void {
|
||||
if (e.element && e.element.isSectionToggle && e.element.section) {
|
||||
const section = e.element.section;
|
||||
queueMicrotask(() => this._toggleSection(section));
|
||||
return;
|
||||
}
|
||||
if (e.element && this.focusCondition(e.element)) {
|
||||
this._list.setFocus([]);
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
color: var(--vscode-foreground) !important;
|
||||
}
|
||||
|
||||
.action-widget .monaco-list-row.action .codicon {
|
||||
@@ -150,6 +151,16 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.action-widget .monaco-list-row.action .action-item-badge {
|
||||
padding: 0px 6px;
|
||||
border-radius: 10px;
|
||||
background-color: var(--vscode-badge-background);
|
||||
color: var(--vscode-badge-foreground);
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-widget .monaco-list-row.action .monaco-keybinding > .monaco-keybinding-key {
|
||||
background-color: var(--vscode-keybindingLabel-background);
|
||||
color: var(--vscode-keybindingLabel-foreground);
|
||||
@@ -205,8 +216,10 @@
|
||||
.action-widget .monaco-list .monaco-list-row .description {
|
||||
opacity: 0.7;
|
||||
margin-left: 0.5em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Item toolbar - shows on hover/focus */
|
||||
.action-widget .monaco-list-row.action .action-list-item-toolbar {
|
||||
display: none;
|
||||
@@ -227,3 +240,29 @@
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Filter input */
|
||||
.action-widget .action-list-filter {
|
||||
border-top: 1px solid var(--vscode-editorHoverWidget-border);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.action-widget .action-list-filter-input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--vscode-input-border, transparent);
|
||||
border-radius: 3px;
|
||||
background-color: var(--vscode-input-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
font-size: 12px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.action-widget .action-list-filter-input:focus {
|
||||
border-color: var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
.action-widget .action-list-filter-input::placeholder {
|
||||
color: var(--vscode-input-placeholderForeground);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { KeyCode, KeyMod } from '../../../base/common/keyCodes.js';
|
||||
import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../base/common/lifecycle.js';
|
||||
import './actionWidget.css';
|
||||
import { localize, localize2 } from '../../../nls.js';
|
||||
import { acceptSelectedActionCommand, ActionList, IActionListDelegate, IActionListItem, previewSelectedActionCommand } from './actionList.js';
|
||||
import { acceptSelectedActionCommand, ActionList, IActionListDelegate, IActionListItem, IActionListOptions, previewSelectedActionCommand } from './actionList.js';
|
||||
import { Action2, registerAction2 } from '../../actions/common/actions.js';
|
||||
import { IContextKeyService, RawContextKey } from '../../contextkey/common/contextkey.js';
|
||||
import { IContextViewService } from '../../contextview/browser/contextView.js';
|
||||
@@ -36,7 +36,7 @@ export const IActionWidgetService = createDecorator<IActionWidgetService>('actio
|
||||
export interface IActionWidgetService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
show<T>(user: string, supportsPreview: boolean, items: readonly IActionListItem<T>[], delegate: IActionListDelegate<T>, anchor: HTMLElement | StandardMouseEvent | IAnchor, container: HTMLElement | undefined, actionBarActions?: readonly IAction[], accessibilityProvider?: Partial<IListAccessibilityProvider<IActionListItem<T>>>): void;
|
||||
show<T>(user: string, supportsPreview: boolean, items: readonly IActionListItem<T>[], delegate: IActionListDelegate<T>, anchor: HTMLElement | StandardMouseEvent | IAnchor, container: HTMLElement | undefined, actionBarActions?: readonly IAction[], accessibilityProvider?: Partial<IListAccessibilityProvider<IActionListItem<T>>>, listOptions?: IActionListOptions): void;
|
||||
|
||||
hide(didCancel?: boolean): void;
|
||||
|
||||
@@ -60,10 +60,10 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {
|
||||
super();
|
||||
}
|
||||
|
||||
show<T>(user: string, supportsPreview: boolean, items: readonly IActionListItem<T>[], delegate: IActionListDelegate<T>, anchor: HTMLElement | StandardMouseEvent | IAnchor, container: HTMLElement | undefined, actionBarActions?: readonly IAction[], accessibilityProvider?: Partial<IListAccessibilityProvider<IActionListItem<T>>>): void {
|
||||
show<T>(user: string, supportsPreview: boolean, items: readonly IActionListItem<T>[], delegate: IActionListDelegate<T>, anchor: HTMLElement | StandardMouseEvent | IAnchor, container: HTMLElement | undefined, actionBarActions?: readonly IAction[], accessibilityProvider?: Partial<IListAccessibilityProvider<IActionListItem<T>>>, listOptions?: IActionListOptions): void {
|
||||
const visibleContext = ActionWidgetContextKeys.Visible.bindTo(this._contextKeyService);
|
||||
|
||||
const list = this._instantiationService.createInstance(ActionList, user, supportsPreview, items, delegate, accessibilityProvider);
|
||||
const list = this._instantiationService.createInstance(ActionList, user, supportsPreview, items, delegate, accessibilityProvider, listOptions);
|
||||
this._contextViewService.showContextView({
|
||||
getAnchor: () => anchor,
|
||||
render: (container: HTMLElement) => {
|
||||
@@ -137,6 +137,11 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {
|
||||
}
|
||||
}
|
||||
|
||||
// Filter input (appended after the list, before action bar visually)
|
||||
if (this._list.value?.filterContainer) {
|
||||
widget.appendChild(this._list.value.filterContainer);
|
||||
}
|
||||
|
||||
const width = this._list.value?.layout(actionBarWidth);
|
||||
widget.style.width = `${width}px`;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IActionWidgetService } from './actionWidget.js';
|
||||
import { IAction } from '../../../base/common/actions.js';
|
||||
import { BaseDropdown, IActionProvider, IBaseDropdownOptions } from '../../../base/browser/ui/dropdown/dropdown.js';
|
||||
import { ActionListItemKind, IActionListDelegate, IActionListItem, IActionListItemHover } from './actionList.js';
|
||||
import { ActionListItemKind, IActionListDelegate, IActionListItem, IActionListItemHover, IActionListOptions } from './actionList.js';
|
||||
import { ThemeIcon } from '../../../base/common/themables.js';
|
||||
import { Codicon } from '../../../base/common/codicons.js';
|
||||
import { getActiveElement, isHTMLElement } from '../../../base/browser/dom.js';
|
||||
@@ -52,6 +52,11 @@ export interface IActionWidgetDropdownOptions extends IBaseDropdownOptions {
|
||||
* provided, no telemetry will be sent.
|
||||
*/
|
||||
readonly reporter?: { id: string; name?: string; includeOptions?: boolean };
|
||||
|
||||
/**
|
||||
* Options for the underlying ActionList (filter, collapsible sections).
|
||||
*/
|
||||
readonly listOptions?: IActionListOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,7 +206,8 @@ export class ActionWidgetDropdown extends BaseDropdown {
|
||||
this._options.getAnchor?.() ?? this.element,
|
||||
undefined,
|
||||
actionBarActions,
|
||||
accessibilityProvider
|
||||
accessibilityProvider,
|
||||
this._options.listOptions
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -281,10 +281,11 @@ export class MenuId {
|
||||
static readonly ChatToolOutputResourceContext = new MenuId('ChatToolOutputResourceContext');
|
||||
static readonly ChatMultiDiffContext = new MenuId('ChatMultiDiffContext');
|
||||
static readonly ChatConfirmationMenu = new MenuId('ChatConfirmationMenu');
|
||||
static readonly ChatEditorInlineGutter = new MenuId('ChatEditorInlineGutter');
|
||||
static readonly ChatEditorInlineMenu = new MenuId('ChatEditorInlineGutter');
|
||||
static readonly ChatEditorInlineExecute = new MenuId('ChatEditorInputExecute');
|
||||
static readonly ChatEditorInlineInputSide = new MenuId('ChatEditorInputSide');
|
||||
static readonly InlineChatEditorAffordance = new MenuId('InlineChatEditorAffordance');
|
||||
static readonly InlineChatInput = new MenuId('InlineChatInput');
|
||||
static readonly AccessibleView = new MenuId('AccessibleView');
|
||||
static readonly MultiDiffEditorContent = new MenuId('MultiDiffEditorContent');
|
||||
static readonly MultiDiffEditorFileToolbar = new MenuId('MultiDiffEditorFileToolbar');
|
||||
|
||||
@@ -46,6 +46,10 @@ export interface INativeBrowserElementsService {
|
||||
getElementData(rect: IRectangle, token: CancellationToken, locator: IBrowserTargetLocator, cancellationId?: number): Promise<IElementData | undefined>;
|
||||
|
||||
startDebugSession(token: CancellationToken, locator: IBrowserTargetLocator, cancelAndDetachId?: number): Promise<void>;
|
||||
|
||||
startConsoleSession(token: CancellationToken, locator: IBrowserTargetLocator, cancelAndDetachId?: number): Promise<void>;
|
||||
|
||||
getConsoleLogs(locator: IBrowserTargetLocator): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,17 @@ interface NodeDataResponse {
|
||||
bounds: IRectangle;
|
||||
}
|
||||
|
||||
const MAX_CONSOLE_LOG_ENTRIES = 1000;
|
||||
const consoleLogStore = new Map<string, string[]>();
|
||||
|
||||
function locatorKey(locator: IBrowserTargetLocator): string {
|
||||
const key = locator.browserViewId ?? locator.webviewId;
|
||||
if (!key) {
|
||||
return 'unknown';
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
export class NativeBrowserElementsMainService extends Disposable implements INativeBrowserElementsMainService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
@@ -38,11 +49,82 @@ export class NativeBrowserElementsMainService extends Disposable implements INat
|
||||
|
||||
get windowId(): never { throw new Error('Not implemented in electron-main'); }
|
||||
|
||||
async getConsoleLogs(windowId: number | undefined, locator: IBrowserTargetLocator): Promise<string | undefined> {
|
||||
const key = locatorKey(locator);
|
||||
const entries = consoleLogStore.get(key);
|
||||
if (!entries || entries.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return entries.join('\n');
|
||||
}
|
||||
|
||||
async startConsoleSession(windowId: number | undefined, token: CancellationToken, locator: IBrowserTargetLocator, cancelAndDetachId?: number): Promise<void> {
|
||||
const window = this.windowById(windowId);
|
||||
if (!window?.win) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let targetWebContents: Electron.WebContents | undefined;
|
||||
if (locator.browserViewId) {
|
||||
targetWebContents = this.browserViewMainService.tryGetBrowserView(locator.browserViewId)?.webContents;
|
||||
}
|
||||
|
||||
if (!targetWebContents) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const key = locatorKey(locator);
|
||||
if (!consoleLogStore.has(key)) {
|
||||
consoleLogStore.set(key, []);
|
||||
}
|
||||
|
||||
const levelMap: Record<number, string> = { 0: 'log', 1: 'warning', 2: 'error' };
|
||||
const onConsoleMessage = (_event: Electron.Event, level: number, message: string, _line: number, _sourceId: string) => {
|
||||
const levelName = levelMap[level] ?? 'log';
|
||||
const formatted = `[${levelName}] ${message}`;
|
||||
const current = consoleLogStore.get(key) ?? [];
|
||||
current.push(formatted);
|
||||
if (current.length > MAX_CONSOLE_LOG_ENTRIES) {
|
||||
current.splice(0, current.length - MAX_CONSOLE_LOG_ENTRIES);
|
||||
}
|
||||
consoleLogStore.set(key, current);
|
||||
};
|
||||
|
||||
const cleanupListeners = () => {
|
||||
targetWebContents?.off('console-message', onConsoleMessage);
|
||||
window.win?.webContents.off('ipc-message', onIpcMessage);
|
||||
};
|
||||
|
||||
const onIpcMessage = async (_event: Electron.Event, channel: string, closedCancelAndDetachId: number) => {
|
||||
if (channel === `vscode:cancelConsoleSession${cancelAndDetachId}`) {
|
||||
if (cancelAndDetachId !== closedCancelAndDetachId) {
|
||||
return;
|
||||
}
|
||||
cleanupListeners();
|
||||
consoleLogStore.delete(key);
|
||||
}
|
||||
};
|
||||
|
||||
targetWebContents.on('console-message', onConsoleMessage);
|
||||
|
||||
targetWebContents.once('destroyed', () => {
|
||||
cleanupListeners();
|
||||
consoleLogStore.delete(key);
|
||||
});
|
||||
|
||||
token.onCancellationRequested(() => {
|
||||
cleanupListeners();
|
||||
consoleLogStore.delete(key);
|
||||
});
|
||||
|
||||
window.win.webContents.on('ipc-message', onIpcMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the webview target that matches the given locator.
|
||||
* Checks either webviewId or browserViewId depending on what's provided.
|
||||
*/
|
||||
async findWebviewTarget(debuggers: Electron.Debugger, locator: IBrowserTargetLocator): Promise<string | undefined> {
|
||||
private async findWebviewTarget(debuggers: Electron.Debugger, locator: IBrowserTargetLocator): Promise<string | undefined> {
|
||||
const { targetInfos } = await debuggers.sendCommand('Target.getTargets');
|
||||
|
||||
if (locator.webviewId) {
|
||||
|
||||
@@ -275,9 +275,4 @@ export interface IBrowserViewService {
|
||||
* @param id The browser view identifier
|
||||
*/
|
||||
clearStorage(id: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get a CDP WebSocket endpoint URL.
|
||||
*/
|
||||
getDebugWebSocketEndpoint(): Promise<string>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from '../../../base/common/event.js';
|
||||
import { IDisposable } from '../../../base/common/lifecycle.js';
|
||||
|
||||
export const ipcBrowserViewGroupChannelName = 'browserViewGroup';
|
||||
|
||||
/**
|
||||
* Fired when a browser view is added to or removed from a group.
|
||||
*/
|
||||
export interface IBrowserViewGroupViewEvent {
|
||||
/** The ID of the browser view that was added or removed. */
|
||||
readonly viewId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A browser view group - an isolated collection of browser views.
|
||||
*
|
||||
* This interface is shared between the main-process entity and remote proxies.
|
||||
*/
|
||||
export interface IBrowserViewGroup extends IDisposable {
|
||||
readonly id: string;
|
||||
|
||||
readonly onDidAddView: Event<IBrowserViewGroupViewEvent>;
|
||||
readonly onDidRemoveView: Event<IBrowserViewGroupViewEvent>;
|
||||
readonly onDidDestroy: Event<void>;
|
||||
|
||||
addView(viewId: string): Promise<void>;
|
||||
removeView(viewId: string): Promise<void>;
|
||||
getDebugWebSocketEndpoint(): Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common service for managing browser view groups across processes.
|
||||
*
|
||||
* A browser view group is an isolated collection of browser views that can be
|
||||
* independently exposed to different services or CDP clients.
|
||||
*
|
||||
* This interface is consumed via {@link ProxyChannel}.
|
||||
* The main-process implementation is {@link BrowserViewGroupMainService}.
|
||||
*/
|
||||
export interface IBrowserViewGroupService {
|
||||
|
||||
// Dynamic events - one per group instance, keyed by group ID.
|
||||
onDynamicDidAddView(groupId: string): Event<IBrowserViewGroupViewEvent>;
|
||||
onDynamicDidRemoveView(groupId: string): Event<IBrowserViewGroupViewEvent>;
|
||||
onDynamicDidDestroy(groupId: string): Event<void>;
|
||||
|
||||
/**
|
||||
* Create a new browser view group.
|
||||
* @returns The id of the newly created group.
|
||||
*/
|
||||
createGroup(): Promise<string>;
|
||||
|
||||
/**
|
||||
* Destroy a browser view group.
|
||||
* Views in the group are **not** destroyed - they are simply detached.
|
||||
* @param groupId The group identifier.
|
||||
*/
|
||||
destroyGroup(groupId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Add a browser view to a group.
|
||||
* A view can belong to multiple groups simultaneously.
|
||||
* @param groupId The group identifier.
|
||||
* @param viewId The browser view identifier.
|
||||
*/
|
||||
addViewToGroup(groupId: string, viewId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove a browser view from a group.
|
||||
* @param groupId The group identifier.
|
||||
* @param viewId The browser view identifier.
|
||||
*/
|
||||
removeViewFromGroup(groupId: string, viewId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get a short-lived CDP WebSocket endpoint URL for a specific group.
|
||||
* The returned URL contains a single-use token.
|
||||
* @param groupId The group identifier.
|
||||
*/
|
||||
getDebugWebSocketEndpoint(groupId: string): Promise<string>;
|
||||
}
|
||||
@@ -22,44 +22,60 @@ export interface IBrowserViewCDPProxyServer {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Returns a debug endpoint with a short-lived, single-use token.
|
||||
* Returns a debug endpoint with a short-lived, single-use token for a specific browser target.
|
||||
*/
|
||||
getWebSocketEndpoint(): Promise<string>;
|
||||
getWebSocketEndpointForTarget(target: ICDPBrowserTarget): Promise<string>;
|
||||
|
||||
/**
|
||||
* Unregister a previously registered browser target.
|
||||
*/
|
||||
removeTarget(target: ICDPBrowserTarget): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket server that provides CDP debugging for browser views.
|
||||
*
|
||||
* Manages a registry of {@link ICDPBrowserTarget} instances, each reachable
|
||||
* at its own `/devtools/browser/{id}` WebSocket endpoint.
|
||||
*/
|
||||
export class BrowserViewCDPProxyServer extends Disposable implements IBrowserViewCDPProxyServer {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private server: http.Server | undefined;
|
||||
private port: number | undefined;
|
||||
private readonly tokens: TokenManager;
|
||||
|
||||
private readonly tokens = this._register(new TokenManager<string>());
|
||||
private readonly targets = new Map<string, ICDPBrowserTarget>();
|
||||
|
||||
constructor(
|
||||
private readonly browserTarget: ICDPBrowserTarget,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.tokens = this._register(new TokenManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a debug endpoint with a short-lived, single-use token in the
|
||||
* WebSocket URL. The token is revoked once a WebSocket connection is made
|
||||
* or after 30 seconds, whichever comes first.
|
||||
* Register a browser target and return a WebSocket endpoint URL for it.
|
||||
* The target is reachable at `/devtools/browser/{targetId}`.
|
||||
*/
|
||||
async getWebSocketEndpoint(): Promise<string> {
|
||||
async getWebSocketEndpointForTarget(target: ICDPBrowserTarget): Promise<string> {
|
||||
await this.ensureServerStarted();
|
||||
|
||||
const token = await this.tokens.issueToken();
|
||||
return this.getWebSocketUrl(token);
|
||||
const targetInfo = await target.getTargetInfo();
|
||||
const targetId = targetInfo.targetId;
|
||||
|
||||
// Register (or re-register) the target
|
||||
this.targets.set(targetId, target);
|
||||
|
||||
const token = await this.tokens.issueToken(targetId);
|
||||
return `ws://localhost:${this.port}/devtools/browser/${targetId}?token=${token}`;
|
||||
}
|
||||
|
||||
private getWebSocketUrl(token: string): string {
|
||||
return `ws://localhost:${this.port}/devtools/browser?token=${token}`;
|
||||
/**
|
||||
* Unregister a previously registered browser target.
|
||||
*/
|
||||
async removeTarget(target: ICDPBrowserTarget): Promise<void> {
|
||||
const targetInfo = await target.getTargetInfo();
|
||||
this.targets.delete(targetInfo.targetId);
|
||||
}
|
||||
|
||||
private async ensureServerStarted(): Promise<void> {
|
||||
@@ -93,14 +109,7 @@ export class BrowserViewCDPProxyServer extends Disposable implements IBrowserVie
|
||||
private handleWebSocketUpgrade(req: http.IncomingMessage, socket: Socket): void {
|
||||
const [pathname, params] = (req.url || '').split('?');
|
||||
|
||||
const token = new URLSearchParams(params).get('token');
|
||||
if (!token || !this.tokens.consumeToken(token)) {
|
||||
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const browserMatch = pathname.match(/^\/devtools\/browser(\/.*)?$/);
|
||||
const browserMatch = pathname.match(/^\/devtools\/browser\/([^/?]+)$/);
|
||||
|
||||
this.logService.debug(`[BrowserViewDebugProxy] WebSocket upgrade requested: ${pathname}`);
|
||||
|
||||
@@ -111,6 +120,24 @@ export class BrowserViewCDPProxyServer extends Disposable implements IBrowserVie
|
||||
return;
|
||||
}
|
||||
|
||||
const targetId = browserMatch[1];
|
||||
|
||||
const token = new URLSearchParams(params).get('token');
|
||||
const tokenTargetId = token && this.tokens.consumeToken(token);
|
||||
if (!tokenTargetId || tokenTargetId !== targetId) {
|
||||
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const target = this.targets.get(targetId);
|
||||
if (!target) {
|
||||
this.logService.warn(`[BrowserViewDebugProxy] Browser target not found: ${targetId}`);
|
||||
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.debug(`[BrowserViewDebugProxy] WebSocket connected: ${pathname}`);
|
||||
|
||||
const upgraded = upgradeToISocket(req, socket, {
|
||||
@@ -122,7 +149,7 @@ export class BrowserViewCDPProxyServer extends Disposable implements IBrowserVie
|
||||
return;
|
||||
}
|
||||
|
||||
const proxy = new CDPBrowserProxy(this.browserTarget);
|
||||
const proxy = new CDPBrowserProxy(target);
|
||||
const disposables = this.wireWebSocket(upgraded, proxy);
|
||||
this._register(disposables);
|
||||
this._register(upgraded);
|
||||
@@ -200,31 +227,35 @@ export class BrowserViewCDPProxyServer extends Disposable implements IBrowserVie
|
||||
}
|
||||
}
|
||||
|
||||
class TokenManager extends Disposable {
|
||||
/** Map of currently valid single-use tokens. Each expires after 30 seconds. */
|
||||
private readonly tokens = new Map<string, { expiresAt: number }>();
|
||||
class TokenManager<TDetails> extends Disposable {
|
||||
/** Map of currently valid single-use tokens to their associated details. */
|
||||
private readonly tokens = new Map<string, { details: TDetails; expiresAt: number }>();
|
||||
|
||||
/**
|
||||
* Creates a short-lived, single-use token.
|
||||
* Creates a short-lived, single-use token bound to a specific target.
|
||||
* The token is revoked once consumed or after 30 seconds.
|
||||
*/
|
||||
async issueToken(): Promise<string> {
|
||||
async issueToken(details: TDetails): Promise<string> {
|
||||
const token = this.makeToken();
|
||||
this.tokens.set(token, { expiresAt: Date.now() + 30_000 });
|
||||
this.tokens.set(token, { details: Object.freeze(details), expiresAt: Date.now() + 30_000 });
|
||||
this._register(disposableTimeout(() => this.tokens.delete(token), 30_000));
|
||||
return token;
|
||||
}
|
||||
|
||||
consumeToken(token: string): boolean {
|
||||
/**
|
||||
* Consume a token. Returns the details it was issued with, or
|
||||
* `undefined` if the token is invalid or expired.
|
||||
*/
|
||||
consumeToken(token: string): TDetails | undefined {
|
||||
if (!token) {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
const info = this.tokens.get(token);
|
||||
if (!info) {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
this.tokens.delete(token);
|
||||
return Date.now() <= info.expiresAt;
|
||||
return Date.now() <= info.expiresAt ? info.details : undefined;
|
||||
}
|
||||
|
||||
private makeToken(): string {
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';
|
||||
import { Emitter, Event } from '../../../base/common/event.js';
|
||||
import { BrowserView } from './browserView.js';
|
||||
import { ICDPTarget, CDPBrowserVersion, CDPWindowBounds, CDPTargetInfo, ICDPConnection, ICDPBrowserTarget } from '../common/cdp/types.js';
|
||||
import { CDPBrowserProxy } from '../common/cdp/proxy.js';
|
||||
import { IBrowserViewGroup, IBrowserViewGroupViewEvent } from '../common/browserViewGroup.js';
|
||||
import { IBrowserViewCDPProxyServer } from './browserViewCDPProxyServer.js';
|
||||
import { IBrowserViewMainService } from './browserViewMainService.js';
|
||||
|
||||
/**
|
||||
* An isolated group of {@link BrowserView} instances exposed as CDP targets.
|
||||
*
|
||||
* Each group represents an independent CDP "browser" endpoint
|
||||
* (`/devtools/browser/{id}`). Different groups can expose different
|
||||
* subsets of browser views, enabling selective target visibility across
|
||||
* CDP sessions.
|
||||
*
|
||||
* Created via {@link BrowserViewGroupMainService.createGroup}.
|
||||
*/
|
||||
export class BrowserViewGroup extends Disposable implements ICDPBrowserTarget, IBrowserViewGroup {
|
||||
|
||||
private readonly views = new Map<string, BrowserView>();
|
||||
private readonly viewListeners = this._register(new DisposableStore());
|
||||
|
||||
/** All context IDs known to this group, including those from views added to it. */
|
||||
private readonly knownContextIds = new Set<string>();
|
||||
/** Browser context IDs created by this group via {@link createBrowserContext}. */
|
||||
private readonly ownedContextIds = new Set<string>();
|
||||
|
||||
private readonly _onTargetCreated = this._register(new Emitter<BrowserView>());
|
||||
readonly onTargetCreated: Event<BrowserView> = this._onTargetCreated.event;
|
||||
|
||||
private readonly _onTargetDestroyed = this._register(new Emitter<BrowserView>());
|
||||
readonly onTargetDestroyed: Event<BrowserView> = this._onTargetDestroyed.event;
|
||||
|
||||
private readonly _onDidAddView = this._register(new Emitter<IBrowserViewGroupViewEvent>());
|
||||
readonly onDidAddView: Event<IBrowserViewGroupViewEvent> = this._onDidAddView.event;
|
||||
|
||||
private readonly _onDidRemoveView = this._register(new Emitter<IBrowserViewGroupViewEvent>());
|
||||
readonly onDidRemoveView: Event<IBrowserViewGroupViewEvent> = this._onDidRemoveView.event;
|
||||
|
||||
private readonly _onDidDestroy = this._register(new Emitter<void>());
|
||||
readonly onDidDestroy: Event<void> = this._onDidDestroy.event;
|
||||
|
||||
constructor(
|
||||
readonly id: string,
|
||||
@IBrowserViewMainService private readonly browserViewMainService: IBrowserViewMainService,
|
||||
@IBrowserViewCDPProxyServer private readonly cdpProxyServer: IBrowserViewCDPProxyServer,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
// #region View management
|
||||
|
||||
/**
|
||||
* Add a {@link BrowserView} to this group.
|
||||
* Fires {@link onDidAddView} and {@link onTargetCreated}.
|
||||
* Automatically removes the view when it closes.
|
||||
*/
|
||||
async addView(viewId: string): Promise<void> {
|
||||
if (this.views.has(viewId)) {
|
||||
return;
|
||||
}
|
||||
const view = this.browserViewMainService.tryGetBrowserView(viewId);
|
||||
if (!view) {
|
||||
throw new Error(`Browser view ${viewId} not found`);
|
||||
}
|
||||
this.views.set(view.id, view);
|
||||
this.knownContextIds.add(view.session.id);
|
||||
this._onDidAddView.fire({ viewId: view.id });
|
||||
this._onTargetCreated.fire(view);
|
||||
|
||||
this.viewListeners.add(Event.once(view.onDidClose)(() => {
|
||||
this.removeView(viewId);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a {@link BrowserView} from this group.
|
||||
* Fires {@link onDidRemoveView} and {@link onTargetDestroyed} if the view was tracked.
|
||||
*/
|
||||
async removeView(viewId: string): Promise<void> {
|
||||
const view = this.views.get(viewId);
|
||||
if (view && this.views.delete(viewId)) {
|
||||
this._onDidRemoveView.fire({ viewId: view.id });
|
||||
this._onTargetDestroyed.fire(view);
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region ICDPBrowserTarget implementation
|
||||
|
||||
getVersion(): CDPBrowserVersion {
|
||||
return this.browserViewMainService.getVersion();
|
||||
}
|
||||
|
||||
getWindowForTarget(target: ICDPTarget): { windowId: number; bounds: CDPWindowBounds } {
|
||||
return this.browserViewMainService.getWindowForTarget(target);
|
||||
}
|
||||
|
||||
async attach(): Promise<ICDPConnection> {
|
||||
return new CDPBrowserProxy(this);
|
||||
}
|
||||
|
||||
async getTargetInfo(): Promise<CDPTargetInfo> {
|
||||
return {
|
||||
targetId: this.id,
|
||||
type: 'browser',
|
||||
title: this.getVersion().product,
|
||||
url: '',
|
||||
attached: true,
|
||||
canAccessOpener: false
|
||||
};
|
||||
}
|
||||
|
||||
getTargets(): IterableIterator<BrowserView> {
|
||||
return this.views.values();
|
||||
}
|
||||
|
||||
async createTarget(url: string, browserContextId?: string): Promise<ICDPTarget> {
|
||||
if (browserContextId && !this.knownContextIds.has(browserContextId)) {
|
||||
throw new Error(`Unknown browser context ${browserContextId}`);
|
||||
}
|
||||
|
||||
const target = await this.browserViewMainService.createTarget(url, browserContextId);
|
||||
if (target instanceof BrowserView) {
|
||||
await this.addView(target.id);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
async activateTarget(target: ICDPTarget): Promise<void> {
|
||||
return this.browserViewMainService.activateTarget(target);
|
||||
}
|
||||
|
||||
async closeTarget(target: ICDPTarget): Promise<boolean> {
|
||||
if (target instanceof BrowserView) {
|
||||
await this.removeView(target.id);
|
||||
}
|
||||
return this.browserViewMainService.closeTarget(target);
|
||||
}
|
||||
|
||||
// Browser context management
|
||||
|
||||
/**
|
||||
* Returns only the browser context IDs that are visible to this group,
|
||||
* i.e. contexts used by views currently in the group.
|
||||
*/
|
||||
getBrowserContexts(): string[] {
|
||||
return [...this.knownContextIds];
|
||||
}
|
||||
|
||||
async createBrowserContext(): Promise<string> {
|
||||
const contextId = await this.browserViewMainService.createBrowserContext();
|
||||
this.knownContextIds.add(contextId);
|
||||
this.ownedContextIds.add(contextId);
|
||||
return contextId;
|
||||
}
|
||||
|
||||
async disposeBrowserContext(browserContextId: string): Promise<void> {
|
||||
if (!this.ownedContextIds.has(browserContextId)) {
|
||||
throw new Error('Can only dispose browser contexts created by this group');
|
||||
}
|
||||
|
||||
// Close views in this group that belong to the context before disposing
|
||||
for (const view of this.views.values()) {
|
||||
if (view.session.id === browserContextId) {
|
||||
await this.removeView(view.id);
|
||||
}
|
||||
}
|
||||
|
||||
this.knownContextIds.delete(browserContextId);
|
||||
this.ownedContextIds.delete(browserContextId);
|
||||
return this.browserViewMainService.disposeBrowserContext(browserContextId);
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region CDP endpoint
|
||||
|
||||
/**
|
||||
* Get a WebSocket endpoint URL for connecting to this group's CDP
|
||||
* session. The URL contains a short-lived, single-use token.
|
||||
*/
|
||||
async getDebugWebSocketEndpoint(): Promise<string> {
|
||||
return this.cdpProxyServer.getWebSocketEndpointForTarget(this);
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
override dispose(): void {
|
||||
this._onDidDestroy.fire();
|
||||
this.cdpProxyServer.removeTarget(this);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js';
|
||||
import { Event } from '../../../base/common/event.js';
|
||||
import { createDecorator, IInstantiationService } from '../../instantiation/common/instantiation.js';
|
||||
import { generateUuid } from '../../../base/common/uuid.js';
|
||||
import { IBrowserViewGroupService, IBrowserViewGroupViewEvent } from '../common/browserViewGroup.js';
|
||||
import { BrowserViewGroup } from './browserViewGroup.js';
|
||||
|
||||
export const IBrowserViewGroupMainService = createDecorator<IBrowserViewGroupMainService>('browserViewGroupMainService');
|
||||
|
||||
export interface IBrowserViewGroupMainService extends IBrowserViewGroupService {
|
||||
readonly _serviceBrand: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main-process service that manages {@link BrowserViewGroup} instances.
|
||||
*
|
||||
* Implements {@link IBrowserViewGroupService} so it can be surfaced to
|
||||
* the workbench/shared process via {@link ProxyChannel}.
|
||||
*/
|
||||
export class BrowserViewGroupMainService extends Disposable implements IBrowserViewGroupMainService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly groups = this._register(new DisposableMap<string, BrowserViewGroup>());
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async createGroup(): Promise<string> {
|
||||
const id = generateUuid();
|
||||
const group = this.instantiationService.createInstance(BrowserViewGroup, id);
|
||||
this.groups.set(id, group);
|
||||
|
||||
// Auto-cleanup when the group disposes itself
|
||||
Event.once(group.onDidDestroy)(() => {
|
||||
this.groups.deleteAndLeak(id);
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
async destroyGroup(groupId: string): Promise<void> {
|
||||
this.groups.deleteAndDispose(groupId);
|
||||
}
|
||||
|
||||
async addViewToGroup(groupId: string, viewId: string): Promise<void> {
|
||||
return this._getGroup(groupId).addView(viewId);
|
||||
}
|
||||
|
||||
async removeViewFromGroup(groupId: string, viewId: string): Promise<void> {
|
||||
return this._getGroup(groupId).removeView(viewId);
|
||||
}
|
||||
|
||||
async getDebugWebSocketEndpoint(groupId: string): Promise<string> {
|
||||
return this._getGroup(groupId).getDebugWebSocketEndpoint();
|
||||
}
|
||||
|
||||
onDynamicDidAddView(groupId: string): Event<IBrowserViewGroupViewEvent> {
|
||||
return this._getGroup(groupId).onDidAddView;
|
||||
}
|
||||
|
||||
onDynamicDidRemoveView(groupId: string): Event<IBrowserViewGroupViewEvent> {
|
||||
return this._getGroup(groupId).onDidRemoveView;
|
||||
}
|
||||
|
||||
onDynamicDidDestroy(groupId: string): Event<void> {
|
||||
return this._getGroup(groupId).onDidDestroy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a group or throw if not found.
|
||||
*/
|
||||
private _getGroup(groupId: string): BrowserViewGroup {
|
||||
const group = this.groups.get(groupId);
|
||||
if (!group) {
|
||||
throw new Error(`Browser view group ${groupId} not found`);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import { generateUuid } from '../../../base/common/uuid.js';
|
||||
import { BrowserViewUri } from '../common/browserViewUri.js';
|
||||
import { IWindowsMainService } from '../../windows/electron-main/windows.js';
|
||||
import { BrowserSession } from './browserSession.js';
|
||||
import { IBrowserViewCDPProxyServer } from './browserViewCDPProxyServer.js';
|
||||
import { IProductService } from '../../product/common/productService.js';
|
||||
import { CDPBrowserProxy } from '../common/cdp/proxy.js';
|
||||
|
||||
@@ -51,8 +50,7 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
|
||||
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IBrowserViewCDPProxyServer private readonly cdpProxyServer: IBrowserViewCDPProxyServer,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -363,8 +361,4 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
|
||||
);
|
||||
await browserSession.electronSession.clearData();
|
||||
}
|
||||
|
||||
async getDebugWebSocketEndpoint(): Promise<string> {
|
||||
return this.cdpProxyServer.getWebSocketEndpoint();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from '../../../base/common/event.js';
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
import { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js';
|
||||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
import { IMainProcessService } from '../../ipc/common/mainProcessService.js';
|
||||
import { IBrowserViewGroup, IBrowserViewGroupService, IBrowserViewGroupViewEvent, ipcBrowserViewGroupChannelName } from '../common/browserViewGroup.js';
|
||||
|
||||
export const IBrowserViewGroupRemoteService = createDecorator<IBrowserViewGroupRemoteService>('browserViewGroupRemoteService');
|
||||
|
||||
/**
|
||||
* Remote-process service for managing browser view groups.
|
||||
*
|
||||
* Connects to the main-process {@link BrowserViewGroupMainService} via
|
||||
* IPC and provides {@link IBrowserViewGroup} instances for
|
||||
* interacting with groups.
|
||||
*
|
||||
* Usable from the shared process.
|
||||
*/
|
||||
export interface IBrowserViewGroupRemoteService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Create a new browser view group.
|
||||
*/
|
||||
createGroup(): Promise<IBrowserViewGroup>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote proxy for a browser view group living in the main process.
|
||||
*/
|
||||
class RemoteBrowserViewGroup extends Disposable implements IBrowserViewGroup {
|
||||
constructor(
|
||||
readonly id: string,
|
||||
private readonly groupService: IBrowserViewGroupService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(groupService.onDynamicDidDestroy(this.id)(() => {
|
||||
// Avoid loops
|
||||
this.dispose(true);
|
||||
}));
|
||||
}
|
||||
|
||||
get onDidAddView(): Event<IBrowserViewGroupViewEvent> {
|
||||
return this.groupService.onDynamicDidAddView(this.id);
|
||||
}
|
||||
|
||||
get onDidRemoveView(): Event<IBrowserViewGroupViewEvent> {
|
||||
return this.groupService.onDynamicDidRemoveView(this.id);
|
||||
}
|
||||
|
||||
get onDidDestroy(): Event<void> {
|
||||
return this.groupService.onDynamicDidDestroy(this.id);
|
||||
}
|
||||
|
||||
async addView(viewId: string): Promise<void> {
|
||||
return this.groupService.addViewToGroup(this.id, viewId);
|
||||
}
|
||||
|
||||
async removeView(viewId: string): Promise<void> {
|
||||
return this.groupService.removeViewFromGroup(this.id, viewId);
|
||||
}
|
||||
|
||||
async getDebugWebSocketEndpoint(): Promise<string> {
|
||||
return this.groupService.getDebugWebSocketEndpoint(this.id);
|
||||
}
|
||||
|
||||
override dispose(fromService = false): void {
|
||||
if (!fromService) {
|
||||
this.groupService.destroyGroup(this.id);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class BrowserViewGroupRemoteService implements IBrowserViewGroupRemoteService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _groupService: IBrowserViewGroupService;
|
||||
private readonly _groups = new Map<string, IBrowserViewGroup>();
|
||||
|
||||
constructor(
|
||||
@IMainProcessService mainProcessService: IMainProcessService,
|
||||
) {
|
||||
const channel = mainProcessService.getChannel(ipcBrowserViewGroupChannelName);
|
||||
this._groupService = ProxyChannel.toService<IBrowserViewGroupService>(channel);
|
||||
}
|
||||
|
||||
async createGroup(): Promise<IBrowserViewGroup> {
|
||||
const id = await this._groupService.createGroup();
|
||||
return this._wrap(id);
|
||||
}
|
||||
|
||||
private _wrap(id: string): IBrowserViewGroup {
|
||||
const group = new RemoteBrowserViewGroup(id, this._groupService);
|
||||
this._groups.set(id, group);
|
||||
|
||||
Event.once(group.onDidDestroy)(() => {
|
||||
this._groups.delete(id);
|
||||
});
|
||||
|
||||
return group;
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
import { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js';
|
||||
import { DeferredPromise } from '../../../base/common/async.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
import { IBrowserViewService, ipcBrowserViewChannelName } from '../common/browserView.js';
|
||||
import { IPlaywrightService } from '../common/playwrightService.js';
|
||||
import { IMainProcessService } from '../../ipc/common/mainProcessService.js';
|
||||
import { IBrowserViewGroupRemoteService } from '../node/browserViewGroupRemoteService.js';
|
||||
import { IBrowserViewGroup } from '../common/browserViewGroup.js';
|
||||
|
||||
// eslint-disable-next-line local/code-import-patterns
|
||||
import type { Browser } from 'playwright-core';
|
||||
import type { Browser, BrowserContext, Page } from 'playwright-core';
|
||||
|
||||
/**
|
||||
* Shared-process implementation of {@link IPlaywrightService}.
|
||||
@@ -19,22 +19,22 @@ import type { Browser } from 'playwright-core';
|
||||
export class PlaywrightService extends Disposable implements IPlaywrightService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly browserViewService: IBrowserViewService;
|
||||
private _browser: Browser | undefined;
|
||||
private _pages: PlaywrightPageManager | undefined;
|
||||
private _initPromise: Promise<void> | undefined;
|
||||
|
||||
constructor(
|
||||
@IMainProcessService mainProcessService: IMainProcessService,
|
||||
@IBrowserViewGroupRemoteService private readonly browserViewGroupRemoteService: IBrowserViewGroupRemoteService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
|
||||
const channel = mainProcessService.getChannel(ipcBrowserViewChannelName);
|
||||
this.browserViewService = ProxyChannel.toService<IBrowserViewService>(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the Playwright browser connection and page map are initialized.
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
if (this._browser?.isConnected()) {
|
||||
if (this._pages) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,30 +44,41 @@ export class PlaywrightService extends Disposable implements IPlaywrightService
|
||||
|
||||
this._initPromise = (async () => {
|
||||
try {
|
||||
this.logService.debug('[PlaywrightService] Connecting to browser via CDP');
|
||||
this.logService.debug('[PlaywrightService] Creating browser view group');
|
||||
const group = this._register(await this.browserViewGroupRemoteService.createGroup());
|
||||
|
||||
this.logService.debug('[PlaywrightService] Connecting to browser via CDP');
|
||||
const playwright = await import('playwright-core');
|
||||
const endpoint = await this.browserViewService.getDebugWebSocketEndpoint();
|
||||
const endpoint = await group.getDebugWebSocketEndpoint();
|
||||
const browser = await playwright.chromium.connectOverCDP(endpoint);
|
||||
|
||||
this.logService.debug('[PlaywrightService] Connected to browser');
|
||||
|
||||
browser.on('disconnected', () => {
|
||||
this.logService.debug('[PlaywrightService] Browser disconnected');
|
||||
if (this._browser === browser) {
|
||||
this._browser = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// This can happen if the service was disposed while we were waiting for the connection. In that case, clean up immediately.
|
||||
if (this._initPromise === undefined) {
|
||||
browser.close().catch(() => { /* ignore */ });
|
||||
throw new Error('PlaywrightService was disposed during initialization');
|
||||
}
|
||||
|
||||
const pageManager = this._register(new PlaywrightPageManager(group, browser, this.logService));
|
||||
|
||||
browser.on('disconnected', () => {
|
||||
this.logService.debug('[PlaywrightService] Browser disconnected');
|
||||
if (this._browser === browser) {
|
||||
group.dispose();
|
||||
pageManager.dispose();
|
||||
|
||||
this._browser = undefined;
|
||||
this._pages = undefined;
|
||||
this._initPromise = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
this._browser = browser;
|
||||
} finally {
|
||||
this._pages = pageManager;
|
||||
} catch (e) {
|
||||
this._initPromise = undefined;
|
||||
throw e;
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -83,3 +94,272 @@ export class PlaywrightService extends Disposable implements IPlaywrightService
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Correlates browser view IDs with Playwright {@link Page} instances.
|
||||
*
|
||||
* When a browser view is added to a group, two asynchronous events follow
|
||||
* through independent channels:
|
||||
*
|
||||
* 1. The group fires {@link IBrowserViewGroup.onDidAddView} (via IPC).
|
||||
* 2. Playwright receives a CDP `Target.targetCreated` event (via WebSocket)
|
||||
* and fires a `page` event on the matching {@link BrowserContext}.
|
||||
*
|
||||
* This class pairs the two event streams by FIFO ordering: the first view-ID
|
||||
* received is matched with the first page event received.
|
||||
*
|
||||
* A periodic scan handles the case where Playwright creates a new
|
||||
* {@link BrowserContext} for a target whose session was previously unknown.
|
||||
*/
|
||||
class PlaywrightPageManager extends Disposable {
|
||||
|
||||
private readonly _viewIdToPage = new Map<string, Page>();
|
||||
private readonly _pageToViewId = new WeakMap<Page, string>();
|
||||
|
||||
/** View IDs received from the group but not yet matched with a page. */
|
||||
private _viewIdQueue: Array<{
|
||||
viewId: string;
|
||||
page: DeferredPromise<Page>;
|
||||
}> = [];
|
||||
|
||||
/** Pages received from Playwright but not yet matched with a view ID. */
|
||||
private _pageQueue: Array<{
|
||||
page: Page;
|
||||
viewId: DeferredPromise<string>;
|
||||
}> = [];
|
||||
|
||||
private readonly _watchedContexts = new WeakSet<BrowserContext>();
|
||||
private _scanTimer: ReturnType<typeof setInterval> | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly _group: IBrowserViewGroup,
|
||||
private readonly _browser: Browser,
|
||||
private readonly logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(_group.onDidAddView(e => this.onViewAdded(e.viewId)));
|
||||
this._register(_group.onDidRemoveView(e => this.onViewRemoved(e.viewId)));
|
||||
this.scanForNewContexts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new page in the browser and return its associated page and view ID.
|
||||
*/
|
||||
async newPage(): Promise<{ viewId: string; page: Page }> {
|
||||
const page = await this._browser.newPage();
|
||||
const viewId = await this.onPageAdded(page);
|
||||
|
||||
return { viewId, page };
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly add an existing browser view to the CDP group.
|
||||
*/
|
||||
async addPage(viewId: string): Promise<void> {
|
||||
if (this._viewIdToPage.has(viewId)) {
|
||||
return;
|
||||
}
|
||||
if (this._viewIdQueue.some(item => item.viewId === viewId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure the viewId is queued so we can immediately fetch the promise via getPage().
|
||||
this.onViewAdded(viewId);
|
||||
|
||||
try {
|
||||
await this._group.addView(viewId);
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
this.logService.error('[PlaywrightPageMap] Failed to add view:', errorMessage);
|
||||
this.onViewRemoved(viewId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a browser view from the CDP group.
|
||||
*/
|
||||
async removePage(viewId: string): Promise<void> {
|
||||
this.onViewRemoved(viewId);
|
||||
try {
|
||||
await this._group.removeView(viewId);
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
this.logService.error('[PlaywrightPageMap] Failed to remove view:', errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Playwright {@link Page} for a browser view that has already been added.
|
||||
* Throws if the view has not been added.
|
||||
*/
|
||||
async getPage(viewId: string): Promise<Page> {
|
||||
const resolved = this._viewIdToPage.get(viewId);
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
const queued = this._viewIdQueue.find(item => item.viewId === viewId);
|
||||
if (queued) {
|
||||
return queued.page.p;
|
||||
}
|
||||
|
||||
throw new Error(`Page "${viewId}" has not been added to the Playwright service`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the group fires onDidAddView. Creates a deferred entry in
|
||||
* the view ID queue and attempts to match it with a page.
|
||||
*/
|
||||
private onViewAdded(viewId: string, timeoutMs = 10000): Promise<Page> {
|
||||
const resolved = this._viewIdToPage.get(viewId);
|
||||
if (resolved) {
|
||||
return Promise.resolve(resolved);
|
||||
}
|
||||
const queued = this._viewIdQueue.find(item => item.viewId === viewId);
|
||||
if (queued) {
|
||||
return queued.page.p;
|
||||
}
|
||||
|
||||
const deferred = new DeferredPromise<Page>();
|
||||
const timeout = setTimeout(() => deferred.error(new Error(`Timed out waiting for page`)), timeoutMs);
|
||||
|
||||
deferred.p.finally(() => {
|
||||
clearTimeout(timeout);
|
||||
this._viewIdQueue = this._viewIdQueue.filter(item => item.viewId !== viewId);
|
||||
if (this._viewIdQueue.length === 0) {
|
||||
this.stopScanning();
|
||||
}
|
||||
});
|
||||
|
||||
this._viewIdQueue.push({ viewId, page: deferred });
|
||||
this.tryMatch();
|
||||
this.ensureScanning();
|
||||
|
||||
return deferred.p;
|
||||
}
|
||||
|
||||
private onViewRemoved(viewId: string): void {
|
||||
this._viewIdQueue = this._viewIdQueue.filter(item => item.viewId !== viewId);
|
||||
const page = this._viewIdToPage.get(viewId);
|
||||
if (page) {
|
||||
this._pageToViewId.delete(page);
|
||||
}
|
||||
this._viewIdToPage.delete(viewId);
|
||||
}
|
||||
|
||||
private onPageAdded(page: Page, timeoutMs = 10000): Promise<string> {
|
||||
const resolved = this._pageToViewId.get(page);
|
||||
if (resolved) {
|
||||
return Promise.resolve(resolved);
|
||||
}
|
||||
const queued = this._pageQueue.find(item => item.page === page);
|
||||
if (queued) {
|
||||
return queued.viewId.p;
|
||||
}
|
||||
|
||||
this.onContextAdded(page.context());
|
||||
page.once('close', () => this.onPageRemoved(page));
|
||||
|
||||
const deferred = new DeferredPromise<string>();
|
||||
const timeout = setTimeout(() => deferred.error(new Error(`Timed out waiting for browser view`)), timeoutMs);
|
||||
deferred.p.finally(() => {
|
||||
clearTimeout(timeout);
|
||||
this._pageQueue = this._pageQueue.filter(item => item.page !== page);
|
||||
});
|
||||
|
||||
this._pageQueue.push({ page, viewId: deferred });
|
||||
this.tryMatch();
|
||||
|
||||
return deferred.p;
|
||||
}
|
||||
|
||||
private onPageRemoved(page: Page): void {
|
||||
this._pageQueue = this._pageQueue.filter(item => item.page !== page);
|
||||
const viewId = this._pageToViewId.get(page);
|
||||
if (viewId) {
|
||||
this._viewIdToPage.delete(viewId);
|
||||
}
|
||||
this._pageToViewId.delete(page);
|
||||
}
|
||||
|
||||
private onContextAdded(context: BrowserContext): void {
|
||||
if (this._watchedContexts.has(context)) {
|
||||
return;
|
||||
}
|
||||
this._watchedContexts.add(context);
|
||||
|
||||
context.on('page', (page: Page) => this.onPageAdded(page));
|
||||
context.on('close', () => this.onContextRemoved(context));
|
||||
|
||||
for (const page of context.pages()) {
|
||||
this.onPageAdded(page);
|
||||
}
|
||||
}
|
||||
|
||||
private onContextRemoved(context: BrowserContext): void {
|
||||
this._watchedContexts.delete(context);
|
||||
}
|
||||
|
||||
// --- Matching ---
|
||||
|
||||
/**
|
||||
* Pair up queued view IDs with queued pages in FIFO order and resolve
|
||||
* any callers waiting for the matched view IDs.
|
||||
*/
|
||||
private tryMatch(): void {
|
||||
while (this._viewIdQueue.length > 0 && this._pageQueue.length > 0) {
|
||||
const viewIdItem = this._viewIdQueue.shift()!;
|
||||
const pageItem = this._pageQueue.shift()!;
|
||||
|
||||
this._viewIdToPage.set(viewIdItem.viewId, pageItem.page);
|
||||
this._pageToViewId.set(pageItem.page, viewIdItem.viewId);
|
||||
|
||||
viewIdItem.page.complete(pageItem.page);
|
||||
pageItem.viewId.complete(viewIdItem.viewId);
|
||||
|
||||
this.logService.debug(`[PlaywrightPageMap] Matched view ${viewIdItem.viewId} → page`);
|
||||
}
|
||||
|
||||
if (this._viewIdQueue.length === 0) {
|
||||
this.stopScanning();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Context scanning ---
|
||||
|
||||
/**
|
||||
* Watch all current {@link BrowserContext BrowserContexts} for new pages.
|
||||
* Also processes any existing pages in newly discovered contexts.
|
||||
*/
|
||||
private scanForNewContexts(): void {
|
||||
for (const context of this._browser.contexts()) {
|
||||
this.onContextAdded(context);
|
||||
}
|
||||
}
|
||||
|
||||
private ensureScanning(): void {
|
||||
if (this._scanTimer === undefined) {
|
||||
this._scanTimer = setInterval(() => this.scanForNewContexts(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
private stopScanning(): void {
|
||||
if (this._scanTimer !== undefined) {
|
||||
clearInterval(this._scanTimer);
|
||||
this._scanTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.stopScanning();
|
||||
for (const { page } of this._viewIdQueue) {
|
||||
page.error(new Error('PlaywrightPageMap disposed'));
|
||||
}
|
||||
for (const { viewId } of this._pageQueue) {
|
||||
viewId.error(new Error('PlaywrightPageMap disposed'));
|
||||
}
|
||||
this._viewIdQueue = [];
|
||||
this._pageQueue = [];
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +339,7 @@ export interface IPtyService {
|
||||
shutdown(id: number, immediate: boolean): Promise<void>;
|
||||
input(id: number, data: string): Promise<void>;
|
||||
sendSignal(id: number, signal: string): Promise<void>;
|
||||
resize(id: number, cols: number, rows: number): Promise<void>;
|
||||
resize(id: number, cols: number, rows: number, pixelWidth?: number, pixelHeight?: number): Promise<void>;
|
||||
clearBuffer(id: number): Promise<void>;
|
||||
getInitialCwd(id: number): Promise<string>;
|
||||
getCwd(id: number): Promise<string>;
|
||||
@@ -391,7 +391,7 @@ export interface IPtyServiceContribution {
|
||||
handleProcessReady(persistentProcessId: number, process: ITerminalChildProcess): void;
|
||||
handleProcessDispose(persistentProcessId: number): void;
|
||||
handleProcessInput(persistentProcessId: number, data: string): void;
|
||||
handleProcessResize(persistentProcessId: number, cols: number, rows: number): void;
|
||||
handleProcessResize(persistentProcessId: number, cols: number, rows: number, pixelWidth?: number, pixelHeight?: number): void;
|
||||
}
|
||||
|
||||
export interface IPtyHostController {
|
||||
@@ -810,7 +810,7 @@ export interface ITerminalChildProcess {
|
||||
input(data: string): void;
|
||||
sendSignal(signal: string): void;
|
||||
processBinary(data: string): Promise<void>;
|
||||
resize(cols: number, rows: number): void;
|
||||
resize(cols: number, rows: number, pixelWidth?: number, pixelHeight?: number): void;
|
||||
clearBuffer(): void | Promise<void>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -254,8 +254,8 @@ export class PtyHostService extends Disposable implements IPtyHostService {
|
||||
processBinary(id: number, data: string): Promise<void> {
|
||||
return this._proxy.processBinary(id, data);
|
||||
}
|
||||
resize(id: number, cols: number, rows: number): Promise<void> {
|
||||
return this._proxy.resize(id, cols, rows);
|
||||
resize(id: number, cols: number, rows: number, pixelWidth?: number, pixelHeight?: number): Promise<void> {
|
||||
return this._proxy.resize(id, cols, rows, pixelWidth, pixelHeight);
|
||||
}
|
||||
clearBuffer(id: number): Promise<void> {
|
||||
return this._proxy.clearBuffer(id);
|
||||
|
||||
@@ -455,13 +455,13 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
return this._throwIfNoPty(id).writeBinary(data);
|
||||
}
|
||||
@traceRpc
|
||||
async resize(id: number, cols: number, rows: number): Promise<void> {
|
||||
async resize(id: number, cols: number, rows: number, pixelWidth?: number, pixelHeight?: number): Promise<void> {
|
||||
const pty = this._throwIfNoPty(id);
|
||||
if (pty) {
|
||||
for (const contrib of this._contributions) {
|
||||
contrib.handleProcessResize(id, cols, rows);
|
||||
contrib.handleProcessResize(id, cols, rows, pixelWidth, pixelHeight);
|
||||
}
|
||||
pty.resize(cols, rows);
|
||||
pty.resize(cols, rows, pixelWidth, pixelHeight);
|
||||
}
|
||||
}
|
||||
@traceRpc
|
||||
@@ -902,7 +902,7 @@ class PersistentTerminalProcess extends Disposable {
|
||||
writeBinary(data: string): Promise<void> {
|
||||
return this._terminalProcess.processBinary(data);
|
||||
}
|
||||
resize(cols: number, rows: number): void {
|
||||
resize(cols: number, rows: number, pixelWidth?: number, pixelHeight?: number): void {
|
||||
if (this._inReplay) {
|
||||
return;
|
||||
}
|
||||
@@ -911,7 +911,7 @@ class PersistentTerminalProcess extends Disposable {
|
||||
// Buffered events should flush when a resize occurs
|
||||
this._bufferer.flushBuffer(this._persistentProcessId);
|
||||
|
||||
return this._terminalProcess.resize(cols, rows);
|
||||
return this._terminalProcess.resize(cols, rows, pixelWidth, pixelHeight);
|
||||
}
|
||||
async clearBuffer(): Promise<void> {
|
||||
this._serializer.clearBuffer();
|
||||
|
||||
@@ -515,7 +515,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
}
|
||||
}
|
||||
|
||||
resize(cols: number, rows: number): void {
|
||||
resize(cols: number, rows: number, pixelWidth?: number, pixelHeight?: number): void {
|
||||
if (this._store.isDisposed) {
|
||||
return;
|
||||
}
|
||||
@@ -537,7 +537,10 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
|
||||
this._logService.trace('node-pty.IPty#resize', cols, rows);
|
||||
try {
|
||||
this._ptyProcess.resize(cols, rows);
|
||||
const pixelSize = pixelWidth !== undefined && pixelHeight !== undefined
|
||||
? { width: pixelWidth, height: pixelHeight }
|
||||
: undefined;
|
||||
this._ptyProcess.resize(cols, rows, pixelSize);
|
||||
} catch (e) {
|
||||
// Swallow error if the pty has already exited
|
||||
this._logService.trace('node-pty.IPty#resize exception ' + e.message);
|
||||
|
||||
@@ -704,6 +704,7 @@ interface IPartVisibilityState {
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-02-17 | Added `-webkit-app-region: drag` to sidebar title area so it can be used to drag the window; interactive children (actions, composite bar, labels) marked `no-drag`; CSS rules scoped to `.agent-sessions-workbench` in `parts/media/sidebarPart.css` |
|
||||
| 2026-02-13 | Documentation sync: Updated all file names, class names, and references to match current implementation. `AgenticWorkbench` → `Workbench`, `AgenticSidebarPart` → `SidebarPart`, `AgenticAuxiliaryBarPart` → `AuxiliaryBarPart`, `AgenticPanelPart` → `PanelPart`, `agenticWorkbench.ts` → `workbench.ts`, `agenticWorkbenchMenus.ts` → `menus.ts`, `agenticLayoutActions.ts` → `layoutActions.ts`, `AgenticTitleBarWidget` → `SessionsTitleBarWidget`, `AgenticTitleBarContribution` → `SessionsTitleBarContribution`. Removed references to deleted files (`sidebarRevealButton.ts`, `floatingToolbar.ts`, `agentic.contributions.ts`, `agenticTitleBarWidget.ts`). Updated pane composite architecture from `SyncDescriptor`-based to `AgenticPaneCompositePartService`. Moved account widget docs from titlebar to sidebar footer. Added documentation for sidebar footer, project bar, traffic light spacer, card appearance styling, widget directory, and new contrib structure (`accountMenu/`, `chat/`, `configuration/`, `sessions/`). Updated titlebar actions to reflect Run Script split button and Open submenu. Removed Toggle Maximize panel action (no longer registered). Updated contributions section with all current contributions and their locations. |
|
||||
| 2026-02-13 | Changed grid structure: sidebar now spans full window height at root level (HORIZONTAL root orientation); Titlebar moved inside right section; Grid is now `Sidebar \| [Titlebar / TopRight / Panel]` instead of `Titlebar / [Sidebar \| RightSection]`; Panel maximize now excludes both titlebar and sidebar; Floating toolbar positioning no longer depends on titlebar height |
|
||||
| 2026-02-11 | Simplified titlebar: replaced `BrowserTitlebarPart`-derived implementation with standalone `TitlebarPart` using three `MenuWorkbenchToolBar` sections (left/center/right); Removed `CommandCenterControl`, `WindowTitle`, layout toolbar, and manual toolbar management; Center section uses `Menus.CommandCenter` which renders session picker via `IActionViewItemService`; Right section uses `Menus.TitleBarRight` which includes account submenu; Removed `commandCenterControl.ts` file |
|
||||
|
||||
@@ -23,4 +23,5 @@ export const Menus = {
|
||||
AuxiliaryBarTitle: new MenuId('SessionsAuxiliaryBarTitle'),
|
||||
AuxiliaryBarTitleLeft: new MenuId('SessionsAuxiliaryBarTitleLeft'),
|
||||
SidebarFooter: new MenuId('SessionsSidebarFooter'),
|
||||
SidebarCustomizations: new MenuId('SessionsSidebarCustomizations'),
|
||||
} as const;
|
||||
|
||||
@@ -105,7 +105,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart {
|
||||
{
|
||||
hasTitle: true,
|
||||
trailingSeparator: false,
|
||||
borderWidth: () => 0,
|
||||
borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0,
|
||||
},
|
||||
AuxiliaryBarPart.activeViewSettingsKey,
|
||||
ActiveAuxiliaryContext.bindTo(contextKeyService),
|
||||
|
||||
@@ -8,6 +8,26 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Make the sidebar title area draggable to move the window */
|
||||
.agent-sessions-workbench .part.sidebar > .composite.title {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.agent-sessions-workbench .part.sidebar > .composite.title > .titlebar-drag-region {
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
/* Interactive elements in the title area must not be draggable */
|
||||
.agent-sessions-workbench .part.sidebar > .composite.title .action-item {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
/* Sidebar Footer Container */
|
||||
.monaco-workbench .part.sidebar > .sidebar-footer {
|
||||
display: flex;
|
||||
|
||||
@@ -99,7 +99,7 @@ export class SidebarPart extends AbstractPaneCompositePart {
|
||||
) {
|
||||
super(
|
||||
Parts.SIDEBAR_PART,
|
||||
{ hasTitle: true, trailingSeparator: false, borderWidth: () => 0 },
|
||||
{ hasTitle: true, trailingSeparator: false, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 },
|
||||
SidebarPart.activeViewletSettingsKey,
|
||||
ActiveViewletContext.bindTo(contextKeyService),
|
||||
SidebarFocusContext.bindTo(contextKeyService),
|
||||
@@ -134,6 +134,12 @@ export class SidebarPart extends AbstractPaneCompositePart {
|
||||
protected override createTitleArea(parent: HTMLElement): HTMLElement | undefined {
|
||||
const titleArea = super.createTitleArea(parent);
|
||||
|
||||
if (titleArea) {
|
||||
// Add a drag region so the sidebar title area can be used to move the window,
|
||||
// matching the titlebar's drag behavior.
|
||||
prepend(titleArea, $('div.titlebar-drag-region'));
|
||||
}
|
||||
|
||||
// macOS native: the sidebar spans full height and the traffic lights
|
||||
// overlay the top-left corner. Add a fixed-width spacer inside the
|
||||
// title area to push content horizontally past the traffic lights.
|
||||
|
||||
@@ -203,7 +203,7 @@ export class TitlebarPart extends Part implements ITitlebarPart {
|
||||
}));
|
||||
|
||||
// Right toolbar (driven by Menus.TitleBarRight - includes account submenu)
|
||||
const rightToolbarContainer = append(this.rightContent, $('div.action-toolbar-container'));
|
||||
const rightToolbarContainer = prepend(this.rightContent, $('div.action-toolbar-container'));
|
||||
this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, rightToolbarContainer, Menus.TitleBarRight, {
|
||||
contextMenu: Menus.TitleBarContext,
|
||||
telemetrySource: 'titlePart.right',
|
||||
@@ -258,8 +258,15 @@ export class TitlebarPart extends Part implements ITitlebarPart {
|
||||
|
||||
private lastLayoutDimension: Dimension | undefined;
|
||||
|
||||
get hasZoomableElements(): boolean {
|
||||
return true; // sessions titlebar always has command center and toolbar actions
|
||||
}
|
||||
|
||||
get preventZoom(): boolean {
|
||||
return getZoomFactor(getWindow(this.element)) < 1;
|
||||
// Prevent zooming behavior if any of the following conditions are met:
|
||||
// 1. Shrinking below the window control size (zoom < 1)
|
||||
// 2. No custom items are present in the title bar
|
||||
return getZoomFactor(getWindow(this.element)) < 1 || !this.hasZoomableElements;
|
||||
}
|
||||
|
||||
override layout(width: number, height: number): void {
|
||||
@@ -342,6 +349,7 @@ export class AuxiliaryTitlebarPart extends TitlebarPart implements IAuxiliaryTit
|
||||
constructor(
|
||||
readonly container: HTMLElement,
|
||||
editorGroupsContainer: IEditorGroupsContainer,
|
||||
private readonly mainTitlebar: TitlebarPart,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@@ -354,6 +362,15 @@ export class AuxiliaryTitlebarPart extends TitlebarPart implements IAuxiliaryTit
|
||||
const id = AuxiliaryTitlebarPart.COUNTER++;
|
||||
super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), contextMenuService, configurationService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService);
|
||||
}
|
||||
|
||||
override get preventZoom(): boolean {
|
||||
// Prevent zooming behavior if any of the following conditions are met:
|
||||
// 1. Shrinking below the window control size (zoom < 1)
|
||||
// 2. No custom items are present in the main title bar
|
||||
// The auxiliary title bar never contains any zoomable items itself,
|
||||
// but we want to match the behavior of the main title bar.
|
||||
return getZoomFactor(getWindow(this.element)) < 1 || !this.mainTitlebar.hasZoomableElements;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,17 +383,21 @@ export class TitleService extends MultiWindowParts<TitlebarPart> implements ITit
|
||||
readonly mainPart: TitlebarPart;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IInstantiationService protected readonly instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super('workbench.agentSessionsTitleService', themeService, storageService);
|
||||
|
||||
this.mainPart = this._register(this.instantiationService.createInstance(MainTitlebarPart));
|
||||
this.mainPart = this._register(this.createMainTitlebarPart());
|
||||
this.onMenubarVisibilityChange = this.mainPart.onMenubarVisibilityChange;
|
||||
this._register(this.registerPart(this.mainPart));
|
||||
}
|
||||
|
||||
protected createMainTitlebarPart(): TitlebarPart {
|
||||
return this.instantiationService.createInstance(MainTitlebarPart);
|
||||
}
|
||||
|
||||
//#region Auxiliary Titlebar Parts
|
||||
|
||||
createAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): IAuxiliaryTitlebarPart {
|
||||
@@ -386,7 +407,7 @@ export class TitleService extends MultiWindowParts<TitlebarPart> implements ITit
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
const titlebarPart = instantiationService.createInstance(AuxiliaryTitlebarPart, titlebarPartContainer, editorGroupsContainer);
|
||||
const titlebarPart = this.doCreateAuxiliaryTitlebarPart(titlebarPartContainer, editorGroupsContainer, instantiationService);
|
||||
disposables.add(this.registerPart(titlebarPart));
|
||||
|
||||
disposables.add(Event.runAndSubscribe(titlebarPart.onDidChange, () => titlebarPartContainer.style.height = `${titlebarPart.height}px`));
|
||||
@@ -397,6 +418,10 @@ export class TitleService extends MultiWindowParts<TitlebarPart> implements ITit
|
||||
return titlebarPart;
|
||||
}
|
||||
|
||||
protected doCreateAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): TitlebarPart & IAuxiliaryTitlebarPart {
|
||||
return instantiationService.createInstance(AuxiliaryTitlebarPart, container, editorGroupsContainer, this.mainPart);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Service Implementation
|
||||
|
||||
@@ -236,7 +236,7 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService {
|
||||
|
||||
private readonly partVisibility: IPartVisibilityState = {
|
||||
sidebar: true,
|
||||
auxiliaryBar: true,
|
||||
auxiliaryBar: false,
|
||||
editor: false,
|
||||
panel: false,
|
||||
chatBar: true
|
||||
@@ -1059,12 +1059,7 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService {
|
||||
}
|
||||
|
||||
this.partVisibility.sidebar = !hidden;
|
||||
|
||||
// Adjust CSS - for hiding, defer adding the class until animation
|
||||
// completes so the part stays visible during the exit animation.
|
||||
if (!hidden) {
|
||||
this.mainContainer.classList.remove(LayoutClasses.SIDEBAR_HIDDEN);
|
||||
}
|
||||
this.mainContainer.classList.toggle(LayoutClasses.SIDEBAR_HIDDEN, hidden);
|
||||
|
||||
// Propagate to grid
|
||||
this.workbenchGrid.setViewVisible(
|
||||
@@ -1087,12 +1082,7 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService {
|
||||
}
|
||||
|
||||
this.partVisibility.auxiliaryBar = !hidden;
|
||||
|
||||
// Adjust CSS - for hiding, defer adding the class until animation
|
||||
// completes so the part stays visible during the exit animation.
|
||||
if (!hidden) {
|
||||
this.mainContainer.classList.remove(LayoutClasses.AUXILIARYBAR_HIDDEN);
|
||||
}
|
||||
this.mainContainer.classList.toggle(LayoutClasses.AUXILIARYBAR_HIDDEN, hidden);
|
||||
|
||||
// Propagate to grid
|
||||
this.workbenchGrid.setViewVisible(
|
||||
@@ -1115,15 +1105,8 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService {
|
||||
}
|
||||
|
||||
this.partVisibility.editor = !hidden;
|
||||
|
||||
// Adjust CSS for main container
|
||||
if (hidden) {
|
||||
this.mainContainer.classList.add(LayoutClasses.MAIN_EDITOR_AREA_HIDDEN);
|
||||
this.mainContainer.classList.remove(LayoutClasses.EDITOR_MODAL_VISIBLE);
|
||||
} else {
|
||||
this.mainContainer.classList.remove(LayoutClasses.MAIN_EDITOR_AREA_HIDDEN);
|
||||
this.mainContainer.classList.add(LayoutClasses.EDITOR_MODAL_VISIBLE);
|
||||
}
|
||||
this.mainContainer.classList.toggle(LayoutClasses.MAIN_EDITOR_AREA_HIDDEN, hidden);
|
||||
this.mainContainer.classList.toggle(LayoutClasses.EDITOR_MODAL_VISIBLE, !hidden);
|
||||
|
||||
// Show/hide modal
|
||||
if (hidden) {
|
||||
@@ -1144,13 +1127,7 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService {
|
||||
}
|
||||
|
||||
this.partVisibility.panel = !hidden;
|
||||
|
||||
// Adjust CSS - for hiding, defer adding the class until animation
|
||||
// completes because `.nopanel .part.panel { display: none !important }`
|
||||
// would instantly hide the panel content mid-animation.
|
||||
if (!hidden) {
|
||||
this.mainContainer.classList.remove(LayoutClasses.PANEL_HIDDEN);
|
||||
}
|
||||
this.mainContainer.classList.toggle(LayoutClasses.PANEL_HIDDEN, hidden);
|
||||
|
||||
// Propagate to grid
|
||||
this.workbenchGrid.setViewVisible(
|
||||
@@ -1173,13 +1150,7 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService {
|
||||
}
|
||||
|
||||
this.partVisibility.chatBar = !hidden;
|
||||
|
||||
// Adjust CSS
|
||||
if (hidden) {
|
||||
this.mainContainer.classList.add(LayoutClasses.CHATBAR_HIDDEN);
|
||||
} else {
|
||||
this.mainContainer.classList.remove(LayoutClasses.CHATBAR_HIDDEN);
|
||||
}
|
||||
this.mainContainer.classList.toggle(LayoutClasses.CHATBAR_HIDDEN, hidden);
|
||||
|
||||
// Propagate to grid
|
||||
this.workbenchGrid.setViewVisible(this.chatBarPartView, !hidden);
|
||||
|
||||
+2
-2
@@ -39,7 +39,7 @@ import { ILabelService } from '../../../../platform/label/common/label.js';
|
||||
import { parseAllHookFiles } from '../../../../workbench/contrib/chat/browser/promptSyntax/hookUtils.js';
|
||||
import { OS } from '../../../../base/common/platform.js';
|
||||
import { IRemoteAgentService } from '../../../../workbench/services/remote/common/remoteAgentService.js';
|
||||
import { ISessionsWorkbenchService } from '../../sessions/browser/sessionsWorkbenchService.js';
|
||||
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { Action, Separator } from '../../../../base/common/actions.js';
|
||||
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
|
||||
@@ -340,7 +340,7 @@ export class AICustomizationListWidget extends Disposable {
|
||||
@IPathService private readonly pathService: IPathService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
|
||||
@ISessionsWorkbenchService private readonly activeSessionService: ISessionsWorkbenchService,
|
||||
@ISessionsManagementService private readonly activeSessionService: ISessionsManagementService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService,
|
||||
@ISCMService private readonly scmService: ISCMService,
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@
|
||||
|
||||
import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { ISessionsWorkbenchService } from '../../sessions/browser/sessionsWorkbenchService.js';
|
||||
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { MenuId } from '../../../../platform/actions/common/actions.js';
|
||||
|
||||
@@ -96,7 +96,7 @@ export const SIDEBAR_MIN_WIDTH = 150;
|
||||
export const SIDEBAR_MAX_WIDTH = 350;
|
||||
export const CONTENT_MIN_WIDTH = 400;
|
||||
|
||||
export function getActiveSessionRoot(activeSessionService: ISessionsWorkbenchService): URI | undefined {
|
||||
export function getActiveSessionRoot(activeSessionService: ISessionsManagementService): URI | undefined {
|
||||
const session = activeSessionService.getActiveSession();
|
||||
return session?.worktree ?? session?.repository;
|
||||
}
|
||||
|
||||
+2
-2
@@ -59,7 +59,7 @@ import { showConfigureHooksQuickPick } from '../../../../workbench/contrib/chat/
|
||||
import { CustomizationCreatorService } from './customizationCreatorService.js';
|
||||
import { ICommandService } from '../../../../platform/commands/common/commands.js';
|
||||
import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';
|
||||
import { ISessionsWorkbenchService } from '../../sessions/browser/sessionsWorkbenchService.js';
|
||||
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
|
||||
import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js';
|
||||
import { IWorkingCopyService } from '../../../../workbench/services/workingCopy/common/workingCopyService.js';
|
||||
|
||||
@@ -176,7 +176,7 @@ export class AICustomizationManagementEditor extends EditorPane {
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ILayoutService private readonly layoutService: ILayoutService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@ISessionsWorkbenchService private readonly activeSessionService: ISessionsWorkbenchService,
|
||||
@ISessionsManagementService private readonly activeSessionService: ISessionsManagementService,
|
||||
@IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService,
|
||||
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService,
|
||||
) {
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ import { AICustomizationManagementEditorInput } from './aiCustomizationManagemen
|
||||
import { AICustomizationManagementEditor } from './aiCustomizationManagementEditor.js';
|
||||
import { agentIcon, instructionsIcon, promptIcon, skillIcon } from '../../aiCustomizationTreeView/browser/aiCustomizationTreeViewIcons.js';
|
||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { ISessionsWorkbenchService } from '../../sessions/browser/sessionsWorkbenchService.js';
|
||||
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
|
||||
|
||||
const $ = DOM.$;
|
||||
|
||||
@@ -66,7 +66,7 @@ export class AICustomizationOverviewView extends ViewPane {
|
||||
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
|
||||
@IPromptsService private readonly promptsService: IPromptsService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@ISessionsWorkbenchService private readonly activeSessionService: ISessionsWorkbenchService,
|
||||
@ISessionsManagementService private readonly activeSessionService: ISessionsManagementService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
|
||||
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISessionsWorkbenchService } from '../../sessions/browser/sessionsWorkbenchService.js';
|
||||
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
|
||||
import { IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js';
|
||||
import { IChatService } from '../../../../workbench/contrib/chat/common/chatService/chatService.js';
|
||||
import { ChatModeKind } from '../../../../workbench/contrib/chat/common/constants.js';
|
||||
@@ -30,7 +30,7 @@ export class CustomizationCreatorService {
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IChatService private readonly chatService: IChatService,
|
||||
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
|
||||
@ISessionsWorkbenchService private readonly activeSessionService: ISessionsWorkbenchService,
|
||||
@ISessionsManagementService private readonly activeSessionService: ISessionsManagementService,
|
||||
@IPromptsService private readonly promptsService: IPromptsService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
) { }
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';
|
||||
import { IEditorService } from '../../../../workbench/services/editor/common/editorService.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { ISessionsWorkbenchService } from '../../sessions/browser/sessionsWorkbenchService.js';
|
||||
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
|
||||
|
||||
//#region Context Keys
|
||||
|
||||
@@ -487,7 +487,7 @@ export class AICustomizationViewPane extends ViewPane {
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@ISessionsWorkbenchService private readonly activeSessionService: ISessionsWorkbenchService,
|
||||
@ISessionsManagementService private readonly activeSessionService: ISessionsManagementService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { MarkdownString } from '../../../../base/common/htmlContent.js';
|
||||
import { Iterable } from '../../../../base/common/iterator.js';
|
||||
import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { autorun, derived, observableFromEvent, observableValue } from '../../../../base/common/observable.js';
|
||||
import { autorun, derived, derivedOpts, IObservable, IObservableWithChange, observableFromEvent, observableValue } from '../../../../base/common/observable.js';
|
||||
import { basename, dirname } from '../../../../base/common/path.js';
|
||||
import { isEqual } from '../../../../base/common/resources.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
@@ -44,7 +44,6 @@ import { IResourceLabel, ResourceLabels } from '../../../../workbench/browser/la
|
||||
import { ViewPane, IViewPaneOptions, ViewAction } from '../../../../workbench/browser/parts/views/viewPane.js';
|
||||
import { ViewPaneContainer } from '../../../../workbench/browser/parts/views/viewPaneContainer.js';
|
||||
import { IViewDescriptorService } from '../../../../workbench/common/views.js';
|
||||
import { IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js';
|
||||
import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';
|
||||
import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js';
|
||||
import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actions/chatContextKeys.js';
|
||||
@@ -56,6 +55,7 @@ import { IActivityService, NumberBadge } from '../../../../workbench/services/ac
|
||||
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../../workbench/services/editor/common/editorService.js';
|
||||
import { IExtensionService } from '../../../../workbench/services/extensions/common/extensions.js';
|
||||
import { IWorkbenchLayoutService } from '../../../../workbench/services/layout/browser/layoutService.js';
|
||||
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -94,6 +94,11 @@ interface IChangesFolderItem {
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
interface IActiveSession {
|
||||
readonly resource: URI;
|
||||
readonly sessionType: string;
|
||||
}
|
||||
|
||||
type ChangesTreeElement = IChangesFileItem | IChangesFolderItem;
|
||||
|
||||
function isChangesFileItem(element: ChangesTreeElement): element is IChangesFileItem {
|
||||
@@ -201,8 +206,14 @@ export class ChangesViewPane extends ViewPane {
|
||||
this.storageService.store('changesView.viewMode', mode, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
}
|
||||
|
||||
// Track the active session's editing session resource
|
||||
private readonly activeSessionResource = observableValue<URI | undefined>(this, undefined);
|
||||
// Track the active session used by this view
|
||||
private readonly activeSession: IObservableWithChange<IActiveSession | undefined>;
|
||||
private readonly activeSessionFileCountObs: IObservableWithChange<number>;
|
||||
private readonly activeSessionHasChangesObs: IObservableWithChange<boolean>;
|
||||
|
||||
get activeSessionHasChanges(): IObservable<boolean> {
|
||||
return this.activeSessionHasChangesObs;
|
||||
}
|
||||
|
||||
// Badge for file count
|
||||
private readonly badgeDisposable = this._register(new MutableDisposable());
|
||||
@@ -220,9 +231,9 @@ export class ChangesViewPane extends ViewPane {
|
||||
@IHoverService hoverService: IHoverService,
|
||||
@IChatEditingService private readonly chatEditingService: IChatEditingService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
|
||||
@IActivityService private readonly activityService: IActivityService,
|
||||
@IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService,
|
||||
@ISessionsManagementService private readonly sessionManagementService: ISessionsManagementService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
@@ -235,113 +246,85 @@ export class ChangesViewPane extends ViewPane {
|
||||
this.viewModeContextKey = changesViewModeContextKey.bindTo(contextKeyService);
|
||||
this.viewModeContextKey.set(initialMode);
|
||||
|
||||
// Track active session from sessions management service
|
||||
this.activeSession = derivedOpts<IActiveSession | undefined>({
|
||||
equalsFn: (a, b) => isEqual(a?.resource, b?.resource),
|
||||
}, reader => {
|
||||
const activeSession = this.sessionManagementService.activeSession.read(reader);
|
||||
if (!activeSession?.resource) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
resource: activeSession.resource,
|
||||
sessionType: getChatSessionType(activeSession.resource),
|
||||
};
|
||||
}).recomputeInitiallyAndOnChange(this._store);
|
||||
|
||||
this.activeSessionFileCountObs = this.createActiveSessionFileCountObservable();
|
||||
this.activeSessionHasChangesObs = this.activeSessionFileCountObs.map(fileCount => fileCount > 0).recomputeInitiallyAndOnChange(this._store);
|
||||
|
||||
// Setup badge tracking
|
||||
this.registerBadgeTracking();
|
||||
|
||||
// Track active session from focused chat widgets
|
||||
this.registerActiveSessionTracking();
|
||||
|
||||
// Set chatSessionType on the view's context key service so ViewTitle
|
||||
// menu items can use it in their `when` clauses. Update reactively
|
||||
// when the active session changes.
|
||||
const viewSessionTypeKey = this.scopedContextKeyService.createKey<string>(ChatContextKeys.agentSessionType.key, '');
|
||||
this._register(autorun(reader => {
|
||||
const sessionResource = this.activeSessionResource.read(reader);
|
||||
viewSessionTypeKey.set(sessionResource ? getChatSessionType(sessionResource) : '');
|
||||
const activeSession = this.activeSession.read(reader);
|
||||
viewSessionTypeKey.set(activeSession?.sessionType ?? '');
|
||||
}));
|
||||
}
|
||||
|
||||
private registerActiveSessionTracking(): void {
|
||||
// Initialize with the last focused widget's session if available
|
||||
const lastFocused = this.chatWidgetService.lastFocusedWidget;
|
||||
if (lastFocused?.viewModel?.sessionResource) {
|
||||
this.activeSessionResource.set(lastFocused.viewModel.sessionResource, undefined);
|
||||
}
|
||||
|
||||
// Listen for new widgets and track their focus
|
||||
this._register(this.chatWidgetService.onDidAddWidget(widget => {
|
||||
this._register(widget.onDidFocus(() => {
|
||||
if (widget.viewModel?.sessionResource) {
|
||||
this.activeSessionResource.set(widget.viewModel.sessionResource, undefined);
|
||||
}
|
||||
}));
|
||||
|
||||
// Also track view model changes (when a widget loads a different session)
|
||||
this._register(widget.onDidChangeViewModel(({ currentSessionResource }) => {
|
||||
// Only update if this widget is focused
|
||||
if (this.chatWidgetService.lastFocusedWidget === widget && currentSessionResource) {
|
||||
this.activeSessionResource.set(currentSessionResource, undefined);
|
||||
}
|
||||
}));
|
||||
}));
|
||||
|
||||
// Track focus changes on existing widgets
|
||||
for (const widget of this.chatWidgetService.getAllWidgets()) {
|
||||
this._register(widget.onDidFocus(() => {
|
||||
if (widget.viewModel?.sessionResource) {
|
||||
this.activeSessionResource.set(widget.viewModel.sessionResource, undefined);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(widget.onDidChangeViewModel(({ currentSessionResource }) => {
|
||||
if (this.chatWidgetService.lastFocusedWidget === widget && currentSessionResource) {
|
||||
this.activeSessionResource.set(currentSessionResource, undefined);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private registerBadgeTracking(): void {
|
||||
// Signal observable that triggers when sessions data changes
|
||||
// Update badge when file count changes
|
||||
this._register(autorun(reader => {
|
||||
const fileCount = this.activeSessionFileCountObs.read(reader);
|
||||
this.updateBadge(fileCount);
|
||||
}));
|
||||
}
|
||||
|
||||
private createActiveSessionFileCountObservable(): IObservableWithChange<number> {
|
||||
const activeSessionResource = this.activeSession.map(a => a?.resource);
|
||||
|
||||
const sessionsChangedSignal = observableFromEvent(
|
||||
this,
|
||||
this.agentSessionsService.model.onDidChangeSessions,
|
||||
() => ({}),
|
||||
);
|
||||
|
||||
// Observable for session file changes from agentSessionsService (cloud/background sessions)
|
||||
// Reactive to both activeSessionResource changes AND session data changes
|
||||
const sessionFileChangesObs = derived(reader => {
|
||||
const sessionResource = this.activeSessionResource.read(reader);
|
||||
const sessionResource = activeSessionResource.read(reader);
|
||||
sessionsChangedSignal.read(reader);
|
||||
if (!sessionResource) {
|
||||
return Iterable.empty();
|
||||
}
|
||||
|
||||
const model = this.agentSessionsService.getSession(sessionResource);
|
||||
return model?.changes instanceof Array ? model.changes : Iterable.empty();
|
||||
});
|
||||
|
||||
// Create observable for the number of files changed in the active session
|
||||
// Combines both editing session entries and session file changes (for cloud/background sessions)
|
||||
const fileCountObs = derived(reader => {
|
||||
const sessionResource = this.activeSessionResource.read(reader);
|
||||
if (!sessionResource) {
|
||||
return derived(reader => {
|
||||
const activeSession = this.activeSession.read(reader);
|
||||
if (!activeSession) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Background chat sessions render the working set based on the session files, not the editing session
|
||||
const isBackgroundSession = getChatSessionType(sessionResource) === AgentSessionProviders.Background;
|
||||
const isBackgroundSession = activeSession.sessionType === AgentSessionProviders.Background;
|
||||
|
||||
// Count from editing session entries (skip for background sessions)
|
||||
let editingSessionCount = 0;
|
||||
if (!isBackgroundSession) {
|
||||
const sessions = this.chatEditingService.editingSessionsObs.read(reader);
|
||||
const session = sessions.find(candidate => isEqual(candidate.chatSessionResource, sessionResource));
|
||||
const session = sessions.find(candidate => isEqual(candidate.chatSessionResource, activeSession.resource));
|
||||
editingSessionCount = session ? session.entries.read(reader).length : 0;
|
||||
}
|
||||
|
||||
// Count from session file changes (cloud/background sessions)
|
||||
const sessionFiles = [...sessionFileChangesObs.read(reader)];
|
||||
const sessionFilesCount = sessionFiles.length;
|
||||
|
||||
return editingSessionCount + sessionFilesCount;
|
||||
});
|
||||
|
||||
// Update badge when file count changes
|
||||
this._register(autorun(reader => {
|
||||
const fileCount = fileCountObs.read(reader);
|
||||
this.updateBadge(fileCount);
|
||||
}));
|
||||
}).recomputeInitiallyAndOnChange(this._store);
|
||||
}
|
||||
|
||||
private updateBadge(fileCount: number): void {
|
||||
@@ -404,25 +387,26 @@ export class ChangesViewPane extends ViewPane {
|
||||
|
||||
private onVisible(): void {
|
||||
this.renderDisposables.clear();
|
||||
const activeSessionResource = this.activeSession.map(a => a?.resource);
|
||||
|
||||
// Create observable for the active editing session
|
||||
// Note: We must read editingSessionsObs to establish a reactive dependency,
|
||||
// so that the view updates when a new editing session is added (e.g., cloud sessions)
|
||||
const activeEditingSessionObs = derived(reader => {
|
||||
const sessionResource = this.activeSessionResource.read(reader);
|
||||
if (!sessionResource) {
|
||||
const activeSession = this.activeSession.read(reader);
|
||||
if (!activeSession) {
|
||||
return undefined;
|
||||
}
|
||||
const sessions = this.chatEditingService.editingSessionsObs.read(reader);
|
||||
return sessions.find(candidate => isEqual(candidate.chatSessionResource, sessionResource));
|
||||
return sessions.find(candidate => isEqual(candidate.chatSessionResource, activeSession.resource));
|
||||
});
|
||||
|
||||
// Create observable for edit session entries from the ACTIVE session only (local editing sessions)
|
||||
const editSessionEntriesObs = derived(reader => {
|
||||
const sessionResource = this.activeSessionResource.read(reader);
|
||||
const activeSession = this.activeSession.read(reader);
|
||||
|
||||
// Background chat sessions render the working set based on the session files, not the editing session
|
||||
if (sessionResource && getChatSessionType(sessionResource) === AgentSessionProviders.Background) {
|
||||
if (activeSession?.sessionType === AgentSessionProviders.Background) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -462,9 +446,9 @@ export class ChangesViewPane extends ViewPane {
|
||||
);
|
||||
|
||||
// Observable for session file changes from agentSessionsService (cloud/background sessions)
|
||||
// Reactive to both activeSessionResource changes AND session data changes
|
||||
// Reactive to both activeSession changes AND session data changes
|
||||
const sessionFileChangesObs = derived(reader => {
|
||||
const sessionResource = this.activeSessionResource.read(reader);
|
||||
const sessionResource = activeSessionResource.read(reader);
|
||||
sessionsChangedSignal.read(reader);
|
||||
if (!sessionResource) {
|
||||
return Iterable.empty();
|
||||
@@ -530,8 +514,8 @@ export class ChangesViewPane extends ViewPane {
|
||||
// `chatSessionType == copilotcli` (e.g. Create Pull Request) are shown
|
||||
const chatSessionTypeKey = scopedContextKeyService.createKey<string>(ChatContextKeys.agentSessionType.key, '');
|
||||
this.renderDisposables.add(autorun(reader => {
|
||||
const sessionResource = this.activeSessionResource.read(reader);
|
||||
chatSessionTypeKey.set(sessionResource ? getChatSessionType(sessionResource) : '');
|
||||
const activeSession = this.activeSession.read(reader);
|
||||
chatSessionTypeKey.set(activeSession?.sessionType ?? '');
|
||||
}));
|
||||
|
||||
// Bind required context keys for the menu buttons
|
||||
@@ -560,7 +544,7 @@ export class ChangesViewPane extends ViewPane {
|
||||
|
||||
this.renderDisposables.add(autorun(reader => {
|
||||
const { isSessionMenu, added, removed } = topLevelStats.read(reader);
|
||||
const sessionResource = this.activeSessionResource.read(reader);
|
||||
const sessionResource = activeSessionResource.read(reader);
|
||||
reader.store.add(scopedInstantiationService.createInstance(
|
||||
MenuWorkbenchButtonBar,
|
||||
this.actionsContainer!,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
|
||||
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { localize, localize2 } from '../../../../nls.js';
|
||||
import { Action2, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||
@@ -14,15 +15,19 @@ import { Registry } from '../../../../platform/registry/common/platform.js';
|
||||
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
|
||||
import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js';
|
||||
import { isAgentSession } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
|
||||
import { ISessionsWorkbenchService, IsNewChatSessionContext } from '../../sessions/browser/sessionsWorkbenchService.js';
|
||||
import { ITerminalService, ITerminalGroupService } from '../../../../workbench/contrib/terminal/browser/terminal.js';
|
||||
import { ISessionsManagementService, IsNewChatSessionContext } from '../../sessions/browser/sessionsManagementService.js';
|
||||
import { ITerminalService } from '../../../../workbench/contrib/terminal/browser/terminal.js';
|
||||
import { TERMINAL_VIEW_ID } from '../../../../workbench/contrib/terminal/common/terminal.js';
|
||||
import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js';
|
||||
import { Menus } from '../../../browser/menus.js';
|
||||
import { BranchChatSessionAction } from './branchChatSessionAction.js';
|
||||
import { RunScriptContribution } from './runScriptAction.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
|
||||
import { AgenticPromptsService } from './promptsService.js';
|
||||
import { IPromptsService } from '../../../../workbench/contrib/chat/common/promptSyntax/service/promptsService.js';
|
||||
import { ChatViewContainerId, ChatViewId } from '../../../../workbench/contrib/chat/browser/chat.js';
|
||||
import { CHAT_CATEGORY } from '../../../../workbench/contrib/chat/browser/actions/chatActions.js';
|
||||
import { NewChatViewPane, SessionsViewId } from './newChatViewPane.js';
|
||||
import { ViewPaneContainer } from '../../../../workbench/browser/parts/views/viewPaneContainer.js';
|
||||
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
|
||||
@@ -46,9 +51,9 @@ export class OpenSessionWorktreeInVSCodeAction extends Action2 {
|
||||
|
||||
override async run(accessor: ServicesAccessor,): Promise<void> {
|
||||
const hostService = accessor.get(IHostService);
|
||||
const agentSessionsService = accessor.get(ISessionsWorkbenchService);
|
||||
const sessionsManagementService = accessor.get(ISessionsManagementService);
|
||||
|
||||
const activeSession = agentSessionsService.activeSession.get();
|
||||
const activeSession = sessionsManagementService.activeSession.get();
|
||||
if (!activeSession) {
|
||||
return;
|
||||
}
|
||||
@@ -64,6 +69,33 @@ export class OpenSessionWorktreeInVSCodeAction extends Action2 {
|
||||
}
|
||||
registerAction2(OpenSessionWorktreeInVSCodeAction);
|
||||
|
||||
class NewChatInSessionsWindowAction extends Action2 {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.sessions.newChat',
|
||||
title: localize2('chat.newEdits.label', "New Chat"),
|
||||
category: CHAT_CATEGORY,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib + 2,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KeyN,
|
||||
secondary: [KeyMod.CtrlCmd | KeyCode.KeyL],
|
||||
mac: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KeyN,
|
||||
secondary: [KeyMod.WinCtrl | KeyCode.KeyL]
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override run(accessor: ServicesAccessor): void {
|
||||
const sessionsManagementService = accessor.get(ISessionsManagementService);
|
||||
sessionsManagementService.openNewSession();
|
||||
}
|
||||
}
|
||||
|
||||
registerAction2(NewChatInSessionsWindowAction);
|
||||
|
||||
export class OpenSessionInTerminalAction extends Action2 {
|
||||
|
||||
constructor() {
|
||||
@@ -81,10 +113,10 @@ export class OpenSessionInTerminalAction extends Action2 {
|
||||
|
||||
override async run(accessor: ServicesAccessor,): Promise<void> {
|
||||
const terminalService = accessor.get(ITerminalService);
|
||||
const terminalGroupService = accessor.get(ITerminalGroupService);
|
||||
const agentSessionsService = accessor.get(ISessionsWorkbenchService);
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
const sessionsManagementService = accessor.get(ISessionsManagementService);
|
||||
|
||||
const activeSession = agentSessionsService.activeSession.get();
|
||||
const activeSession = sessionsManagementService.activeSession.get();
|
||||
const repository = isAgentSession(activeSession) && activeSession.providerType !== AgentSessionProviders.Cloud
|
||||
? activeSession.worktree
|
||||
: undefined;
|
||||
@@ -94,7 +126,7 @@ export class OpenSessionInTerminalAction extends Action2 {
|
||||
terminalService.setActiveInstance(instance);
|
||||
}
|
||||
}
|
||||
terminalGroupService.showPanel(true);
|
||||
await viewsService.openView(TERMINAL_VIEW_ID, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
transition: justify-content 0.4s ease;
|
||||
padding-bottom: 10%;
|
||||
}
|
||||
|
||||
.chat-full-welcome.revealed {
|
||||
@@ -29,32 +29,29 @@
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Mascot */
|
||||
.chat-full-welcome-mascot {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
/* Watermark letterpress */
|
||||
.chat-full-welcome-letterpress {
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
aspect-ratio: 1/1;
|
||||
background-image: url('../../../../../workbench/browser/parts/editor/media/letterpress-dark.svg');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 12px;
|
||||
animation: chat-full-welcome-mascot-bounce 1s ease-in-out infinite;
|
||||
transition: animation-duration 0.3s ease;
|
||||
background-image: url('../../../../../workbench/browser/media/code-icon.svg');
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chat-full-welcome.revealed .chat-full-welcome-mascot {
|
||||
animation-duration: 2s;
|
||||
.vs .chat-full-welcome-letterpress {
|
||||
background-image: url('../../../../../workbench/browser/parts/editor/media/letterpress-light.svg');
|
||||
}
|
||||
|
||||
@keyframes chat-full-welcome-mascot-bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.hc-light .chat-full-welcome-letterpress {
|
||||
background-image: url('../../../../../workbench/browser/parts/editor/media/letterpress-hcLight.svg');
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
.hc-black .chat-full-welcome-letterpress {
|
||||
background-image: url('../../../../../workbench/browser/parts/editor/media/letterpress-hcDark.svg');
|
||||
}
|
||||
|
||||
/* Input slot */
|
||||
@@ -71,18 +68,18 @@
|
||||
animation: chat-full-welcome-fade-in 0.35s ease 0.15s both;
|
||||
}
|
||||
|
||||
/* Option group pickers container (below the input) */
|
||||
/* Option group pickers container (above the input) */
|
||||
.chat-full-welcome-pickers-container {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: 12px;
|
||||
margin: 0 0 24px 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat-full-welcome.revealed .chat-full-welcome-pickers-container {
|
||||
display: flex;
|
||||
display: block;
|
||||
animation: chat-full-welcome-fade-in 0.35s ease 0.1s both;
|
||||
}
|
||||
|
||||
@@ -103,6 +100,39 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Local mode picker (Workspace / Worktree) below input */
|
||||
.chat-full-welcome-local-mode {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin-top: 8px;
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.chat-full-welcome.revealed .chat-full-welcome-local-mode {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sessions-chat-local-mode-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sessions-chat-local-mode-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sessions-chat-local-mode-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Ensure the input editor fits properly */
|
||||
.chat-full-welcome-inputSlot .interactive-input-part {
|
||||
margin: 0;
|
||||
@@ -128,7 +158,7 @@
|
||||
background-color: var(--vscode-input-background) !important;
|
||||
}
|
||||
|
||||
/* Pickers row - flat horizontal bar below the input */
|
||||
/* Pickers row - two equal halves */
|
||||
.chat-full-welcome-pickers {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -136,56 +166,76 @@
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0 2px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.chat-full-welcome-pickers:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Left group (target dropdown + left-side extension pickers) */
|
||||
.sessions-chat-pickers-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Spacer between left and right groups */
|
||||
.sessions-chat-pickers-spacer {
|
||||
/* Left half: target switcher, right-justified */
|
||||
.sessions-chat-pickers-left-half {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Right group (repo/folder pickers) */
|
||||
.sessions-chat-extension-pickers-right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sessions-chat-extension-pickers-right:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Left extension pickers container */
|
||||
.sessions-chat-extension-pickers-left {
|
||||
/* Right half: pickers, left-justified */
|
||||
.sessions-chat-pickers-right-half {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sessions-chat-extension-pickers-left:empty {
|
||||
/* Separator between switcher and folder picker */
|
||||
.sessions-chat-pickers-left-separator {
|
||||
width: 1px;
|
||||
height: 22px;
|
||||
background-color: var(--vscode-editorWidget-border, var(--vscode-contrastBorder));
|
||||
margin: 0 12px;
|
||||
flex-shrink: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Target switcher radio buttons - bigger and fancier */
|
||||
.sessions-chat-dropdown-wrapper .monaco-custom-radio > .monaco-button {
|
||||
font-size: 13px;
|
||||
line-height: 1.4em;
|
||||
padding: 4px 14px;
|
||||
}
|
||||
|
||||
.sessions-chat-dropdown-wrapper .monaco-custom-radio > .monaco-button:first-child {
|
||||
border-top-left-radius: 6px;
|
||||
border-bottom-left-radius: 6px;
|
||||
}
|
||||
|
||||
.sessions-chat-dropdown-wrapper .monaco-custom-radio > .monaco-button:last-child {
|
||||
border-top-right-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
}
|
||||
|
||||
.sessions-chat-dropdown-wrapper .monaco-custom-radio > .monaco-button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Folder label next to the picker */
|
||||
.sessions-chat-folder-label {
|
||||
font-size: 13px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
white-space: nowrap;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* Target dropdown button */
|
||||
.sessions-chat-dropdown-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 16px;
|
||||
padding: 3px 0 3px 6px;
|
||||
padding: 3px 3px 3px 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
@@ -231,7 +281,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 16px;
|
||||
padding: 3px 0 3px 6px;
|
||||
padding: 3px 3px 3px 6px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
|
||||
@@ -7,7 +7,8 @@ import './media/chatWidget.css';
|
||||
import './media/chatWelcomePart.css';
|
||||
import * as dom from '../../../../base/browser/dom.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { toAction } from '../../../../base/common/actions.js';
|
||||
import { Separator, toAction } from '../../../../base/common/actions.js';
|
||||
import { Radio } from '../../../../base/browser/ui/radio/radio.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { KeyCode } from '../../../../base/common/keyCodes.js';
|
||||
import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';
|
||||
@@ -25,17 +26,14 @@ import { IInstantiationService } from '../../../../platform/instantiation/common
|
||||
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
|
||||
import { IProductService } from '../../../../platform/product/common/productService.js';
|
||||
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
|
||||
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
|
||||
import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';
|
||||
import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js';
|
||||
import { isEqual } from '../../../../base/common/resources.js';
|
||||
import { asCSSUrl } from '../../../../base/browser/cssValue.js';
|
||||
import { FileAccess } from '../../../../base/common/network.js';
|
||||
import { basename, isEqual } from '../../../../base/common/resources.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { AgentSessionProviders, getAgentSessionProviderIcon } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js';
|
||||
import { ISessionsWorkbenchService } from '../../sessions/browser/sessionsWorkbenchService.js';
|
||||
import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js';
|
||||
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
|
||||
import { ChatSessionPosition, getResourceForNewChatSession } from '../../../../workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.js';
|
||||
import { ChatSessionPickerActionItem, IChatSessionPickerDelegate } from '../../../../workbench/contrib/chat/browser/chatSessions/chatSessionPickerActionItem.js';
|
||||
import { SearchableOptionPickerActionItem } from '../../../../workbench/contrib/chat/browser/chatSessions/searchableOptionPickerActionItem.js';
|
||||
@@ -43,10 +41,15 @@ import { ChatAgentLocation, ChatModeKind } from '../../../../workbench/contrib/c
|
||||
import { IChatSendRequestOptions } from '../../../../workbench/contrib/chat/common/chatService/chatService.js';
|
||||
import { IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';
|
||||
import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../../../../workbench/contrib/chat/common/languageModels.js';
|
||||
import { IModelPickerDelegate, ModelPickerActionItem } from '../../../../workbench/contrib/chat/browser/widget/input/modelPickerActionItem.js';
|
||||
import { IModelPickerDelegate } from '../../../../workbench/contrib/chat/browser/widget/input/modelPickerActionItem.js';
|
||||
import { EnhancedModelPickerActionItem } from '../../../../workbench/contrib/chat/browser/widget/input/modelPickerActionItem2.js';
|
||||
import { IChatInputPickerOptions } from '../../../../workbench/contrib/chat/browser/widget/input/chatInputPickerActionItem.js';
|
||||
import { WorkspaceFolderCountContext } from '../../../../workbench/common/contextkeys.js';
|
||||
import { IViewDescriptorService } from '../../../../workbench/common/views.js';
|
||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';
|
||||
import { IWorkspacesService, isRecentFolder } from '../../../../platform/workspaces/common/workspaces.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
|
||||
import { IViewPaneOptions, ViewPane } from '../../../../workbench/browser/parts/views/viewPane.js';
|
||||
import { ContextMenuController } from '../../../../editor/contrib/contextmenu/browser/contextmenu.js';
|
||||
import { getSimpleEditorOptions } from '../../../../workbench/contrib/codeEditor/browser/simpleEditorOptions.js';
|
||||
@@ -104,6 +107,20 @@ class TargetConfig extends Disposable implements ITargetConfig {
|
||||
this._onDidChangeSelectedTarget.fire(target);
|
||||
}
|
||||
}
|
||||
|
||||
setAllowedTargets(targets: AgentSessionProviders[]): void {
|
||||
const newSet = new Set(targets);
|
||||
this._allowedTargets.set(newSet, undefined);
|
||||
this._onDidChangeAllowedTargets.fire(newSet);
|
||||
|
||||
// If the currently selected target is no longer allowed, switch to the first allowed target
|
||||
const current = this._selectedTarget.get();
|
||||
if (current && !newSet.has(current)) {
|
||||
const fallback = newSet.values().next().value;
|
||||
this._selectedTarget.set(fallback, undefined);
|
||||
this._onDidChangeSelectedTarget.fire(fallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
@@ -119,6 +136,7 @@ export interface INewChatSendRequestData {
|
||||
readonly query: string;
|
||||
readonly sendOptions: IChatSendRequestOptions;
|
||||
readonly selectedOptions: ReadonlyMap<string, IChatSessionProviderOptionItem>;
|
||||
readonly folderUri?: URI;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,7 +157,7 @@ export interface INewChatWidgetOptions {
|
||||
*/
|
||||
class NewChatWidget extends Disposable {
|
||||
|
||||
private readonly _targetConfig: ITargetConfig;
|
||||
private readonly _targetConfig: TargetConfig;
|
||||
private readonly _options: INewChatWidgetOptions;
|
||||
|
||||
// Input
|
||||
@@ -155,6 +173,11 @@ class NewChatWidget extends Disposable {
|
||||
private _extensionPickersLeftContainer: HTMLElement | undefined;
|
||||
private _extensionPickersRightContainer: HTMLElement | undefined;
|
||||
private _inputSlot: HTMLElement | undefined;
|
||||
private _localModeContainer: HTMLElement | undefined;
|
||||
private _localModeDropdownContainer: HTMLElement | undefined;
|
||||
private _localModePickersContainer: HTMLElement | undefined;
|
||||
private _localMode: 'workspace' | 'worktree' = 'worktree';
|
||||
private _selectedFolderUri: URI | undefined;
|
||||
private readonly _pickerWidgets = new Map<string, ChatSessionPickerActionItem | SearchableOptionPickerActionItem>();
|
||||
private readonly _pickerWidgetDisposables = this._register(new DisposableStore());
|
||||
private readonly _optionEmitters = new Map<string, Emitter<IChatSessionProviderOptionItem>>();
|
||||
@@ -171,19 +194,29 @@ class NewChatWidget extends Disposable {
|
||||
@ILanguageModelsService private readonly languageModelsService: ILanguageModelsService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IHoverService _hoverService: IHoverService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
super();
|
||||
this._targetConfig = this._register(new TargetConfig(options.targetConfig));
|
||||
this._options = options;
|
||||
|
||||
// Restore last picked folder
|
||||
const lastFolder = this.storageService.get('agentSessions.lastPickedFolder', StorageScope.PROFILE);
|
||||
if (lastFolder) {
|
||||
try { this._selectedFolderUri = URI.parse(lastFolder); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// When target changes, regenerate pending resource
|
||||
this._register(this._targetConfig.onDidChangeSelectedTarget(() => {
|
||||
this._generatePendingSessionResource();
|
||||
this._updateTargetDropdown();
|
||||
this._notifyFolderSelection();
|
||||
this._renderExtensionPickers(true);
|
||||
this._renderLocalModePicker();
|
||||
}));
|
||||
|
||||
this._register(this._targetConfig.onDidChangeAllowedTargets(() => {
|
||||
@@ -196,6 +229,7 @@ class NewChatWidget extends Disposable {
|
||||
|
||||
// Listen for option group changes to re-render pickers
|
||||
this._register(this.chatSessionsService.onDidChangeOptionGroups(() => {
|
||||
this._notifyFolderSelection();
|
||||
this._renderExtensionPickers();
|
||||
}));
|
||||
|
||||
@@ -224,11 +258,12 @@ class NewChatWidget extends Disposable {
|
||||
const wrapper = dom.append(container, dom.$('.sessions-chat-widget'));
|
||||
const welcomeElement = dom.append(wrapper, dom.$('.chat-full-welcome'));
|
||||
|
||||
// Mascot
|
||||
// Watermark letterpress
|
||||
const header = dom.append(welcomeElement, dom.$('.chat-full-welcome-header'));
|
||||
const quality = this.productService.quality ?? 'stable';
|
||||
const mascot = dom.append(header, dom.$('.chat-full-welcome-mascot'));
|
||||
mascot.style.backgroundImage = asCSSUrl(FileAccess.asBrowserUri(`vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions-${quality}.svg`));
|
||||
dom.append(header, dom.$('.chat-full-welcome-letterpress'));
|
||||
|
||||
// Option group pickers (above the input)
|
||||
this._pickersContainer = dom.append(welcomeElement, dom.$('.chat-full-welcome-pickers-container'));
|
||||
|
||||
// Input slot
|
||||
this._inputSlot = dom.append(welcomeElement, dom.$('.chat-full-welcome-inputSlot'));
|
||||
@@ -239,8 +274,11 @@ class NewChatWidget extends Disposable {
|
||||
this._createToolbar(inputArea);
|
||||
this._inputSlot.appendChild(inputArea);
|
||||
|
||||
// Option group pickers (below the input)
|
||||
this._pickersContainer = dom.append(welcomeElement, dom.$('.chat-full-welcome-pickers-container'));
|
||||
// Local mode picker (below the input, shown when Local is selected)
|
||||
this._localModeContainer = dom.append(welcomeElement, dom.$('.chat-full-welcome-local-mode'));
|
||||
this._localModeDropdownContainer = dom.append(this._localModeContainer, dom.$('.sessions-chat-local-mode-left'));
|
||||
dom.append(this._localModeContainer, dom.$('.sessions-chat-local-mode-spacer'));
|
||||
this._localModePickersContainer = dom.append(this._localModeContainer, dom.$('.sessions-chat-local-mode-right'));
|
||||
|
||||
// Render target buttons & extension pickers
|
||||
this._renderOptionGroupPickers();
|
||||
@@ -251,12 +289,23 @@ class NewChatWidget extends Disposable {
|
||||
// Generate pending resource for option changes
|
||||
this._generatePendingSessionResource();
|
||||
|
||||
// Render local mode picker
|
||||
this._renderLocalModePicker();
|
||||
|
||||
// Reveal
|
||||
welcomeElement.classList.add('revealed');
|
||||
}
|
||||
|
||||
private _generatePendingSessionResource(): void {
|
||||
private _getEffectiveTarget(): AgentSessionProviders | undefined {
|
||||
const target = this._targetConfig.selectedTarget.get();
|
||||
if (target === AgentSessionProviders.Local && this._localMode === 'worktree') {
|
||||
return AgentSessionProviders.Background;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private _generatePendingSessionResource(): void {
|
||||
const target = this._getEffectiveTarget();
|
||||
if (!target || target === AgentSessionProviders.Local) {
|
||||
this._pendingSessionResource = undefined;
|
||||
return;
|
||||
@@ -354,7 +403,7 @@ class NewChatWidget extends Disposable {
|
||||
const action = { id: 'sessions.modelPicker', label: '', enabled: true, class: undefined, tooltip: '', run: () => { } };
|
||||
|
||||
const modelPicker = this.instantiationService.createInstance(
|
||||
ModelPickerActionItem, action, undefined, delegate, pickerOptions,
|
||||
EnhancedModelPickerActionItem, action, delegate, pickerOptions,
|
||||
);
|
||||
this._modelPickerDisposable.value = modelPicker;
|
||||
modelPicker.render(container);
|
||||
@@ -397,17 +446,15 @@ class NewChatWidget extends Disposable {
|
||||
|
||||
const pickersRow = dom.append(this._pickersContainer, dom.$('.chat-full-welcome-pickers'));
|
||||
|
||||
// Left group: target dropdown + non-repo extension pickers
|
||||
const leftGroup = dom.append(pickersRow, dom.$('.sessions-chat-pickers-left'));
|
||||
this._targetDropdownContainer = dom.append(leftGroup, dom.$('.sessions-chat-dropdown-wrapper'));
|
||||
// Left half: target switcher (right-justified within its half)
|
||||
const leftHalf = dom.append(pickersRow, dom.$('.sessions-chat-pickers-left-half'));
|
||||
this._targetDropdownContainer = dom.append(leftHalf, dom.$('.sessions-chat-dropdown-wrapper'));
|
||||
this._renderTargetDropdown(this._targetDropdownContainer);
|
||||
this._extensionPickersLeftContainer = dom.append(leftGroup, dom.$('.sessions-chat-extension-pickers-left'));
|
||||
|
||||
// Spacer
|
||||
dom.append(pickersRow, dom.$('.sessions-chat-pickers-spacer'));
|
||||
|
||||
// Right group: repo/folder pickers
|
||||
this._extensionPickersRightContainer = dom.append(pickersRow, dom.$('.sessions-chat-extension-pickers-right'));
|
||||
// Right half: separator + pickers (left-justified within its half)
|
||||
const rightHalf = dom.append(pickersRow, dom.$('.sessions-chat-pickers-right-half'));
|
||||
this._extensionPickersLeftContainer = dom.append(rightHalf, dom.$('.sessions-chat-pickers-left-separator'));
|
||||
this._extensionPickersRightContainer = dom.append(rightHalf, dom.$('.sessions-chat-extension-pickers-right'));
|
||||
|
||||
this._renderExtensionPickers();
|
||||
}
|
||||
@@ -418,62 +465,146 @@ class NewChatWidget extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeType = this._targetConfig.selectedTarget.get() ?? AgentSessionProviders.Background;
|
||||
const icon = getAgentSessionProviderIcon(activeType);
|
||||
const name = getAgentSessionProviderName(activeType);
|
||||
const activeType = this._targetConfig.selectedTarget.get() ?? AgentSessionProviders.Local;
|
||||
const targets = [AgentSessionProviders.Local, AgentSessionProviders.Cloud].filter(t => allowed.has(t));
|
||||
const activeIndex = targets.indexOf(activeType);
|
||||
|
||||
const button = dom.append(container, dom.$('.sessions-chat-dropdown-button'));
|
||||
const radio = new Radio({
|
||||
items: targets.map(target => ({
|
||||
text: getAgentSessionProviderName(target),
|
||||
isActive: target === activeType,
|
||||
})),
|
||||
});
|
||||
this._welcomeContentDisposables.add(radio);
|
||||
container.appendChild(radio.domNode);
|
||||
|
||||
if (activeIndex >= 0) {
|
||||
radio.setActiveItem(activeIndex);
|
||||
}
|
||||
|
||||
this._welcomeContentDisposables.add(radio.onDidSelect(index => {
|
||||
this._targetConfig.setSelectedTarget(targets[index]);
|
||||
}));
|
||||
}
|
||||
|
||||
// --- Local mode picker (Workspace / Worktree) ---
|
||||
|
||||
private readonly _localModeDisposables = this._register(new DisposableStore());
|
||||
|
||||
private _renderLocalModePicker(): void {
|
||||
if (!this._localModeContainer || !this._localModeDropdownContainer || !this._localModePickersContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._localModeDisposables.clear();
|
||||
dom.clearNode(this._localModeDropdownContainer);
|
||||
dom.clearNode(this._localModePickersContainer);
|
||||
|
||||
const selectedTarget = this._targetConfig.selectedTarget.get();
|
||||
if (selectedTarget !== AgentSessionProviders.Local) {
|
||||
this._localModeContainer.style.visibility = 'hidden';
|
||||
return;
|
||||
}
|
||||
|
||||
this._localModeContainer.style.visibility = '';
|
||||
|
||||
// Dropdown button for Workspace / Worktree
|
||||
const modeLabel = this._localMode === 'workspace'
|
||||
? localize('localMode.workspace', "Workspace")
|
||||
: localize('localMode.worktree', "Worktree");
|
||||
const modeIcon = this._localMode === 'workspace' ? Codicon.folder : Codicon.worktree;
|
||||
|
||||
const button = dom.append(this._localModeDropdownContainer, dom.$('.sessions-chat-dropdown-button'));
|
||||
button.tabIndex = 0;
|
||||
button.role = 'button';
|
||||
button.ariaHasPopup = 'true';
|
||||
dom.append(button, renderIcon(icon));
|
||||
dom.append(button, dom.$('span.sessions-chat-dropdown-label', undefined, name));
|
||||
dom.append(button, renderIcon(modeIcon));
|
||||
dom.append(button, dom.$('span.sessions-chat-dropdown-label', undefined, modeLabel));
|
||||
dom.append(button, renderIcon(Codicon.chevronDown));
|
||||
|
||||
this._welcomeContentDisposables.add(dom.addDisposableListener(button, dom.EventType.CLICK, () => {
|
||||
const currentAllowed = this._targetConfig.allowedTargets.get();
|
||||
const currentActive = this._targetConfig.selectedTarget.get();
|
||||
const actions = [...currentAllowed]
|
||||
.filter(t => t !== AgentSessionProviders.Local)
|
||||
.map(sessionType => {
|
||||
const label = getAgentSessionProviderName(sessionType);
|
||||
return toAction({
|
||||
id: `target.${sessionType}`,
|
||||
label,
|
||||
checked: sessionType === currentActive,
|
||||
run: () => this._targetConfig.setSelectedTarget(sessionType),
|
||||
});
|
||||
});
|
||||
this._localModeDisposables.add(dom.addDisposableListener(button, dom.EventType.CLICK, () => {
|
||||
const actions = [
|
||||
toAction({
|
||||
id: 'localMode.workspace',
|
||||
label: localize('localMode.workspace', "Workspace"),
|
||||
checked: this._localMode === 'workspace',
|
||||
run: () => this._setLocalMode('workspace'),
|
||||
}),
|
||||
toAction({
|
||||
id: 'localMode.worktree',
|
||||
label: localize('localMode.worktree', "Worktree"),
|
||||
checked: this._localMode === 'worktree',
|
||||
run: () => this._setLocalMode('worktree'),
|
||||
}),
|
||||
];
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => button,
|
||||
getActions: () => actions,
|
||||
});
|
||||
}));
|
||||
|
||||
// Render pickers in the right side
|
||||
this._renderLocalModePickers();
|
||||
}
|
||||
|
||||
private _updateTargetDropdown(): void {
|
||||
if (!this._targetDropdownContainer) {
|
||||
private _setLocalMode(mode: 'workspace' | 'worktree'): void {
|
||||
if (this._localMode !== mode) {
|
||||
this._localMode = mode;
|
||||
this._generatePendingSessionResource();
|
||||
this._notifyFolderSelection();
|
||||
this._renderLocalModePicker();
|
||||
}
|
||||
}
|
||||
|
||||
private _notifyFolderSelection(): void {
|
||||
if (!this._pendingSessionResource) {
|
||||
return;
|
||||
}
|
||||
dom.clearNode(this._targetDropdownContainer);
|
||||
this._renderTargetDropdown(this._targetDropdownContainer);
|
||||
const folderUri = this._selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri;
|
||||
if (folderUri) {
|
||||
this.chatSessionsService.notifySessionOptionsChange(
|
||||
this._pendingSessionResource,
|
||||
[{ optionId: 'repository', value: folderUri.fsPath }]
|
||||
).catch((err) => this.logService.error('Failed to notify extension of folder selection:', err));
|
||||
}
|
||||
}
|
||||
|
||||
private _renderLocalModePickers(): void {
|
||||
if (!this._localModePickersContainer) {
|
||||
return;
|
||||
}
|
||||
dom.clearNode(this._localModePickersContainer);
|
||||
|
||||
if (this._localMode === 'worktree') {
|
||||
// Worktree mode: render extension pickers for Background provider
|
||||
this._renderExtensionPickersInContainer(this._localModePickersContainer, AgentSessionProviders.Background);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Welcome: Extension option pickers ---
|
||||
|
||||
private _renderExtensionPickers(force?: boolean): void {
|
||||
if (!this._extensionPickersLeftContainer || !this._extensionPickersRightContainer) {
|
||||
if (!this._extensionPickersRightContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeSessionType = this._targetConfig.selectedTarget.get();
|
||||
const activeSessionType = this._getEffectiveTarget();
|
||||
if (!activeSessionType) {
|
||||
this._clearExtensionPickers();
|
||||
return;
|
||||
}
|
||||
|
||||
// For Local target, show folder picker in top row and handle bottom row
|
||||
if (this._targetConfig.selectedTarget.get() === AgentSessionProviders.Local) {
|
||||
this._clearExtensionPickers();
|
||||
this._renderLocalFolderPickerInTopRow();
|
||||
this._renderLocalModePicker();
|
||||
return;
|
||||
}
|
||||
|
||||
const optionGroups = this.chatSessionsService.getOptionGroupsForSessionType(activeSessionType);
|
||||
if (!optionGroups || optionGroups.length === 0) {
|
||||
this._clearExtensionPickers();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -503,7 +634,15 @@ class NewChatWidget extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
visibleGroups.sort((a, b) => (a.when ? 1 : 0) - (b.when ? 1 : 0));
|
||||
visibleGroups.sort((a, b) => {
|
||||
// Repo/folder pickers first, then others
|
||||
const aRepo = isRepoOrFolderGroup(a) ? 0 : 1;
|
||||
const bRepo = isRepoOrFolderGroup(b) ? 0 : 1;
|
||||
if (aRepo !== bRepo) {
|
||||
return aRepo - bRepo;
|
||||
}
|
||||
return (a.when ? 1 : 0) - (b.when ? 1 : 0);
|
||||
});
|
||||
|
||||
if (!force && this._pickerWidgets.size === visibleGroups.length) {
|
||||
const allMatch = visibleGroups.every(g => this._pickerWidgets.has(g.id));
|
||||
@@ -514,6 +653,11 @@ class NewChatWidget extends Disposable {
|
||||
|
||||
this._clearExtensionPickers();
|
||||
|
||||
// Show the separator between target switcher and extension pickers
|
||||
if (this._extensionPickersLeftContainer) {
|
||||
this._extensionPickersLeftContainer.style.display = 'block';
|
||||
}
|
||||
|
||||
for (const optionGroup of visibleGroups) {
|
||||
const initialItem = this._getDefaultOptionForGroup(optionGroup);
|
||||
const initialState = { group: optionGroup, item: initialItem };
|
||||
@@ -556,15 +700,150 @@ class NewChatWidget extends Disposable {
|
||||
this._pickerWidgetDisposables.add(widget);
|
||||
this._pickerWidgets.set(optionGroup.id, widget);
|
||||
|
||||
// Repo/folder pickers go to the right; others go to the left
|
||||
const isRightAligned = isRepoOrFolderGroup(optionGroup);
|
||||
const targetContainer = isRightAligned ? this._extensionPickersRightContainer! : this._extensionPickersLeftContainer!;
|
||||
// All pickers go to the right
|
||||
const targetContainer = this._extensionPickersRightContainer!;
|
||||
|
||||
const slot = dom.append(targetContainer, dom.$('.sessions-chat-picker-slot'));
|
||||
widget.render(slot);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderLocalFolderPickerInTopRow(): void {
|
||||
if (!this._extensionPickersRightContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the separator
|
||||
if (this._extensionPickersLeftContainer) {
|
||||
this._extensionPickersLeftContainer.style.display = 'block';
|
||||
}
|
||||
|
||||
this._renderLocalFolderPickerInContainer(this._extensionPickersRightContainer, this._pickerWidgetDisposables);
|
||||
}
|
||||
|
||||
private _renderLocalFolderPickerInContainer(container: HTMLElement, disposables: DisposableStore): void {
|
||||
const currentFolderUri = this._selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri;
|
||||
const folderName = currentFolderUri ? basename(currentFolderUri) : localize('pickFolder', "Pick Folder");
|
||||
|
||||
const slot = dom.append(container, dom.$('.sessions-chat-picker-slot'));
|
||||
const button = dom.append(slot, dom.$('.sessions-chat-dropdown-button'));
|
||||
button.tabIndex = 0;
|
||||
button.role = 'button';
|
||||
button.ariaHasPopup = 'true';
|
||||
dom.append(button, dom.$('span.sessions-chat-dropdown-label', undefined, folderName));
|
||||
dom.append(button, renderIcon(Codicon.chevronDown));
|
||||
|
||||
const switchFolder = async (folderUri: URI) => {
|
||||
this._selectedFolderUri = folderUri;
|
||||
this.storageService.store('agentSessions.lastPickedFolder', folderUri.toString(), StorageScope.PROFILE, StorageTarget.MACHINE);
|
||||
this._notifyFolderSelection();
|
||||
this._renderExtensionPickers(true);
|
||||
};
|
||||
|
||||
disposables.add(dom.addDisposableListener(button, dom.EventType.CLICK, async () => {
|
||||
const recentlyOpened = await this.workspacesService.getRecentlyOpened();
|
||||
const recentFolders = recentlyOpened.workspaces
|
||||
.filter(isRecentFolder)
|
||||
.filter(r => !currentFolderUri || !isEqual(r.folderUri, currentFolderUri))
|
||||
.slice(0, 10);
|
||||
|
||||
const actions = recentFolders.map(recent => toAction({
|
||||
id: recent.folderUri.toString(),
|
||||
label: recent.label || basename(recent.folderUri),
|
||||
run: () => switchFolder(recent.folderUri),
|
||||
}));
|
||||
|
||||
actions.push(new Separator());
|
||||
actions.push(toAction({
|
||||
id: 'browse',
|
||||
label: localize('browseFolder', "Browse..."),
|
||||
run: async () => {
|
||||
const selected = await this.fileDialogService.showOpenDialog({
|
||||
canSelectFiles: false,
|
||||
canSelectFolders: true,
|
||||
canSelectMany: false,
|
||||
title: localize('selectFolder', "Select Folder"),
|
||||
});
|
||||
if (selected?.[0]) {
|
||||
await switchFolder(selected[0]);
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => button,
|
||||
getActions: () => actions,
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private _renderExtensionPickersInContainer(container: HTMLElement, sessionType: AgentSessionProviders): void {
|
||||
const optionGroups = this.chatSessionsService.getOptionGroupsForSessionType(sessionType);
|
||||
if (!optionGroups || optionGroups.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const visibleGroups: IChatSessionProviderOptionGroup[] = [];
|
||||
for (const group of optionGroups) {
|
||||
if (isModelOptionGroup(group)) {
|
||||
continue;
|
||||
}
|
||||
if (group.id === 'repository') {
|
||||
continue;
|
||||
}
|
||||
const hasItems = group.items.length > 0 || (group.commands || []).length > 0 || !!group.searchable;
|
||||
const passesWhenClause = this._evaluateOptionGroupVisibility(group);
|
||||
if (hasItems && passesWhenClause) {
|
||||
visibleGroups.push(group);
|
||||
}
|
||||
}
|
||||
|
||||
for (const optionGroup of visibleGroups) {
|
||||
const initialItem = this._getDefaultOptionForGroup(optionGroup);
|
||||
const initialState = { group: optionGroup, item: initialItem };
|
||||
|
||||
if (initialItem) {
|
||||
this._updateOptionContextKey(optionGroup.id, initialItem.id);
|
||||
}
|
||||
|
||||
const emitter = this._getOrCreateOptionEmitter(optionGroup.id);
|
||||
const itemDelegate: IChatSessionPickerDelegate = {
|
||||
getCurrentOption: () => this._selectedOptions.get(optionGroup.id) ?? this._getDefaultOptionForGroup(optionGroup),
|
||||
onDidChangeOption: emitter.event,
|
||||
setOption: (option: IChatSessionProviderOptionItem) => {
|
||||
this._selectedOptions.set(optionGroup.id, option);
|
||||
this._updateOptionContextKey(optionGroup.id, option.id);
|
||||
emitter.fire(option);
|
||||
|
||||
if (this._pendingSessionResource) {
|
||||
this.chatSessionsService.notifySessionOptionsChange(
|
||||
this._pendingSessionResource,
|
||||
[{ optionId: optionGroup.id, value: option }]
|
||||
).catch((err) => this.logService.error(`Failed to notify extension of ${optionGroup.id} change:`, err));
|
||||
}
|
||||
|
||||
this._renderLocalModePickers();
|
||||
},
|
||||
getOptionGroup: () => {
|
||||
const groups = this.chatSessionsService.getOptionGroupsForSessionType(sessionType);
|
||||
return groups?.find((g: { id: string }) => g.id === optionGroup.id);
|
||||
},
|
||||
getSessionResource: () => this._pendingSessionResource,
|
||||
};
|
||||
|
||||
const action = toAction({ id: optionGroup.id, label: optionGroup.name, run: () => { } });
|
||||
const widget = this.instantiationService.createInstance(
|
||||
optionGroup.searchable ? SearchableOptionPickerActionItem : ChatSessionPickerActionItem,
|
||||
action, initialState, itemDelegate
|
||||
);
|
||||
|
||||
this._localModeDisposables.add(widget);
|
||||
|
||||
const slot = dom.append(container, dom.$('.sessions-chat-picker-slot'));
|
||||
widget.render(slot);
|
||||
}
|
||||
}
|
||||
|
||||
private _evaluateOptionGroupVisibility(optionGroup: { id: string; when?: string }): boolean {
|
||||
if (!optionGroup.when) {
|
||||
return true;
|
||||
@@ -578,7 +857,7 @@ class NewChatWidget extends Disposable {
|
||||
}
|
||||
|
||||
private _syncOptionsFromSession(sessionResource: URI): void {
|
||||
const activeSessionType = this._targetConfig.selectedTarget.get();
|
||||
const activeSessionType = this._getEffectiveTarget();
|
||||
if (!activeSessionType) {
|
||||
return;
|
||||
}
|
||||
@@ -640,7 +919,7 @@ class NewChatWidget extends Disposable {
|
||||
this._pickerWidgets.clear();
|
||||
this._optionEmitters.clear();
|
||||
if (this._extensionPickersLeftContainer) {
|
||||
dom.clearNode(this._extensionPickersLeftContainer);
|
||||
this._extensionPickersLeftContainer.style.display = 'none';
|
||||
}
|
||||
if (this._extensionPickersRightContainer) {
|
||||
dom.clearNode(this._extensionPickersRightContainer);
|
||||
@@ -655,7 +934,7 @@ class NewChatWidget extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = this._targetConfig.selectedTarget.get();
|
||||
const target = this._getEffectiveTarget();
|
||||
if (!target) {
|
||||
this.logService.warn('ChatWelcomeWidget: No target selected, cannot create session');
|
||||
return;
|
||||
@@ -682,12 +961,15 @@ class NewChatWidget extends Disposable {
|
||||
agentIdSilent: contribution?.type,
|
||||
};
|
||||
|
||||
const folderUri = this._selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri;
|
||||
|
||||
this._options.onSendRequest?.({
|
||||
resource,
|
||||
target,
|
||||
query,
|
||||
sendOptions,
|
||||
selectedOptions: new Map(this._selectedOptions),
|
||||
folderUri,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -704,6 +986,10 @@ class NewChatWidget extends Disposable {
|
||||
focusInput(): void {
|
||||
this._editor?.focus();
|
||||
}
|
||||
|
||||
updateAllowedTargets(targets: AgentSessionProviders[]): void {
|
||||
this._targetConfig.setAllowedTargets(targets);
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
@@ -730,7 +1016,8 @@ export class NewChatViewPane extends ViewPane {
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IHoverService hoverService: IHoverService,
|
||||
@ISessionsWorkbenchService private readonly activeSessionService: ISessionsWorkbenchService,
|
||||
@ISessionsManagementService private readonly activeSessionService: ISessionsManagementService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
|
||||
@@ -743,12 +1030,12 @@ export class NewChatViewPane extends ViewPane {
|
||||
NewChatWidget,
|
||||
{
|
||||
targetConfig: {
|
||||
allowedTargets: [AgentSessionProviders.Background, AgentSessionProviders.Cloud],
|
||||
defaultTarget: AgentSessionProviders.Background,
|
||||
allowedTargets: this.computeAllowedTargets(),
|
||||
defaultTarget: AgentSessionProviders.Local,
|
||||
},
|
||||
onSendRequest: (data) => {
|
||||
this.activeSessionService.openSessionAndSend(
|
||||
data.resource, data.query, data.sendOptions, data.selectedOptions
|
||||
this.activeSessionService.sendRequestForNewSession(
|
||||
data.resource, data.query, data.sendOptions, data.selectedOptions, data.folderUri
|
||||
).catch(e => this.logService.error('NewChatViewPane: Failed to open session and send request', e));
|
||||
},
|
||||
} satisfies INewChatWidgetOptions,
|
||||
@@ -756,6 +1043,15 @@ export class NewChatViewPane extends ViewPane {
|
||||
|
||||
this._widget.render(container);
|
||||
this._widget.focusInput();
|
||||
|
||||
this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(() => {
|
||||
this._widget?.updateAllowedTargets(this.computeAllowedTargets());
|
||||
}));
|
||||
}
|
||||
|
||||
private computeAllowedTargets(): AgentSessionProviders[] {
|
||||
const targets: AgentSessionProviders[] = [AgentSessionProviders.Local, AgentSessionProviders.Cloud];
|
||||
return targets;
|
||||
}
|
||||
|
||||
protected override layoutBody(height: number, width: number): void {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { IWorkbenchEnvironmentService } from '../../../../workbench/services/env
|
||||
import { IPathService } from '../../../../workbench/services/path/common/pathService.js';
|
||||
import { ISearchService } from '../../../../workbench/services/search/common/search.js';
|
||||
import { IUserDataProfileService } from '../../../../workbench/services/userDataProfile/common/userDataProfile.js';
|
||||
import { ISessionsWorkbenchService } from '../../sessions/browser/sessionsWorkbenchService.js';
|
||||
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
|
||||
|
||||
export class AgenticPromptsService extends PromptsService {
|
||||
protected override createPromptFilesLocator(): PromptFilesLocator {
|
||||
@@ -36,7 +36,7 @@ class AgenticPromptFilesLocator extends PromptFilesLocator {
|
||||
@IUserDataProfileService userDataService: IUserDataProfileService,
|
||||
@ILogService logService: ILogService,
|
||||
@IPathService pathService: IPathService,
|
||||
@ISessionsWorkbenchService private readonly activeSessionService: ISessionsWorkbenchService,
|
||||
@ISessionsManagementService private readonly activeSessionService: ISessionsManagementService,
|
||||
) {
|
||||
super(
|
||||
fileService,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { IQuickInputService } from '../../../../platform/quickinput/common/quick
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
|
||||
import { TerminalLocation } from '../../../../platform/terminal/common/terminal.js';
|
||||
import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js';
|
||||
import { ISessionsWorkbenchService } from '../../sessions/browser/sessionsWorkbenchService.js';
|
||||
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
|
||||
import { ITerminalService } from '../../../../workbench/contrib/terminal/browser/terminal.js';
|
||||
import { Menus } from '../../../browser/menus.js';
|
||||
|
||||
@@ -54,7 +54,7 @@ export class RunScriptContribution extends Disposable implements IWorkbenchContr
|
||||
constructor(
|
||||
@IStorageService private readonly _storageService: IStorageService,
|
||||
@ITerminalService private readonly _terminalService: ITerminalService,
|
||||
@ISessionsWorkbenchService activeSessionService: ISessionsWorkbenchService,
|
||||
@ISessionsManagementService activeSessionService: ISessionsManagementService,
|
||||
@IQuickInputService private readonly _quickInputService: IQuickInputService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -8,7 +8,6 @@ import { Registry } from '../../../../platform/registry/common/platform.js';
|
||||
|
||||
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerDefaultConfigurations([{
|
||||
overrides: {
|
||||
'chat.agentsControl.clickBehavior': 'focus',
|
||||
'chat.agentsControl.enabled': true,
|
||||
'chat.agent.maxRequests': 1000,
|
||||
'chat.restoreLastPanelSession': true,
|
||||
|
||||
@@ -13,8 +13,9 @@ import { ViewPaneContainer } from '../../../../workbench/browser/parts/views/vie
|
||||
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { SessionsTitleBarContribution } from './sessionsTitleBarWidget.js';
|
||||
import { SessionsAuxiliaryBarContribution } from './sessionsAuxiliaryBarContribution.js';
|
||||
import { AgenticSessionsViewPane, SessionsViewId } from './sessionsViewPane.js';
|
||||
import { SessionsWorkbenchService, ISessionsWorkbenchService } from './sessionsWorkbenchService.js';
|
||||
import { SessionsManagementService, ISessionsManagementService } from './sessionsManagementService.js';
|
||||
|
||||
const agentSessionsViewIcon = registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, localize('agentSessionsViewIcon', 'Icon for Agent Sessions View'));
|
||||
const AGENT_SESSIONS_VIEW_TITLE = localize2('agentSessions.view.label', "Sessions");
|
||||
@@ -46,5 +47,6 @@ const agentSessionsViewDescriptor: IViewDescriptor = {
|
||||
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([agentSessionsViewDescriptor], agentSessionsViewContainer);
|
||||
|
||||
registerWorkbenchContribution2(SessionsTitleBarContribution.ID, SessionsTitleBarContribution, WorkbenchPhase.AfterRestored);
|
||||
registerWorkbenchContribution2(SessionsAuxiliaryBarContribution.ID, SessionsAuxiliaryBarContribution, WorkbenchPhase.AfterRestored);
|
||||
|
||||
registerSingleton(ISessionsWorkbenchService, SessionsWorkbenchService, InstantiationType.Delayed);
|
||||
registerSingleton(ISessionsManagementService, SessionsManagementService, InstantiationType.Delayed);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { autorun } from '../../../../base/common/observable.js';
|
||||
import { Disposable, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IWorkbenchLayoutService, Parts } from '../../../../workbench/services/layout/browser/layoutService.js';
|
||||
import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js';
|
||||
import { CHANGES_VIEW_ID, ChangesViewPane } from '../../changesView/browser/changesView.js';
|
||||
|
||||
export class SessionsAuxiliaryBarContribution extends Disposable {
|
||||
|
||||
static readonly ID = 'workbench.contrib.sessionsAuxiliaryBarContribution';
|
||||
|
||||
private readonly activeChangesListener = this._register(new MutableDisposable<IDisposable>());
|
||||
private activeChangesView: ChangesViewPane | null = null;
|
||||
|
||||
constructor(
|
||||
@IViewsService private readonly viewsService: IViewsService,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.tryBindToChangesView();
|
||||
|
||||
this._register(this.viewsService.onDidChangeViewVisibility(e => {
|
||||
if (e.id !== CHANGES_VIEW_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tryBindToChangesView();
|
||||
}));
|
||||
}
|
||||
|
||||
private tryBindToChangesView(): void {
|
||||
const changesView = this.viewsService.getViewWithId<ChangesViewPane>(CHANGES_VIEW_ID);
|
||||
if (!changesView) {
|
||||
this.activeChangesView = null;
|
||||
this.activeChangesListener.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeChangesView === changesView) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeChangesView = changesView;
|
||||
this.activeChangesListener.value = autorun(reader => {
|
||||
const hasChanges = changesView.activeSessionHasChanges.read(reader);
|
||||
this.syncAuxiliaryBarVisibility(hasChanges);
|
||||
});
|
||||
}
|
||||
|
||||
private syncAuxiliaryBarVisibility(hasChanges: boolean): void {
|
||||
const shouldHideAuxiliaryBar = !hasChanges;
|
||||
const isAuxiliaryBarVisible = this.layoutService.isVisible(Parts.AUXILIARYBAR_PART);
|
||||
if (shouldHideAuxiliaryBar === !isAuxiliaryBarVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.layoutService.setPartHidden(shouldHideAuxiliaryBar, Parts.AUXILIARYBAR_PART);
|
||||
}
|
||||
}
|
||||
+136
-56
@@ -12,12 +12,17 @@ import { IContextKey, IContextKeyService, RawContextKey } from '../../../../plat
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
|
||||
import { ISessionOpenOptions, openSession as openSessionDefault } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsOpener.js';
|
||||
import { ChatViewPaneTarget, IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js';
|
||||
import { ChatViewId, ChatViewPaneTarget, IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js';
|
||||
import { ChatViewPane } from '../../../../workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.js';
|
||||
import { IChatSessionItem, IChatSessionProviderOptionItem, IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';
|
||||
import { IChatService, IChatSendRequestOptions } from '../../../../workbench/contrib/chat/common/chatService/chatService.js';
|
||||
import { ChatAgentLocation } from '../../../../workbench/contrib/chat/common/constants.js';
|
||||
import { IAgentSession } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
|
||||
import { IAgentSession, isAgentSession } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
|
||||
import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';
|
||||
import { LocalChatSessionUri } from '../../../../workbench/contrib/chat/common/model/chatUri.js';
|
||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { IWorkspaceEditingService } from '../../../../workbench/services/workspaces/common/workspaceEditing.js';
|
||||
import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js';
|
||||
|
||||
export const IsNewChatSessionContext = new RawContextKey<boolean>('isNewChatSession', true);
|
||||
|
||||
@@ -43,7 +48,7 @@ export type IActiveSessionItem = (IChatSessionItem | IAgentSession) & {
|
||||
readonly worktree: URI | undefined;
|
||||
};
|
||||
|
||||
export interface ISessionsWorkbenchService {
|
||||
export interface ISessionsManagementService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
@@ -62,22 +67,22 @@ export interface ISessionsWorkbenchService {
|
||||
*/
|
||||
openSession(sessionResource: URI, openOptions?: ISessionOpenOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Open a new session, apply options, and send the initial request.
|
||||
* This is the main entry point for the new-chat welcome widget.
|
||||
*/
|
||||
openSessionAndSend(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, selectedOptions?: ReadonlyMap<string, IChatSessionProviderOptionItem>): Promise<void>;
|
||||
|
||||
/**
|
||||
* Switch to the new-session view.
|
||||
* No-op if the current session is already a new session.
|
||||
*/
|
||||
openNewSession(): void;
|
||||
|
||||
/**
|
||||
* Open a new session, apply options, and send the initial request.
|
||||
* This is the main entry point for the new-chat welcome widget.
|
||||
*/
|
||||
sendRequestForNewSession(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, selectedOptions?: ReadonlyMap<string, IChatSessionProviderOptionItem>, folderUri?: URI): Promise<void>;
|
||||
}
|
||||
|
||||
export const ISessionsWorkbenchService = createDecorator<ISessionsWorkbenchService>('sessionsWorkbenchService');
|
||||
export const ISessionsManagementService = createDecorator<ISessionsManagementService>('sessionsManagementService');
|
||||
|
||||
export class SessionsWorkbenchService extends Disposable implements ISessionsWorkbenchService {
|
||||
export class SessionsManagementService extends Disposable implements ISessionsManagementService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
@@ -96,6 +101,9 @@ export class SessionsWorkbenchService extends Disposable implements ISessionsWor
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService,
|
||||
@IViewsService private readonly viewsService: IViewsService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -107,9 +115,7 @@ export class SessionsWorkbenchService extends Disposable implements ISessionsWor
|
||||
this.lastSelectedSession = this.loadLastSelectedSession();
|
||||
|
||||
// Save on shutdown
|
||||
this._register(this.storageService.onWillSaveState(() => {
|
||||
this.saveLastSelectedSession();
|
||||
}));
|
||||
this._register(this.storageService.onWillSaveState(() => this.saveLastSelectedSession()));
|
||||
|
||||
// Update active session when session options change
|
||||
this._register(this.chatSessionsService.onDidChangeSessionOptions(sessionResource => {
|
||||
@@ -124,9 +130,7 @@ export class SessionsWorkbenchService extends Disposable implements ISessionsWor
|
||||
}));
|
||||
|
||||
// Update active session when the agent sessions model changes (e.g., metadata updates with worktree/repository info)
|
||||
this._register(this.agentSessionsService.model.onDidChangeSessions(() => {
|
||||
this.refreshActiveSessionFromModel();
|
||||
}));
|
||||
this._register(this.agentSessionsService.model.onDidChangeSessions(() => this.refreshActiveSessionFromModel()));
|
||||
}
|
||||
|
||||
private refreshActiveSessionFromModel(): void {
|
||||
@@ -137,6 +141,12 @@ export class SessionsWorkbenchService extends Disposable implements ISessionsWor
|
||||
|
||||
const agentSession = this.agentSessionsService.model.getSession(currentActive.resource);
|
||||
if (!agentSession) {
|
||||
// Only switch sessions if the active session was a known agent session
|
||||
// that got deleted. New session resources that aren't yet in the model
|
||||
// should not trigger a switch.
|
||||
if (isAgentSession(currentActive)) {
|
||||
this.showNextSession();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -149,6 +159,19 @@ export class SessionsWorkbenchService extends Disposable implements ISessionsWor
|
||||
this._activeSession.set(activeSessionItem, undefined);
|
||||
}
|
||||
|
||||
private showNextSession(): void {
|
||||
const sessions = this.agentSessionsService.model.sessions
|
||||
.filter(s => !s.isArchived())
|
||||
.sort((a, b) => (b.timing.lastRequestEnded ?? b.timing.created) - (a.timing.lastRequestEnded ?? a.timing.created));
|
||||
|
||||
if (sessions.length > 0) {
|
||||
this.setActiveSession(sessions[0]);
|
||||
this.instantiationService.invokeFunction(openSessionDefault, sessions[0]);
|
||||
} else {
|
||||
this.openNewSession();
|
||||
}
|
||||
}
|
||||
|
||||
private getRepositoryFromMetadata(metadata: { readonly [key: string]: unknown } | undefined): [URI | undefined, URI | undefined] {
|
||||
if (!metadata) {
|
||||
return [undefined, undefined];
|
||||
@@ -189,44 +212,106 @@ export class SessionsWorkbenchService extends Disposable implements ISessionsWor
|
||||
}
|
||||
|
||||
async openSession(sessionResource: URI, openOptions?: ISessionOpenOptions): Promise<void> {
|
||||
const session = this.agentSessionsService.model.getSession(sessionResource);
|
||||
if (session) {
|
||||
this.isNewChatSessionContext.set(false);
|
||||
this.setActiveSession(session);
|
||||
await this.instantiationService.invokeFunction(openSessionDefault, session, openOptions);
|
||||
this.isNewChatSessionContext.set(false);
|
||||
|
||||
const existingSession = this.agentSessionsService.model.getSession(sessionResource);
|
||||
if (existingSession) {
|
||||
await this.openExistingSession(existingSession, openOptions);
|
||||
} else if (LocalChatSessionUri.isLocalSession(sessionResource)) {
|
||||
await this.openLocalSession();
|
||||
} else {
|
||||
// For new sessions, load via the chat service first so the model
|
||||
// is ready before the ChatViewPane renders it.
|
||||
const modelRef = await this.chatService.loadSessionForResource(sessionResource, ChatAgentLocation.Chat, CancellationToken.None);
|
||||
// Switch view only after the model is loaded so the ChatViewPane
|
||||
// has content immediately when it becomes visible.
|
||||
this.isNewChatSessionContext.set(false);
|
||||
const chatWidget = await this.chatWidgetService.openSession(sessionResource, ChatViewPaneTarget);
|
||||
if (!chatWidget?.viewModel) {
|
||||
this.logService.warn(`[ActiveSessionService] Failed to open session: ${sessionResource.toString()}`);
|
||||
modelRef?.dispose();
|
||||
return;
|
||||
}
|
||||
const repository = this.getRepositoryFromSessionOption(sessionResource);
|
||||
const activeSessionItem: IActiveSessionItem = {
|
||||
resource: sessionResource,
|
||||
label: chatWidget.viewModel.model.title || '',
|
||||
timing: chatWidget.viewModel.model.timing,
|
||||
repository,
|
||||
worktree: undefined
|
||||
};
|
||||
this.logService.info(`[ActiveSessionService] Active session changed (new): ${sessionResource.toString()}, repository: ${repository?.toString() ?? 'none'}`);
|
||||
this._activeSession.set(activeSessionItem, undefined);
|
||||
await this.openNewRemoteSession(sessionResource);
|
||||
}
|
||||
}
|
||||
|
||||
async openSessionAndSend(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, selectedOptions?: ReadonlyMap<string, IChatSessionProviderOptionItem>): Promise<void> {
|
||||
// 1. Open the session in ChatViewPane - this transitions views,
|
||||
// loads the model, and connects it to the ChatWidget so
|
||||
// tool invocations work.
|
||||
/**
|
||||
* Open an existing agent session - set it as active and reveal it.
|
||||
*/
|
||||
private async openExistingSession(session: IAgentSession, openOptions?: ISessionOpenOptions): Promise<void> {
|
||||
this.setActiveSession(session);
|
||||
await this.instantiationService.invokeFunction(openSessionDefault, session, openOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a fresh local chat session - show the ChatViewPane and clear the widget.
|
||||
*/
|
||||
private async openLocalSession(): Promise<void> {
|
||||
const view = await this.viewsService.openView(ChatViewId) as ChatViewPane | undefined;
|
||||
if (view) {
|
||||
await view.widget.clear();
|
||||
if (view.widget.viewModel) {
|
||||
const folder = this.workspaceContextService.getWorkspace().folders[0];
|
||||
const activeSessionItem: IActiveSessionItem = {
|
||||
resource: view.widget.viewModel.sessionResource,
|
||||
label: view.widget.viewModel.model.title || '',
|
||||
timing: view.widget.viewModel.model.timing,
|
||||
repository: folder?.uri,
|
||||
worktree: undefined
|
||||
};
|
||||
this._activeSession.set(activeSessionItem, undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new remote session - load the model first, then show it in the ChatViewPane.
|
||||
*/
|
||||
private async openNewRemoteSession(sessionResource: URI): Promise<void> {
|
||||
const modelRef = await this.chatService.loadSessionForResource(sessionResource, ChatAgentLocation.Chat, CancellationToken.None);
|
||||
const chatWidget = await this.chatWidgetService.openSession(sessionResource, ChatViewPaneTarget);
|
||||
if (!chatWidget?.viewModel) {
|
||||
this.logService.warn(`[ActiveSessionService] Failed to open session: ${sessionResource.toString()}`);
|
||||
modelRef?.dispose();
|
||||
return;
|
||||
}
|
||||
const repository = this.getRepositoryFromSessionOption(sessionResource);
|
||||
const activeSessionItem: IActiveSessionItem = {
|
||||
resource: sessionResource,
|
||||
label: chatWidget.viewModel.model.title || '',
|
||||
timing: chatWidget.viewModel.model.timing,
|
||||
repository,
|
||||
worktree: undefined
|
||||
};
|
||||
this.logService.info(`[ActiveSessionService] Active session changed (new): ${sessionResource.toString()}, repository: ${repository?.toString() ?? 'none'}`);
|
||||
this._activeSession.set(activeSessionItem, undefined);
|
||||
}
|
||||
|
||||
async sendRequestForNewSession(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, selectedOptions?: ReadonlyMap<string, IChatSessionProviderOptionItem>, folderUri?: URI): Promise<void> {
|
||||
if (LocalChatSessionUri.isLocalSession(sessionResource)) {
|
||||
await this.sendLocalSession(sessionResource, query, folderUri);
|
||||
} else {
|
||||
await this.sendCustomSession(sessionResource, query, sendOptions, selectedOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Local sessions run directly through the ChatWidget.
|
||||
* Set the workspace folder, open a fresh chat view, and submit via acceptInput.
|
||||
*/
|
||||
private async sendLocalSession(sessionResource: URI, query: string, folderUri?: URI): Promise<void> {
|
||||
if (folderUri) {
|
||||
await this.workspaceEditingService.updateFolders(0, this.workspaceContextService.getWorkspace().folders.length, [{ uri: folderUri }]);
|
||||
}
|
||||
|
||||
await this.openSession(sessionResource);
|
||||
|
||||
// 2. Apply selected options to the contributed session
|
||||
const widget = this.chatWidgetService.lastFocusedWidget;
|
||||
if (widget) {
|
||||
widget.setInput(query);
|
||||
widget.acceptInput(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom sessions (worktree, cloud, etc.) go through the chat service.
|
||||
* Apply selected options, send the request, then wait for the extension
|
||||
* to create an agent session so it appears in the sidebar.
|
||||
*/
|
||||
private async sendCustomSession(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, selectedOptions?: ReadonlyMap<string, IChatSessionProviderOptionItem>): Promise<void> {
|
||||
// 1. Open the session - loads the model and shows the ChatViewPane
|
||||
await this.openSession(sessionResource);
|
||||
|
||||
// 2. Apply selected options (repository, branch, etc.) to the contributed session
|
||||
if (selectedOptions && selectedOptions.size > 0) {
|
||||
const modelRef = this.chatService.getActiveSessionReference(sessionResource);
|
||||
if (modelRef) {
|
||||
@@ -245,22 +330,17 @@ export class SessionsWorkbenchService extends Disposable implements ISessionsWor
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Snapshot existing session resources so we can detect the new one
|
||||
// 3. Send the request
|
||||
const existingResources = new Set(
|
||||
this.agentSessionsService.model.sessions.map(s => s.resource.toString())
|
||||
);
|
||||
|
||||
// 4. Send the request through the chat service - the model is now
|
||||
// connected to the ChatWidget, so tools and rendering work.
|
||||
const result = await this.chatService.sendRequest(sessionResource, query, sendOptions);
|
||||
if (result.kind === 'rejected') {
|
||||
this.logService.error(`[ActiveSessionService] sendRequest rejected: ${result.reason}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. After send, the extension creates an agent session. Wait for it
|
||||
// and set it as the active session so the titlebar and sidebar
|
||||
// reflect the new session.
|
||||
// 4. Wait for the extension to create an agent session, then set it as active
|
||||
let newSession = this.agentSessionsService.model.sessions.find(
|
||||
s => !existingResources.has(s.resource.toString())
|
||||
);
|
||||
@@ -18,7 +18,7 @@ import { IWorkbenchContribution } from '../../../../workbench/common/contributio
|
||||
import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';
|
||||
import { ISessionsWorkbenchService } from './sessionsWorkbenchService.js';
|
||||
import { ISessionsManagementService } from './sessionsManagementService.js';
|
||||
import { FocusAgentSessionsAction } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsActions.js';
|
||||
import { AgentSessionsPicker } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsPicker.js';
|
||||
import { autorun } from '../../../../base/common/observable.js';
|
||||
@@ -60,7 +60,7 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
|
||||
options: IBaseActionViewItemOptions | undefined,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IHoverService private readonly hoverService: IHoverService,
|
||||
@ISessionsWorkbenchService private readonly activeSessionService: ISessionsWorkbenchService,
|
||||
@ISessionsManagementService private readonly activeSessionService: ISessionsManagementService,
|
||||
@IChatService private readonly chatService: IChatService,
|
||||
@IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@@ -344,7 +344,9 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
|
||||
}
|
||||
|
||||
private _showSessionsPicker(): void {
|
||||
const picker = this.instantiationService.createInstance(AgentSessionsPicker, undefined);
|
||||
const picker = this.instantiationService.createInstance(AgentSessionsPicker, undefined, {
|
||||
overrideSessionOpen: (session, openOptions) => this.activeSessionService.openSession(session.resource, openOptions)
|
||||
});
|
||||
picker.pickAgentSession();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { IHoverService } from '../../../../platform/hover/browser/hover.js';
|
||||
import { localize, localize2 } from '../../../../nls.js';
|
||||
import { AgentSessionsControl } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsControl.js';
|
||||
import { AgentSessionsFilter, AgentSessionsGrouping } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsFilter.js';
|
||||
import { ISessionsWorkbenchService } from './sessionsWorkbenchService.js';
|
||||
import { ISessionsManagementService } from './sessionsManagementService.js';
|
||||
import { Action2, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||
import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';
|
||||
import { IWorkbenchLayoutService } from '../../../../workbench/services/layout/browser/layoutService.js';
|
||||
@@ -73,7 +73,6 @@ const CUSTOMIZATIONS_COLLAPSED_KEY = 'agentSessions.customizationsCollapsed';
|
||||
export class AgenticSessionsViewPane extends ViewPane {
|
||||
|
||||
private viewPaneContainer: HTMLElement | undefined;
|
||||
private newSessionButtonContainer: HTMLElement | undefined;
|
||||
private sessionsControlContainer: HTMLElement | undefined;
|
||||
sessionsControl: AgentSessionsControl | undefined;
|
||||
private aiCustomizationContainer: HTMLElement | undefined;
|
||||
@@ -98,7 +97,7 @@ export class AgenticSessionsViewPane extends ViewPane {
|
||||
@IMcpService private readonly mcpService: IMcpService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@ISessionsWorkbenchService private readonly activeSessionService: ISessionsWorkbenchService,
|
||||
@ISessionsManagementService private readonly activeSessionService: ISessionsManagementService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
|
||||
|
||||
@@ -156,7 +155,7 @@ export class AgenticSessionsViewPane extends ViewPane {
|
||||
const sessionsContent = DOM.append(sessionsSection, $('.agent-sessions-content'));
|
||||
|
||||
// New Session Button
|
||||
const newSessionButtonContainer = this.newSessionButtonContainer = DOM.append(sessionsContent, $('.agent-sessions-new-button-container'));
|
||||
const newSessionButtonContainer = DOM.append(sessionsContent, $('.agent-sessions-new-button-container'));
|
||||
const newSessionButton = this._register(new Button(newSessionButtonContainer, { ...defaultButtonStyles, secondary: true }));
|
||||
newSessionButton.label = localize('newSession', "New Session");
|
||||
this._register(newSessionButton.onDidClick(() => this.activeSessionService.openNewSession()));
|
||||
@@ -195,6 +194,8 @@ export class AgenticSessionsViewPane extends ViewPane {
|
||||
if (!sessionsControl.reveal(activeSession.resource)) {
|
||||
sessionsControl.clearFocus();
|
||||
}
|
||||
} else {
|
||||
sessionsControl.clearFocus(); // clear selection when a new session is created
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -402,14 +403,11 @@ export class AgenticSessionsViewPane extends ViewPane {
|
||||
protected override layoutBody(height: number, width: number): void {
|
||||
super.layoutBody(height, width);
|
||||
|
||||
if (!this.sessionsControl || !this.newSessionButtonContainer) {
|
||||
if (!this.sessionsControl || !this.sessionsControlContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buttonHeight = this.newSessionButtonContainer.offsetHeight;
|
||||
const customizationHeight = this.aiCustomizationContainer?.offsetHeight || 0;
|
||||
const availableSessionsHeight = height - buttonHeight - customizationHeight;
|
||||
this.sessionsControl.layout(availableSessionsHeight, width);
|
||||
this.sessionsControl.layout(this.sessionsControlContainer.offsetHeight, width);
|
||||
}
|
||||
|
||||
override focus(): void {
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getZoomFactor } from '../../../base/browser/browser.js';
|
||||
import { getWindow, getWindowId } from '../../../base/browser/dom.js';
|
||||
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
|
||||
import { IContextKeyService } from '../../../platform/contextkey/common/contextkey.js';
|
||||
import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js';
|
||||
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
|
||||
import { INativeHostService } from '../../../platform/native/common/native.js';
|
||||
import { IStorageService } from '../../../platform/storage/common/storage.js';
|
||||
import { IThemeService } from '../../../platform/theme/common/themeService.js';
|
||||
import { useWindowControlsOverlay } from '../../../platform/window/common/window.js';
|
||||
import { IHostService } from '../../../workbench/services/host/browser/host.js';
|
||||
import { IWorkbenchLayoutService, Parts } from '../../../workbench/services/layout/browser/layoutService.js';
|
||||
import { IAuxiliaryTitlebarPart } from '../../../workbench/browser/parts/titlebar/titlebarPart.js';
|
||||
import { IEditorGroupsContainer } from '../../../workbench/services/editor/common/editorGroupsService.js';
|
||||
import { CodeWindow, mainWindow } from '../../../base/browser/window.js';
|
||||
import { TitlebarPart, TitleService } from '../../browser/parts/titlebarPart.js';
|
||||
|
||||
export class NativeTitlebarPart extends TitlebarPart {
|
||||
|
||||
private cachedWindowControlStyles: { bgColor: string; fgColor: string } | undefined;
|
||||
private cachedWindowControlHeight: number | undefined;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
targetWindow: CodeWindow,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IHostService hostService: IHostService,
|
||||
@INativeHostService private readonly nativeHostService: INativeHostService,
|
||||
) {
|
||||
super(id, targetWindow, contextMenuService, configurationService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService);
|
||||
}
|
||||
|
||||
override updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
if (this.element) {
|
||||
if (useWindowControlsOverlay(this.configurationService)) {
|
||||
if (
|
||||
!this.cachedWindowControlStyles ||
|
||||
this.cachedWindowControlStyles.bgColor !== this.element.style.backgroundColor ||
|
||||
this.cachedWindowControlStyles.fgColor !== this.element.style.color
|
||||
) {
|
||||
this.nativeHostService.updateWindowControls({
|
||||
targetWindowId: getWindowId(getWindow(this.element)),
|
||||
backgroundColor: this.element.style.backgroundColor,
|
||||
foregroundColor: this.element.style.color
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override layout(width: number, height: number): void {
|
||||
super.layout(width, height);
|
||||
|
||||
if (useWindowControlsOverlay(this.configurationService)) {
|
||||
const newHeight = Math.round(height * getZoomFactor(getWindow(this.element)));
|
||||
if (newHeight !== this.cachedWindowControlHeight) {
|
||||
this.cachedWindowControlHeight = newHeight;
|
||||
this.nativeHostService.updateWindowControls({
|
||||
targetWindowId: getWindowId(getWindow(this.element)),
|
||||
height: newHeight
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MainNativeTitlebarPart extends NativeTitlebarPart {
|
||||
|
||||
constructor(
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IHostService hostService: IHostService,
|
||||
@INativeHostService nativeHostService: INativeHostService,
|
||||
) {
|
||||
super(Parts.TITLEBAR_PART, mainWindow, contextMenuService, configurationService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService);
|
||||
}
|
||||
}
|
||||
|
||||
class AuxiliaryNativeTitlebarPart extends NativeTitlebarPart implements IAuxiliaryTitlebarPart {
|
||||
|
||||
private static COUNTER = 1;
|
||||
|
||||
get height() { return this.minimumHeight; }
|
||||
|
||||
constructor(
|
||||
readonly container: HTMLElement,
|
||||
editorGroupsContainer: IEditorGroupsContainer,
|
||||
private readonly mainTitlebar: TitlebarPart,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IHostService hostService: IHostService,
|
||||
@INativeHostService nativeHostService: INativeHostService,
|
||||
) {
|
||||
const id = AuxiliaryNativeTitlebarPart.COUNTER++;
|
||||
super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), contextMenuService, configurationService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService);
|
||||
}
|
||||
|
||||
override get preventZoom(): boolean {
|
||||
return getZoomFactor(getWindow(this.element)) < 1 || !this.mainTitlebar.hasZoomableElements;
|
||||
}
|
||||
}
|
||||
|
||||
export class NativeTitleService extends TitleService {
|
||||
|
||||
protected override createMainTitlebarPart(): MainNativeTitlebarPart {
|
||||
return this.instantiationService.createInstance(MainNativeTitlebarPart);
|
||||
}
|
||||
|
||||
protected override doCreateAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): AuxiliaryNativeTitlebarPart {
|
||||
return instantiationService.createInstance(AuxiliaryNativeTitlebarPart, container, editorGroupsContainer, this.mainPart);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { InstantiationType, registerSingleton } from '../../platform/instantiation/common/extensions.js';
|
||||
import { ITitleService } from '../../workbench/services/title/browser/titleService.js';
|
||||
import { NativeTitleService } from './parts/titlebarPart.js';
|
||||
|
||||
registerSingleton(ITitleService, NativeTitleService, InstantiationType.Eager);
|
||||
@@ -351,9 +351,9 @@ import '../workbench/contrib/surveys/browser/nps.contribution.js';
|
||||
import '../workbench/contrib/surveys/browser/languageSurveys.contribution.js';
|
||||
|
||||
// Welcome
|
||||
import '../workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.js';
|
||||
import '../workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.contribution.js';
|
||||
import '../workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.js';
|
||||
// import '../workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.js';
|
||||
// import '../workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.contribution.js';
|
||||
// import '../workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.js';
|
||||
import '../workbench/contrib/welcomeViews/common/viewsWelcome.contribution.js';
|
||||
import '../workbench/contrib/welcomeViews/common/newFile.contribution.js';
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import './sessions.common.main.js';
|
||||
//#region --- workbench (agentic desktop main)
|
||||
|
||||
import './electron-browser/sessions.main.js';
|
||||
import './electron-browser/titleService.js';
|
||||
import '../workbench/electron-browser/desktop.contribution.js';
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -264,7 +264,6 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
|
||||
const options: vscode.LanguageModelToolInvocationStreamOptions<any> = {
|
||||
rawInput: context.rawInput,
|
||||
chatRequestId: context.chatRequestId,
|
||||
chatSessionId: context.chatSessionId,
|
||||
chatSessionResource: context.chatSessionResource,
|
||||
chatInteractionId: context.chatInteractionId
|
||||
};
|
||||
|
||||
@@ -151,7 +151,13 @@ export class ExtHostTreeViews extends Disposable implements ExtHostTreeViewsShap
|
||||
dispose: async () => {
|
||||
// Wait for the registration promise to finish before doing the dispose.
|
||||
await registerPromise;
|
||||
this._treeViews.delete(viewId);
|
||||
// Only notify the main thread if this view was not replaced by a new registration.
|
||||
// When an extension disposes a view and immediately re-registers it, the new
|
||||
// registration may have already updated _treeViews before this async dispose runs.
|
||||
if (this._treeViews.get(viewId) === treeView) {
|
||||
this._treeViews.delete(viewId);
|
||||
this._proxy.$disposeTree(viewId);
|
||||
}
|
||||
treeView.dispose();
|
||||
}
|
||||
};
|
||||
@@ -1108,6 +1114,5 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
this._refreshCancellationSource.dispose();
|
||||
|
||||
this._clearAll();
|
||||
this._proxy.$disposeTree(this._viewId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,6 +499,30 @@ suite('ExtHostTreeView', function () {
|
||||
});
|
||||
});
|
||||
|
||||
test('dispose and re-register tree view', async () => {
|
||||
const disposeTreeSpy = sinon.spy(target, '$disposeTree');
|
||||
const registerSpy = sinon.spy(target, '$registerTreeViewDataProvider');
|
||||
|
||||
// Create, dispose, and re-register a tree view with the same id
|
||||
const treeView1 = testObject.createTreeView('reRegisterTreeProvider', { treeDataProvider: aNodeTreeDataProvider() }, extensionsDescription);
|
||||
treeView1.dispose();
|
||||
const treeView2 = testObject.createTreeView('reRegisterTreeProvider', { treeDataProvider: aNodeTreeDataProvider() }, extensionsDescription);
|
||||
|
||||
// Let all pending microtasks (the async dispose) settle
|
||||
await new Promise<void>(r => setTimeout(r, 0));
|
||||
|
||||
// The new view should work — $getChildren should return results, not reject
|
||||
const elements = await testObject.$getChildren('reRegisterTreeProvider');
|
||||
assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:a', '0/0:b']);
|
||||
|
||||
// $registerTreeViewDataProvider should have been called twice (once per createTreeView)
|
||||
assert.strictEqual(registerSpy.callCount, 2);
|
||||
// $disposeTree should NOT have been called — the old async dispose should detect it was replaced
|
||||
assert.strictEqual(disposeTreeSpy.callCount, 0);
|
||||
|
||||
treeView2.dispose();
|
||||
});
|
||||
|
||||
test('reveal will throw an error if getParent is not implemented', () => {
|
||||
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aNodeTreeDataProvider() }, extensionsDescription);
|
||||
return treeView.reveal({ key: 'a' })
|
||||
|
||||
@@ -16,7 +16,6 @@ import { getRemoteName } from '../../platform/remote/common/remoteHosts.js';
|
||||
import { getVirtualWorkspaceScheme } from '../../platform/workspace/common/virtualWorkspace.js';
|
||||
import { IWorkingCopyService } from '../services/workingCopy/common/workingCopyService.js';
|
||||
import { isNative } from '../../base/common/platform.js';
|
||||
import { IPaneCompositePartService } from '../services/panecomposite/browser/panecomposite.js';
|
||||
import { WebFileSystemAccess } from '../../platform/files/browser/webFileSystemAccess.js';
|
||||
import { IProductService } from '../../platform/product/common/productService.js';
|
||||
import { getTitleBarStyle } from '../../platform/window/common/window.js';
|
||||
@@ -75,7 +74,6 @@ export class WorkbenchContextKeysHandler extends Disposable {
|
||||
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||
@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,
|
||||
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService,
|
||||
) {
|
||||
super();
|
||||
@@ -180,6 +178,7 @@ export class WorkbenchContextKeysHandler extends Disposable {
|
||||
|
||||
// Sidebar
|
||||
this.sideBarVisibleContext = SideBarVisibleContext.bindTo(this.contextKeyService);
|
||||
this.sideBarVisibleContext.set(this.layoutService.isVisible(Parts.SIDEBAR_PART));
|
||||
|
||||
// Title Bar
|
||||
this.titleAreaVisibleContext = TitleBarVisibleContext.bindTo(this.contextKeyService);
|
||||
@@ -245,14 +244,12 @@ export class WorkbenchContextKeysHandler extends Disposable {
|
||||
|
||||
this._register(this.layoutService.onDidChangePanelAlignment(alignment => this.panelAlignmentContext.set(alignment)));
|
||||
|
||||
this._register(this.paneCompositeService.onDidPaneCompositeClose(() => this.updateSideBarContextKeys()));
|
||||
this._register(this.paneCompositeService.onDidPaneCompositeOpen(() => this.updateSideBarContextKeys()));
|
||||
|
||||
this._register(this.layoutService.onDidChangePartVisibility(() => {
|
||||
this.mainEditorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART, mainWindow));
|
||||
this.panelVisibleContext.set(this.layoutService.isVisible(Parts.PANEL_PART));
|
||||
this.panelMaximizedContext.set(this.layoutService.isPanelMaximized());
|
||||
this.auxiliaryBarVisibleContext.set(this.layoutService.isVisible(Parts.AUXILIARYBAR_PART));
|
||||
this.sideBarVisibleContext.set(this.layoutService.isVisible(Parts.SIDEBAR_PART));
|
||||
|
||||
this.updateTitleBarContextKeys();
|
||||
}));
|
||||
@@ -326,10 +323,6 @@ export class WorkbenchContextKeysHandler extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private updateSideBarContextKeys(): void {
|
||||
this.sideBarVisibleContext.set(this.layoutService.isVisible(Parts.SIDEBAR_PART));
|
||||
}
|
||||
|
||||
private updateTitleBarContextKeys(): void {
|
||||
this.titleAreaVisibleContext.set(this.layoutService.isVisible(Parts.TITLEBAR_PART, mainWindow));
|
||||
this.titleBarStyleContext.set(getTitleBarStyle(this.configurationService));
|
||||
|
||||
@@ -78,8 +78,8 @@ export class ModalEditorPart {
|
||||
if (e.target === modalElement) {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
// Guide focus back into the modal when clicking outside modal
|
||||
editorPartContainer.focus();
|
||||
// Close modal when clicking outside the dialog
|
||||
editorPart.close();
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -68,11 +68,6 @@ export interface IBrowserViewWorkbenchService {
|
||||
* Clear all storage data for the current workspace browser session
|
||||
*/
|
||||
clearWorkspaceStorage(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get the endpoint for connecting to a browser view's CDP proxy server
|
||||
*/
|
||||
getDebugWebSocketEndpoint(): Promise<string>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ export const CONTEXT_BROWSER_CAN_GO_FORWARD = new RawContextKey<boolean>('browse
|
||||
export const CONTEXT_BROWSER_FOCUSED = new RawContextKey<boolean>('browserFocused', true, localize('browser.editorFocused', "Whether the browser editor is focused"));
|
||||
export const CONTEXT_BROWSER_STORAGE_SCOPE = new RawContextKey<string>('browserStorageScope', '', localize('browser.storageScope', "The storage scope of the current browser view"));
|
||||
export const CONTEXT_BROWSER_HAS_URL = new RawContextKey<boolean>('browserHasUrl', false, localize('browser.hasUrl', "Whether the browser has a URL loaded"));
|
||||
export const CONTEXT_BROWSER_HAS_ERROR = new RawContextKey<boolean>('browserHasError', false, localize('browser.hasError', "Whether the browser has a load error"));
|
||||
export const CONTEXT_BROWSER_DEVTOOLS_OPEN = new RawContextKey<boolean>('browserDevToolsOpen', false, localize('browser.devToolsOpen', "Whether developer tools are open for the current browser view"));
|
||||
export const CONTEXT_BROWSER_ELEMENT_SELECTION_ACTIVE = new RawContextKey<boolean>('browserElementSelectionActive', false, localize('browser.elementSelectionActive', "Whether element selection is currently active"));
|
||||
|
||||
@@ -188,6 +189,7 @@ export class BrowserEditor extends EditorPane {
|
||||
private _canGoForwardContext!: IContextKey<boolean>;
|
||||
private _storageScopeContext!: IContextKey<string>;
|
||||
private _hasUrlContext!: IContextKey<boolean>;
|
||||
private _hasErrorContext!: IContextKey<boolean>;
|
||||
private _devToolsOpenContext!: IContextKey<boolean>;
|
||||
private _elementSelectionActiveContext!: IContextKey<boolean>;
|
||||
|
||||
@@ -195,6 +197,7 @@ export class BrowserEditor extends EditorPane {
|
||||
private readonly _inputDisposables = this._register(new DisposableStore());
|
||||
private overlayManager: BrowserOverlayManager | undefined;
|
||||
private _elementSelectionCts: CancellationTokenSource | undefined;
|
||||
private _consoleSessionCts: CancellationTokenSource | undefined;
|
||||
private _screenshotTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
constructor(
|
||||
@@ -226,6 +229,7 @@ export class BrowserEditor extends EditorPane {
|
||||
this._canGoForwardContext = CONTEXT_BROWSER_CAN_GO_FORWARD.bindTo(contextKeyService);
|
||||
this._storageScopeContext = CONTEXT_BROWSER_STORAGE_SCOPE.bindTo(contextKeyService);
|
||||
this._hasUrlContext = CONTEXT_BROWSER_HAS_URL.bindTo(contextKeyService);
|
||||
this._hasErrorContext = CONTEXT_BROWSER_HAS_ERROR.bindTo(contextKeyService);
|
||||
this._devToolsOpenContext = CONTEXT_BROWSER_DEVTOOLS_OPEN.bindTo(contextKeyService);
|
||||
this._elementSelectionActiveContext = CONTEXT_BROWSER_ELEMENT_SELECTION_ACTIVE.bindTo(contextKeyService);
|
||||
|
||||
@@ -367,6 +371,12 @@ export class BrowserEditor extends EditorPane {
|
||||
|
||||
// Update navigation bar and context keys from model
|
||||
this.updateNavigationState(navEvent);
|
||||
|
||||
if (navEvent.url) {
|
||||
this.startConsoleSession();
|
||||
} else {
|
||||
this.stopConsoleSession();
|
||||
}
|
||||
}));
|
||||
|
||||
this._inputDisposables.add(this._model.onDidChangeLoadingState(() => {
|
||||
@@ -519,6 +529,7 @@ export class BrowserEditor extends EditorPane {
|
||||
}
|
||||
|
||||
const error: IBrowserViewLoadError | undefined = this._model.error;
|
||||
this._hasErrorContext.set(!!error);
|
||||
if (error) {
|
||||
// Update error content
|
||||
|
||||
@@ -759,6 +770,66 @@ export class BrowserEditor extends EditorPane {
|
||||
}
|
||||
}
|
||||
|
||||
async addConsoleLogsToChat(): Promise<void> {
|
||||
const resourceUri = this.input?.resource;
|
||||
if (!resourceUri) {
|
||||
return;
|
||||
}
|
||||
|
||||
const locator: IBrowserTargetLocator = { browserViewId: BrowserViewUri.getId(resourceUri) };
|
||||
|
||||
try {
|
||||
const logs = await this.browserElementsService.getConsoleLogs(locator);
|
||||
if (!logs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toAttach: IChatRequestVariableEntry[] = [];
|
||||
toAttach.push({
|
||||
id: 'console-logs-' + Date.now(),
|
||||
name: localize('consoleLogs', 'Console Logs'),
|
||||
fullName: localize('consoleLogs', 'Console Logs'),
|
||||
value: logs,
|
||||
kind: 'element',
|
||||
icon: ThemeIcon.fromId(Codicon.output.id),
|
||||
});
|
||||
|
||||
const widget = await this.chatWidgetService.revealWidget() ?? this.chatWidgetService.lastFocusedWidget;
|
||||
widget?.attachmentModel?.addContext(...toAttach);
|
||||
} catch (error) {
|
||||
this.logService.error('BrowserEditor.addConsoleLogsToChat: Failed to get console logs', error);
|
||||
}
|
||||
}
|
||||
|
||||
private startConsoleSession(): void {
|
||||
// don't restart if already running
|
||||
if (this._consoleSessionCts) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resourceUri = this.input?.resource;
|
||||
if (!resourceUri || !this._model?.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cts = new CancellationTokenSource();
|
||||
this._consoleSessionCts = cts;
|
||||
const locator: IBrowserTargetLocator = { browserViewId: BrowserViewUri.getId(resourceUri) };
|
||||
|
||||
this.browserElementsService.startConsoleSession(cts.token, locator).catch(error => {
|
||||
if (!cts.token.isCancellationRequested) {
|
||||
this.logService.error('BrowserEditor: Failed to start console session', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private stopConsoleSession(): void {
|
||||
if (this._consoleSessionCts) {
|
||||
this._consoleSessionCts.dispose(true);
|
||||
this._consoleSessionCts = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update navigation state and context keys
|
||||
*/
|
||||
@@ -907,6 +978,9 @@ export class BrowserEditor extends EditorPane {
|
||||
this._elementSelectionCts = undefined;
|
||||
}
|
||||
|
||||
// Cancel any active console session
|
||||
this.stopConsoleSession();
|
||||
|
||||
// Cancel any scheduled screenshots
|
||||
this.cancelScheduledScreenshot();
|
||||
|
||||
@@ -920,6 +994,7 @@ export class BrowserEditor extends EditorPane {
|
||||
this._canGoBackContext.reset();
|
||||
this._canGoForwardContext.reset();
|
||||
this._hasUrlContext.reset();
|
||||
this._hasErrorContext.reset();
|
||||
this._storageScopeContext.reset();
|
||||
this._devToolsOpenContext.reset();
|
||||
this._elementSelectionActiveContext.reset();
|
||||
|
||||
@@ -11,7 +11,7 @@ import { KeybindingWeight } from '../../../../platform/keybinding/common/keybind
|
||||
import { KeyMod, KeyCode } from '../../../../base/common/keyCodes.js';
|
||||
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { BrowserEditor, CONTEXT_BROWSER_CAN_GO_BACK, CONTEXT_BROWSER_CAN_GO_FORWARD, CONTEXT_BROWSER_DEVTOOLS_OPEN, CONTEXT_BROWSER_FOCUSED, CONTEXT_BROWSER_HAS_URL, CONTEXT_BROWSER_STORAGE_SCOPE, CONTEXT_BROWSER_ELEMENT_SELECTION_ACTIVE, CONTEXT_BROWSER_FIND_WIDGET_FOCUSED, CONTEXT_BROWSER_FIND_WIDGET_VISIBLE } from './browserEditor.js';
|
||||
import { BrowserEditor, CONTEXT_BROWSER_CAN_GO_BACK, CONTEXT_BROWSER_CAN_GO_FORWARD, CONTEXT_BROWSER_DEVTOOLS_OPEN, CONTEXT_BROWSER_FOCUSED, CONTEXT_BROWSER_HAS_ERROR, CONTEXT_BROWSER_HAS_URL, CONTEXT_BROWSER_STORAGE_SCOPE, CONTEXT_BROWSER_ELEMENT_SELECTION_ACTIVE, CONTEXT_BROWSER_FIND_WIDGET_FOCUSED, CONTEXT_BROWSER_FIND_WIDGET_VISIBLE } from './browserEditor.js';
|
||||
import { BrowserViewUri } from '../../../../platform/browserView/common/browserViewUri.js';
|
||||
import { IBrowserViewWorkbenchService } from '../common/browserView.js';
|
||||
import { BrowserViewStorageScope } from '../../../../platform/browserView/common/browserView.js';
|
||||
@@ -55,7 +55,12 @@ class OpenIntegratedBrowserAction extends Action2 {
|
||||
|
||||
logBrowserOpen(telemetryService, options.url ? 'commandWithUrl' : 'commandWithoutUrl');
|
||||
|
||||
await editorService.openEditor({ resource }, group);
|
||||
const editorPane = await editorService.openEditor({ resource }, group);
|
||||
|
||||
// Lock the group when opening to the side
|
||||
if (options.openToSide && editorPane?.group) {
|
||||
editorPane.group.lock(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +228,7 @@ class AddElementToChatAction extends Action2 {
|
||||
category: BrowserCategory,
|
||||
icon: Codicon.inspect,
|
||||
f1: true,
|
||||
precondition: ContextKeyExpr.and(BROWSER_EDITOR_ACTIVE, enabled),
|
||||
precondition: ContextKeyExpr.and(BROWSER_EDITOR_ACTIVE, CONTEXT_BROWSER_HAS_URL, CONTEXT_BROWSER_HAS_ERROR.negate(), enabled),
|
||||
toggled: CONTEXT_BROWSER_ELEMENT_SELECTION_ACTIVE,
|
||||
menu: {
|
||||
id: MenuId.BrowserActionsToolbar,
|
||||
@@ -249,6 +254,34 @@ class AddElementToChatAction extends Action2 {
|
||||
}
|
||||
}
|
||||
|
||||
class AddConsoleLogsToChatAction extends Action2 {
|
||||
static readonly ID = 'workbench.action.browser.addConsoleLogsToChat';
|
||||
|
||||
constructor() {
|
||||
const enabled = ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('config.chat.sendElementsToChat.enabled', true));
|
||||
super({
|
||||
id: AddConsoleLogsToChatAction.ID,
|
||||
title: localize2('browser.addConsoleLogsToChatAction', 'Add Console Logs to Chat'),
|
||||
category: BrowserCategory,
|
||||
icon: Codicon.output,
|
||||
f1: true,
|
||||
precondition: ContextKeyExpr.and(BROWSER_EDITOR_ACTIVE, CONTEXT_BROWSER_HAS_URL, CONTEXT_BROWSER_HAS_ERROR.negate(), enabled),
|
||||
menu: {
|
||||
id: MenuId.BrowserActionsToolbar,
|
||||
group: 'actions',
|
||||
order: 2,
|
||||
when: enabled
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, browserEditor = accessor.get(IEditorService).activeEditorPane): Promise<void> {
|
||||
if (browserEditor instanceof BrowserEditor) {
|
||||
await browserEditor.addConsoleLogsToChat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleDevToolsAction extends Action2 {
|
||||
static readonly ID = 'workbench.action.browser.toggleDevTools';
|
||||
|
||||
@@ -259,12 +292,12 @@ class ToggleDevToolsAction extends Action2 {
|
||||
category: BrowserCategory,
|
||||
icon: Codicon.terminal,
|
||||
f1: true,
|
||||
precondition: ContextKeyExpr.and(BROWSER_EDITOR_ACTIVE, CONTEXT_BROWSER_HAS_URL),
|
||||
precondition: ContextKeyExpr.and(BROWSER_EDITOR_ACTIVE, CONTEXT_BROWSER_HAS_URL, CONTEXT_BROWSER_HAS_ERROR.negate()),
|
||||
toggled: ContextKeyExpr.equals(CONTEXT_BROWSER_DEVTOOLS_OPEN.key, true),
|
||||
menu: {
|
||||
id: MenuId.BrowserActionsToolbar,
|
||||
group: 'actions',
|
||||
order: 2,
|
||||
order: 3,
|
||||
},
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
@@ -290,7 +323,8 @@ class OpenInExternalBrowserAction extends Action2 {
|
||||
category: BrowserCategory,
|
||||
icon: Codicon.linkExternal,
|
||||
f1: true,
|
||||
precondition: BROWSER_EDITOR_ACTIVE,
|
||||
// Note: We do allow opening in an external browser even if there is an error page shown
|
||||
precondition: ContextKeyExpr.and(BROWSER_EDITOR_ACTIVE, CONTEXT_BROWSER_HAS_URL),
|
||||
menu: {
|
||||
id: MenuId.BrowserActionsToolbar,
|
||||
group: ActionGroupPage,
|
||||
@@ -422,7 +456,7 @@ class ShowBrowserFindAction extends Action2 {
|
||||
title: localize2('browser.showFindAction', 'Find in Page'),
|
||||
category: BrowserCategory,
|
||||
f1: true,
|
||||
precondition: BROWSER_EDITOR_ACTIVE,
|
||||
precondition: ContextKeyExpr.and(BROWSER_EDITOR_ACTIVE, CONTEXT_BROWSER_HAS_URL, CONTEXT_BROWSER_HAS_ERROR.negate()),
|
||||
menu: {
|
||||
id: MenuId.BrowserActionsToolbar,
|
||||
group: ActionGroupPage,
|
||||
@@ -537,6 +571,7 @@ registerAction2(GoForwardAction);
|
||||
registerAction2(ReloadAction);
|
||||
registerAction2(FocusUrlInputAction);
|
||||
registerAction2(AddElementToChatAction);
|
||||
registerAction2(AddConsoleLogsToChatAction);
|
||||
registerAction2(ToggleDevToolsAction);
|
||||
registerAction2(OpenInExternalBrowserAction);
|
||||
registerAction2(ClearGlobalBrowserStorageAction);
|
||||
|
||||
@@ -54,8 +54,4 @@ export class BrowserViewWorkbenchService implements IBrowserViewWorkbenchService
|
||||
const workspaceId = this.workspaceContextService.getWorkspace().id;
|
||||
return this._browserViewService.clearWorkspaceStorage(workspaceId);
|
||||
}
|
||||
|
||||
async getDebugWebSocketEndpoint() {
|
||||
return this._browserViewService.getDebugWebSocketEndpoint();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,14 +605,6 @@ export function registerChatActions() {
|
||||
const chatVisible = viewsService.isViewVisible(ChatViewId);
|
||||
const clickBehavior = configurationService.getValue<AgentsControlClickBehavior>(ChatConfiguration.AgentsControlClickBehavior);
|
||||
switch (clickBehavior) {
|
||||
case AgentsControlClickBehavior.Focus:
|
||||
if (chatLocation === ViewContainerLocation.AuxiliaryBar) {
|
||||
layoutService.setAuxiliaryBarMaximized(true);
|
||||
} else {
|
||||
this.updatePartVisibility(layoutService, chatLocation, true);
|
||||
}
|
||||
(await widgetService.revealWidget())?.focusInput();
|
||||
break;
|
||||
case AgentsControlClickBehavior.Cycle:
|
||||
if (chatVisible) {
|
||||
if (
|
||||
|
||||
@@ -129,6 +129,7 @@ class AttachFileToChatAction extends AttachResourceAction {
|
||||
id: AttachFileToChatAction.ID,
|
||||
title: localize2('workbench.action.chat.attachFile.label', "Add File to Chat"),
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.attach,
|
||||
precondition: ChatContextKeys.enabled,
|
||||
f1: true,
|
||||
menu: [{
|
||||
@@ -173,9 +174,14 @@ class AttachFileToChatAction extends AttachResourceAction {
|
||||
)
|
||||
)
|
||||
}, {
|
||||
id: MenuId.ChatEditorInlineGutter,
|
||||
group: '2_chat',
|
||||
order: 2,
|
||||
id: MenuId.InlineChatEditorAffordance,
|
||||
group: '0_chat',
|
||||
order: 3,
|
||||
when: ContextKeyExpr.and(ChatContextKeys.enabled, EditorContextKeys.hasNonEmptySelection.negate())
|
||||
}, {
|
||||
id: MenuId.ChatEditorInlineMenu,
|
||||
group: '0_chat',
|
||||
order: 3,
|
||||
when: ContextKeyExpr.and(ChatContextKeys.enabled, EditorContextKeys.hasNonEmptySelection.negate())
|
||||
}]
|
||||
});
|
||||
@@ -290,6 +296,7 @@ class AttachSelectionToChatAction extends Action2 {
|
||||
id: AttachSelectionToChatAction.ID,
|
||||
title: localize2('workbench.action.chat.attachSelection.label', "Add Selection to Chat"),
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.attach,
|
||||
f1: true,
|
||||
precondition: ChatContextKeys.enabled,
|
||||
menu: [{
|
||||
@@ -307,9 +314,14 @@ class AttachSelectionToChatAction extends Action2 {
|
||||
)
|
||||
)
|
||||
}, {
|
||||
id: MenuId.ChatEditorInlineGutter,
|
||||
group: '2_chat',
|
||||
order: 1,
|
||||
id: MenuId.InlineChatEditorAffordance,
|
||||
group: '0_chat',
|
||||
order: 2,
|
||||
when: ContextKeyExpr.and(ChatContextKeys.enabled, EditorContextKeys.hasNonEmptySelection)
|
||||
}, {
|
||||
id: MenuId.ChatEditorInlineMenu,
|
||||
group: '0_chat',
|
||||
order: 2,
|
||||
when: ContextKeyExpr.and(ChatContextKeys.enabled, EditorContextKeys.hasNonEmptySelection)
|
||||
}]
|
||||
});
|
||||
|
||||
@@ -181,7 +181,7 @@ abstract class SubmitAction extends Action2 {
|
||||
}
|
||||
|
||||
const requestInProgressOrPendingToolCall = ContextKeyExpr.or(ChatContextKeys.requestInProgress, ChatContextKeys.Editing.hasToolConfirmation);
|
||||
const whenNotInProgress = ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), ChatContextKeys.Editing.hasToolConfirmation.negate());
|
||||
const whenNotInProgress = ChatContextKeys.requestInProgress.negate();
|
||||
|
||||
export class ChatSubmitAction extends SubmitAction {
|
||||
static readonly ID = 'workbench.action.chat.submit';
|
||||
|
||||
@@ -13,17 +13,12 @@ import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contex
|
||||
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
|
||||
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
|
||||
import { ChatRequestQueueKind, IChatService } from '../../common/chatService/chatService.js';
|
||||
import { ChatConfiguration } from '../../common/constants.js';
|
||||
import { isRequestVM } from '../../common/model/chatViewModel.js';
|
||||
import { IChatWidgetService } from '../chat.js';
|
||||
import { CHAT_CATEGORY } from './chatActions.js';
|
||||
|
||||
const queueingEnabledCondition = ContextKeyExpr.equals(`config.${ChatConfiguration.RequestQueueingEnabled}`, true);
|
||||
const requestInProgressOrPendingToolCall = ContextKeyExpr.or(ChatContextKeys.requestInProgress, ChatContextKeys.Editing.hasToolConfirmation);
|
||||
|
||||
const queuingActionsPresent = ContextKeyExpr.and(
|
||||
queueingEnabledCondition,
|
||||
ContextKeyExpr.or(requestInProgressOrPendingToolCall, ChatContextKeys.editingRequestType.isEqualTo(ChatContextKeys.EditingRequestType.QueueOrSteer)),
|
||||
ContextKeyExpr.or(ChatContextKeys.requestInProgress, ChatContextKeys.editingRequestType.isEqualTo(ChatContextKeys.EditingRequestType.QueueOrSteer)),
|
||||
ChatContextKeys.editingRequestType.notEqualsTo(ChatContextKeys.EditingRequestType.Sent),
|
||||
);
|
||||
|
||||
@@ -141,7 +136,6 @@ export class ChatRemovePendingRequestAction extends Action2 {
|
||||
group: 'navigation',
|
||||
order: 4,
|
||||
when: ContextKeyExpr.and(
|
||||
queueingEnabledCondition,
|
||||
ChatContextKeys.isRequest,
|
||||
ChatContextKeys.isPendingRequest
|
||||
)
|
||||
@@ -181,7 +175,6 @@ export class ChatSendPendingImmediatelyAction extends Action2 {
|
||||
group: 'navigation',
|
||||
order: 3,
|
||||
when: ContextKeyExpr.and(
|
||||
queueingEnabledCondition,
|
||||
ChatContextKeys.isRequest,
|
||||
ChatContextKeys.isPendingRequest
|
||||
)
|
||||
@@ -239,11 +232,8 @@ export class ChatRemoveAllPendingRequestsAction extends Action2 {
|
||||
id: MenuId.ChatContext,
|
||||
group: 'navigation',
|
||||
order: 3,
|
||||
when: ContextKeyExpr.and(
|
||||
queueingEnabledCondition,
|
||||
ChatContextKeys.hasPendingRequests
|
||||
)
|
||||
}]
|
||||
when: ChatContextKeys.hasPendingRequests,
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ export class PickAgentSessionAction extends Action2 {
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
|
||||
const agentSessionsPicker = instantiationService.createInstance(AgentSessionsPicker, undefined);
|
||||
const agentSessionsPicker = instantiationService.createInstance(AgentSessionsPicker, undefined, undefined);
|
||||
await agentSessionsPicker.pickAgentSession();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { localize } from '../../../../../nls.js';
|
||||
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
|
||||
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../../platform/quickinput/common/quickInput.js';
|
||||
import { openSession } from './agentSessionsOpener.js';
|
||||
import { ISessionOpenOptions, openSession } from './agentSessionsOpener.js';
|
||||
import { IAgentSession, isLocalAgentSessionItem } from './agentSessionsModel.js';
|
||||
import { IAgentSessionsService } from './agentSessionsService.js';
|
||||
import { AgentSessionsSorter, groupAgentSessionsByDate, sessionDateFromNow } from './agentSessionsViewer.js';
|
||||
@@ -62,12 +62,17 @@ export function getSessionButtons(session: IAgentSession): IQuickInputButton[] {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
export interface IAgentSessionsPickerOptions {
|
||||
overrideSessionOpen?(session: IAgentSession, openOptions?: ISessionOpenOptions): Promise<void>;
|
||||
}
|
||||
|
||||
export class AgentSessionsPicker {
|
||||
|
||||
private readonly sorter = new AgentSessionsSorter();
|
||||
|
||||
constructor(
|
||||
private readonly anchor: HTMLElement | undefined,
|
||||
private readonly options: IAgentSessionsPickerOptions | undefined,
|
||||
@IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@@ -87,13 +92,19 @@ export class AgentSessionsPicker {
|
||||
disposables.add(picker.onDidAccept(e => {
|
||||
const pick = picker.selectedItems[0];
|
||||
if (pick) {
|
||||
this.instantiationService.invokeFunction(openSession, pick.session, {
|
||||
const openOptions: ISessionOpenOptions = {
|
||||
sideBySide: e.inBackground,
|
||||
editorOptions: {
|
||||
preserveFocus: e.inBackground,
|
||||
pinned: e.inBackground
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (this.options?.overrideSessionOpen) {
|
||||
this.options.overrideSessionOpen(pick.session, openOptions);
|
||||
} else {
|
||||
this.instantiationService.invokeFunction(openSession, pick.session, openOptions);
|
||||
}
|
||||
}
|
||||
|
||||
if (!e.inBackground) {
|
||||
|
||||
@@ -758,13 +758,19 @@ export function groupAgentSessionsByDate(sessions: IAgentSession[]): Map<AgentSe
|
||||
}
|
||||
}
|
||||
|
||||
const sectionWithCount = (section: AgentSessionSection, sessions: IAgentSession[]) => ({
|
||||
section,
|
||||
label: localize('agentSessions.sectionWithCount', "{0} ({1})", AgentSessionSectionLabels[section], sessions.length),
|
||||
sessions
|
||||
});
|
||||
|
||||
return new Map<AgentSessionSection, IAgentSessionSection>([
|
||||
[AgentSessionSection.InProgress, { section: AgentSessionSection.InProgress, label: AgentSessionSectionLabels[AgentSessionSection.InProgress], sessions: inProgressSessions }],
|
||||
[AgentSessionSection.Today, { section: AgentSessionSection.Today, label: AgentSessionSectionLabels[AgentSessionSection.Today], sessions: todaySessions }],
|
||||
[AgentSessionSection.Yesterday, { section: AgentSessionSection.Yesterday, label: AgentSessionSectionLabels[AgentSessionSection.Yesterday], sessions: yesterdaySessions }],
|
||||
[AgentSessionSection.Week, { section: AgentSessionSection.Week, label: AgentSessionSectionLabels[AgentSessionSection.Week], sessions: weekSessions }],
|
||||
[AgentSessionSection.Older, { section: AgentSessionSection.Older, label: AgentSessionSectionLabels[AgentSessionSection.Older], sessions: olderSessions }],
|
||||
[AgentSessionSection.Archived, { section: AgentSessionSection.Archived, label: localize('agentSessions.archivedSectionWithCount', "Archived ({0})", archivedSessions.length), sessions: archivedSessions }],
|
||||
[AgentSessionSection.InProgress, sectionWithCount(AgentSessionSection.InProgress, inProgressSessions)],
|
||||
[AgentSessionSection.Today, sectionWithCount(AgentSessionSection.Today, todaySessions)],
|
||||
[AgentSessionSection.Yesterday, sectionWithCount(AgentSessionSection.Yesterday, yesterdaySessions)],
|
||||
[AgentSessionSection.Week, sectionWithCount(AgentSessionSection.Week, weekSessions)],
|
||||
[AgentSessionSection.Older, sectionWithCount(AgentSessionSection.Older, olderSessions)],
|
||||
[AgentSessionSection.Archived, sectionWithCount(AgentSessionSection.Archived, archivedSessions)],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -204,11 +204,10 @@ configurationRegistry.registerConfiguration({
|
||||
},
|
||||
[ChatConfiguration.AgentsControlClickBehavior]: {
|
||||
type: 'string',
|
||||
enum: [AgentsControlClickBehavior.Default, AgentsControlClickBehavior.Cycle, AgentsControlClickBehavior.Focus],
|
||||
enum: [AgentsControlClickBehavior.Default, AgentsControlClickBehavior.Cycle],
|
||||
enumDescriptions: [
|
||||
nls.localize('chat.agentsControl.clickBehavior.default', "Clicking chat icon toggles chat visibility."),
|
||||
nls.localize('chat.agentsControl.clickBehavior.cycle', "Clicking chat icon cycles through: show chat, maximize chat, hide chat. This requires chat to be contained in the secondary sidebar."),
|
||||
nls.localize('chat.agentsControl.clickBehavior.focus', "Clicking chat icon focuses the chat view and maximizes it if located in the secondary sidebar.")
|
||||
],
|
||||
markdownDescription: nls.localize('chat.agentsControl.clickBehavior', "Controls the behavior when clicking on the chat icon in the command center."),
|
||||
default: product.quality !== 'stable' ? AgentsControlClickBehavior.Cycle : AgentsControlClickBehavior.Default,
|
||||
@@ -644,12 +643,6 @@ configurationRegistry.registerConfiguration({
|
||||
enumItemLabels: ExploreAgentDefaultModel.modelLabels,
|
||||
markdownEnumDescriptions: ExploreAgentDefaultModel.modelDescriptions
|
||||
},
|
||||
[ChatConfiguration.RequestQueueingEnabled]: {
|
||||
type: 'boolean',
|
||||
description: nls.localize('chat.requestQueuing.enabled.description', "When enabled, allows queuing additional messages while a request is in progress and steering the current request with a new message."),
|
||||
default: true,
|
||||
tags: ['experimental'],
|
||||
},
|
||||
[ChatConfiguration.RequestQueueingDefaultAction]: {
|
||||
type: 'string',
|
||||
enum: ['queue', 'steer'],
|
||||
@@ -689,15 +682,10 @@ configurationRegistry.registerConfiguration({
|
||||
default: true,
|
||||
tags: ['experimental'],
|
||||
},
|
||||
['chat.statusWidget.sku']: {
|
||||
type: 'string',
|
||||
enum: ['free', 'anonymous'],
|
||||
enumDescriptions: [
|
||||
nls.localize('chat.statusWidget.sku.free', "Show status widget for free tier users."),
|
||||
nls.localize('chat.statusWidget.sku.anonymous', "Show status widget for anonymous users.")
|
||||
],
|
||||
description: nls.localize('chat.statusWidget.enabled.description', "Controls which user type should see the status widget in new chat sessions when quota is exceeded."),
|
||||
default: undefined,
|
||||
['chat.statusWidget.anonymous']: {
|
||||
type: 'boolean',
|
||||
description: nls.localize('chat.statusWidget.anonymous.description', "Controls whether anonymous users see the status widget in new chat sessions when rate limited."),
|
||||
default: false,
|
||||
tags: ['experimental', 'advanced'],
|
||||
experiment: {
|
||||
mode: 'auto'
|
||||
|
||||
@@ -10,7 +10,7 @@ import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../../platfor
|
||||
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IChatEditingService, IChatEditingSession, IModifiedFileEntry, ModifiedFileEntryState } from '../../common/editing/chatEditingService.js';
|
||||
import { MenuId } from '../../../../../platform/actions/common/actions.js';
|
||||
import { ActionViewItem, IBaseActionViewItemOptions } from '../../../../../base/browser/ui/actionbar/actionViewItems.js';
|
||||
import { ActionViewItem, IActionViewItemOptions } from '../../../../../base/browser/ui/actionbar/actionViewItems.js';
|
||||
import { IAction, IActionRunner } from '../../../../../base/common/actions.js';
|
||||
import { $, addDisposableGenericMouseMoveListener, append } from '../../../../../base/browser/dom.js';
|
||||
import { assertType } from '../../../../../base/common/types.js';
|
||||
@@ -35,7 +35,7 @@ export class ChatEditingAcceptRejectActionViewItem extends ActionViewItem {
|
||||
|
||||
constructor(
|
||||
action: IAction,
|
||||
options: IBaseActionViewItemOptions,
|
||||
options: IActionViewItemOptions,
|
||||
private readonly _entry: IObservable<IModifiedFileEntry | undefined>,
|
||||
private readonly _editor: { focus(): void } | undefined,
|
||||
private readonly _keybindingService: IKeybindingService,
|
||||
@@ -92,7 +92,7 @@ export class ChatEditingAcceptRejectActionViewItem extends ActionViewItem {
|
||||
|
||||
protected override getTooltip(): string | undefined {
|
||||
const value = super.getTooltip();
|
||||
if (!value || this.options.keybinding) {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
return this._keybindingService.appendKeybinding(value, this._action.id);
|
||||
|
||||
+2
-1
@@ -34,6 +34,7 @@ import { IChatResponseModel } from '../../common/model/chatModel.js';
|
||||
import { ChatAgentLocation } from '../../common/constants.js';
|
||||
import { IDocumentDiff2 } from './chatEditingCodeEditorIntegration.js';
|
||||
import { pendingRewriteMinimap } from './chatEditingModifiedFileEntry.js';
|
||||
import { chatSessionResourceToId } from '../../common/model/chatUri.js';
|
||||
|
||||
type affectedLines = { linesAdded: number; linesRemoved: number; lineCount: number; hasRemainingEdits: boolean };
|
||||
type acceptedOrRejectedLines = affectedLines & { state: 'accepted' | 'rejected' };
|
||||
@@ -260,7 +261,7 @@ export class ChatEditingTextModelChangeService extends Disposable {
|
||||
return EditSources.unknown({ name: 'editSessionUndoRedo' });
|
||||
}
|
||||
|
||||
const sessionId = responseModel.session.sessionId;
|
||||
const sessionId = chatSessionResourceToId(responseModel.session.sessionResource);
|
||||
const request = responseModel.session.getRequests().at(-1);
|
||||
const languageId = this.modifiedModel.getLanguageId();
|
||||
const agent = responseModel.agent;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user