From cb4076e563719dc2b29761f8239cbcf4de585d49 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:37:26 -0800 Subject: [PATCH 001/118] Decorations wip Part of #227095 --- .../browser/gpu/fullFileRenderStrategy.ts | 49 ++++++++++++++++++- .../browser/gpu/raster/glyphRasterizer.ts | 6 +++ src/vs/editor/browser/gpu/viewGpuContext.ts | 9 +++- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts b/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts index a6df706d2b89e..a42148b222561 100644 --- a/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts +++ b/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts @@ -7,9 +7,12 @@ import { getActiveWindow } from '../../../base/browser/dom.js'; import { BugIndicatingError } from '../../../base/common/errors.js'; import { EditorOption } from '../../common/config/editorOptions.js'; import { CursorColumns } from '../../common/core/cursorColumns.js'; +import { Range } from '../../common/core/range.js'; +import { MetadataConsts } from '../../common/encodedTokenAttributes.js'; +import { ClassName } from '../../common/model/intervalTree.js'; import type { IViewLineTokens } from '../../common/tokens/lineTokens.js'; import { ViewEventHandler } from '../../common/viewEventHandler.js'; -import { ViewEventType, type ViewConfigurationChangedEvent, type ViewLinesChangedEvent, type ViewLinesDeletedEvent, type ViewLinesInsertedEvent, type ViewScrollChangedEvent, type ViewTokensChangedEvent, type ViewZonesChangedEvent } from '../../common/viewEvents.js'; +import { ViewEventType, type ViewConfigurationChangedEvent, type ViewDecorationsChangedEvent, type ViewLinesChangedEvent, type ViewLinesDeletedEvent, type ViewLinesInsertedEvent, type ViewScrollChangedEvent, type ViewTokensChangedEvent, type ViewZonesChangedEvent } from '../../common/viewEvents.js'; import type { ViewportData } from '../../common/viewLayout/viewLinesViewportData.js'; import type { ViewLineRenderingData } from '../../common/viewModel.js'; import type { ViewContext } from '../../common/viewModel/viewContext.js'; @@ -115,6 +118,13 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend return true; } + public override onDecorationsChanged(e: ViewDecorationsChangedEvent): boolean { + // TODO: Don't clear all lines + this._upToDateLines[0].clear(); + this._upToDateLines[1].clear(); + return true; + } + public override onTokensChanged(e: ViewTokensChangedEvent): boolean { // TODO: This currently fires for the entire viewport whenever scrolling stops // https://github.com/microsoft/vscode/issues/233942 @@ -274,6 +284,8 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend } } + const decorations = viewportData.getDecorationsInViewport(); + for (y = viewportData.startLineNumber; y <= viewportData.endLineNumber; y++) { // Only attempt to render lines that the GPU renderer can handle @@ -291,6 +303,14 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend dirtyLineStart = Math.min(dirtyLineStart, y); dirtyLineEnd = Math.max(dirtyLineEnd, y); + const inlineDecorations = decorations.filter(e => ( + e.range.startLineNumber <= y && e.range.endLineNumber >= y && + e.options.inlineClassName + )); + if (inlineDecorations.length > 0) { + console.log('decoration!', inlineDecorations); + } + lineData = viewportData.getViewLineRenderingData(y); content = lineData.content; xOffset = 0; @@ -313,6 +333,33 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend break; } chars = content.charAt(x); + + // TODO: We'd want to optimize pulling the decorations in order + // HACK: Temporary replace char to demonstrate inline decorations + const cellDecorations = decorations.filter(decoration => { + // TODO: Why does Range.containsPosition and Range.strictContainsPosition not work here? + if (y < decoration.range.startLineNumber || y > decoration.range.endLineNumber) { + return false; + } + if (y === decoration.range.startLineNumber && x < decoration.range.startColumn - 1) { + return false; + } + if (y === decoration.range.endLineNumber && x >= decoration.range.endColumn - 1) { + return false; + } + return true; + }); + for (const decoration of cellDecorations) { + switch (decoration.options.inlineClassName) { + case (ClassName.EditorDeprecatedInlineDecoration): { + // HACK: We probably shouldn't override tokenMetadata + tokenMetadata |= MetadataConsts.STRIKETHROUGH_MASK; + // chars = '-'; + break; + } + } + } + if (chars === ' ' || chars === '\t') { // Zero out glyph to ensure it doesn't get rendered cellIndex = ((y - 1) * this._viewGpuContext.maxGpuCols + x) * Constants.IndicesPerCell; diff --git a/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts b/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts index 26010b7e38690..07d343bb677c4 100644 --- a/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts +++ b/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts @@ -120,6 +120,12 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { this._ctx.textBaseline = 'top'; this._ctx.fillText(chars, originX, originY); + // TODO: Don't draw beyond glyph - how to handle monospace, wide and proportional? + // TODO: Support strikethrough color + if (fontStyle & FontStyle.Strikethrough) { + this._ctx.fillRect(originX, originY + Math.round(devicePixelFontSize / 2), devicePixelFontSize, Math.max(Math.floor(getActiveWindow().devicePixelRatio), 1)); + } + const imageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); this._findGlyphBoundingBox(imageData, this._workGlyph.boundingBox); // const offset = { diff --git a/src/vs/editor/browser/gpu/viewGpuContext.ts b/src/vs/editor/browser/gpu/viewGpuContext.ts index 307636b37e1fe..310a6f97f5d1e 100644 --- a/src/vs/editor/browser/gpu/viewGpuContext.ts +++ b/src/vs/editor/browser/gpu/viewGpuContext.ts @@ -19,6 +19,7 @@ import { GPULifecycle } from './gpuDisposable.js'; import { ensureNonNullable, observeDevicePixelDimensions } from './gpuUtils.js'; import { RectangleRenderer } from './rectangleRenderer.js'; import type { ViewContext } from '../../common/viewModel/viewContext.js'; +import { ClassName } from '../../common/model/intervalTree.js'; const enum GpuRenderLimits { maxGpuLines = 3000, @@ -131,7 +132,8 @@ export class ViewGpuContext extends Disposable { data.containsRTL || data.maxColumn > GpuRenderLimits.maxGpuCols || data.continuesWithWrappedLine || - data.inlineDecorations.length > 0 || + // HACK: ... + data.inlineDecorations.length > 0 && data.inlineDecorations[0].inlineClassName !== ClassName.EditorDeprecatedInlineDecoration || lineNumber >= GpuRenderLimits.maxGpuLines ) { return false; @@ -155,7 +157,10 @@ export class ViewGpuContext extends Disposable { reasons.push('continuesWithWrappedLine'); } if (data.inlineDecorations.length > 0) { - reasons.push('inlineDecorations > 0'); + // HACK: ... + if (data.inlineDecorations[0].inlineClassName !== ClassName.EditorDeprecatedInlineDecoration) { + reasons.push('inlineDecorations > 0'); + } } if (lineNumber >= GpuRenderLimits.maxGpuLines) { reasons.push('lineNumber >= maxGpuLines'); From 8354641acf0adb64e6b881bbdc91345bd9dc05d5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Nov 2024 07:58:57 -0800 Subject: [PATCH 002/118] Prototype for extracting inline decoration styles --- src/vs/editor/browser/gpu/cssRuleExtractor.ts | 77 +++++++++++++++++++ .../browser/gpu/fullFileRenderStrategy.ts | 27 ++++++- 2 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 src/vs/editor/browser/gpu/cssRuleExtractor.ts diff --git a/src/vs/editor/browser/gpu/cssRuleExtractor.ts b/src/vs/editor/browser/gpu/cssRuleExtractor.ts new file mode 100644 index 0000000000000..d8bd41ac385d7 --- /dev/null +++ b/src/vs/editor/browser/gpu/cssRuleExtractor.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { $, getActiveDocument } from '../../../base/browser/dom.js'; +import { Disposable, toDisposable } from '../../../base/common/lifecycle.js'; +import type { ViewGpuContext } from './viewGpuContext.js'; + +export class CssRuleExtractor extends Disposable { + private _container: HTMLElement; + + private _ruleCache: Map* className */string, CSSStyleRule[]> = new Map(); + + constructor( + private readonly _viewGpuContext: ViewGpuContext, + ) { + super(); + + this._container = $('div.monaco-css-rule-extractor'); + this._container.style.visibility = 'hidden'; + const parentElement = this._viewGpuContext.canvas.domNode.parentElement; + if (!parentElement) { + throw new Error('No parent element found for the canvas'); + } + parentElement.appendChild(this._container); + this._register(toDisposable(() => this._container.remove())); + } + + getStyleRules(className: string): CSSStyleRule[] { + const existing = this._ruleCache.get(className); + if (existing) { + return existing; + } + const dummyElement = $(`span.${className}`); + this._container.appendChild(dummyElement); + const rules = this._getStyleRules(dummyElement, className); + this._ruleCache.set(className, rules); + return rules; + } + + private _getStyleRules(element: HTMLElement, className: string) { + const matchedRules = []; + + // Iterate through all stylesheets + const doc = getActiveDocument(); + for (const stylesheet of doc.styleSheets) { + try { + // Iterate through all CSS rules in the stylesheet + for (const rule of stylesheet.cssRules) { + if (rule instanceof CSSImportRule) { + // Recursively process the import rule + if (rule.styleSheet?.cssRules) { + for (const innerRule of rule.styleSheet.cssRules) { + if (innerRule instanceof CSSStyleRule) { + if (element.matches(innerRule.selectorText) && innerRule.selectorText.includes(className)) { + matchedRules.push(innerRule); + } + } + } + } + } else if (rule instanceof CSSStyleRule) { + // Check if the element matches the selector + if (element.matches(rule.selectorText) && rule.selectorText.includes(className)) { + matchedRules.push(rule); + } + } + } + } catch (e) { + // Some stylesheets may not be accessible due to CORS restrictions + console.warn('Could not access stylesheet:', stylesheet.href); + } + } + + return matchedRules; + } +} diff --git a/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts b/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts index a42148b222561..a57f6c9f085fe 100644 --- a/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts +++ b/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getActiveWindow } from '../../../base/browser/dom.js'; +import { getActiveDocument, getActiveWindow } from '../../../base/browser/dom.js'; import { BugIndicatingError } from '../../../base/common/errors.js'; import { EditorOption } from '../../common/config/editorOptions.js'; import { CursorColumns } from '../../common/core/cursorColumns.js'; -import { Range } from '../../common/core/range.js'; import { MetadataConsts } from '../../common/encodedTokenAttributes.js'; import { ClassName } from '../../common/model/intervalTree.js'; import type { IViewLineTokens } from '../../common/tokens/lineTokens.js'; @@ -18,6 +17,7 @@ import type { ViewLineRenderingData } from '../../common/viewModel.js'; import type { ViewContext } from '../../common/viewModel/viewContext.js'; import type { ViewLineOptions } from '../viewParts/viewLines/viewLineOptions.js'; import type { ITextureAtlasPageGlyph } from './atlas/atlas.js'; +import { CssRuleExtractor } from './cssRuleExtractor.js'; import { fullFileRenderStrategyWgsl } from './fullFileRenderStrategy.wgsl.js'; import { BindingId, type IGpuRenderStrategy } from './gpu.js'; import { GPULifecycle } from './gpuDisposable.js'; @@ -48,6 +48,7 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend readonly wgsl: string = fullFileRenderStrategyWgsl; private readonly _glyphRasterizer: GlyphRasterizer; + private readonly _cssRuleExtractor: CssRuleExtractor; private _cellBindBuffer!: GPUBuffer; @@ -89,6 +90,7 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend const fontSize = this._context.configuration.options.get(EditorOption.fontSize); this._glyphRasterizer = this._register(new GlyphRasterizer(fontSize, fontFamily)); + this._cssRuleExtractor = this._register(new CssRuleExtractor(this._viewGpuContext)); const bufferSize = this._viewGpuContext.maxGpuLines * this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell * Float32Array.BYTES_PER_ELEMENT; this._cellBindBuffer = this._register(GPULifecycle.createBuffer(this._device, { @@ -336,7 +338,7 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend // TODO: We'd want to optimize pulling the decorations in order // HACK: Temporary replace char to demonstrate inline decorations - const cellDecorations = decorations.filter(decoration => { + const cellDecorations = inlineDecorations.filter(decoration => { // TODO: Why does Range.containsPosition and Range.strictContainsPosition not work here? if (y < decoration.range.startLineNumber || y > decoration.range.endLineNumber) { return false; @@ -350,6 +352,25 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend return true; }); for (const decoration of cellDecorations) { + if (!decoration.options.inlineClassName) { + throw new BugIndicatingError('Expected inlineClassName on decoration'); + } + const rules = this._cssRuleExtractor.getStyleRules(decoration.options.inlineClassName); + const supportedCssRules = [ + 'text-decoration-line', + 'text-decoration-thickness', + 'text-decoration-style', + 'text-decoration-color', + ]; + const supported = rules.every(rule => { + for (const r of rule.style) { + if (!supportedCssRules.includes(r)) { + return false; + } + } + return true; + }); + console.log('rules supported?', supported, rules); switch (decoration.options.inlineClassName) { case (ClassName.EditorDeprecatedInlineDecoration): { // HACK: We probably shouldn't override tokenMetadata From a42e4e9a5f923e10a4af8506502ad8041905e464 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:13:49 -0800 Subject: [PATCH 003/118] Improve rule pulling, report unsupported rules --- src/vs/editor/browser/gpu/cssRuleExtractor.ts | 77 ------------------- .../browser/gpu/decorationCssRuleExtractor.ts | 57 ++++++++++++++ .../browser/gpu/fullFileRenderStrategy.ts | 29 ++----- src/vs/editor/browser/gpu/viewGpuContext.ts | 66 ++++++++++++++-- .../browser/viewParts/gpuMark/gpuMark.ts | 4 +- .../browser/viewParts/viewLines/viewLine.ts | 4 +- .../viewParts/viewLinesGpu/viewLinesGpu.ts | 4 +- 7 files changed, 128 insertions(+), 113 deletions(-) delete mode 100644 src/vs/editor/browser/gpu/cssRuleExtractor.ts create mode 100644 src/vs/editor/browser/gpu/decorationCssRuleExtractor.ts diff --git a/src/vs/editor/browser/gpu/cssRuleExtractor.ts b/src/vs/editor/browser/gpu/cssRuleExtractor.ts deleted file mode 100644 index d8bd41ac385d7..0000000000000 --- a/src/vs/editor/browser/gpu/cssRuleExtractor.ts +++ /dev/null @@ -1,77 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { $, getActiveDocument } from '../../../base/browser/dom.js'; -import { Disposable, toDisposable } from '../../../base/common/lifecycle.js'; -import type { ViewGpuContext } from './viewGpuContext.js'; - -export class CssRuleExtractor extends Disposable { - private _container: HTMLElement; - - private _ruleCache: Map* className */string, CSSStyleRule[]> = new Map(); - - constructor( - private readonly _viewGpuContext: ViewGpuContext, - ) { - super(); - - this._container = $('div.monaco-css-rule-extractor'); - this._container.style.visibility = 'hidden'; - const parentElement = this._viewGpuContext.canvas.domNode.parentElement; - if (!parentElement) { - throw new Error('No parent element found for the canvas'); - } - parentElement.appendChild(this._container); - this._register(toDisposable(() => this._container.remove())); - } - - getStyleRules(className: string): CSSStyleRule[] { - const existing = this._ruleCache.get(className); - if (existing) { - return existing; - } - const dummyElement = $(`span.${className}`); - this._container.appendChild(dummyElement); - const rules = this._getStyleRules(dummyElement, className); - this._ruleCache.set(className, rules); - return rules; - } - - private _getStyleRules(element: HTMLElement, className: string) { - const matchedRules = []; - - // Iterate through all stylesheets - const doc = getActiveDocument(); - for (const stylesheet of doc.styleSheets) { - try { - // Iterate through all CSS rules in the stylesheet - for (const rule of stylesheet.cssRules) { - if (rule instanceof CSSImportRule) { - // Recursively process the import rule - if (rule.styleSheet?.cssRules) { - for (const innerRule of rule.styleSheet.cssRules) { - if (innerRule instanceof CSSStyleRule) { - if (element.matches(innerRule.selectorText) && innerRule.selectorText.includes(className)) { - matchedRules.push(innerRule); - } - } - } - } - } else if (rule instanceof CSSStyleRule) { - // Check if the element matches the selector - if (element.matches(rule.selectorText) && rule.selectorText.includes(className)) { - matchedRules.push(rule); - } - } - } - } catch (e) { - // Some stylesheets may not be accessible due to CORS restrictions - console.warn('Could not access stylesheet:', stylesheet.href); - } - } - - return matchedRules; - } -} diff --git a/src/vs/editor/browser/gpu/decorationCssRuleExtractor.ts b/src/vs/editor/browser/gpu/decorationCssRuleExtractor.ts new file mode 100644 index 0000000000000..5959bd36cd3e6 --- /dev/null +++ b/src/vs/editor/browser/gpu/decorationCssRuleExtractor.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { $, getActiveDocument } from '../../../base/browser/dom.js'; +import { Disposable, toDisposable } from '../../../base/common/lifecycle.js'; + +export class DecorationCssRuleExtractor extends Disposable { + private _container: HTMLElement; + + private _ruleCache: Map* className */string, CSSStyleRule[]> = new Map(); + + constructor() { + super(); + this._container = $('div.monaco-css-rule-extractor'); + this._container.style.visibility = 'hidden'; + this._register(toDisposable(() => this._container.remove())); + } + + getStyleRules(canvas: HTMLElement, decorationClassName: string): CSSStyleRule[] { + const existing = this._ruleCache.get(decorationClassName); + if (existing) { + return existing; + } + const dummyElement = $(`span.${decorationClassName}`); + this._container.appendChild(dummyElement); + const rules = this._getStyleRules(canvas, dummyElement, decorationClassName); + this._ruleCache.set(decorationClassName, rules); + return rules; + } + + private _getStyleRules(canvas: HTMLElement, element: HTMLElement, className: string) { + canvas.appendChild(this._container); + + // Iterate through all stylesheets and imported stylesheets to find matching rules + const rules = []; + const doc = getActiveDocument(); + const stylesheets = [...doc.styleSheets]; + for (let i = 0; i < stylesheets.length; i++) { + const stylesheet = stylesheets[i]; + for (const rule of stylesheet.cssRules) { + if (rule instanceof CSSImportRule) { + if (rule.styleSheet) { + stylesheets.push(rule.styleSheet); + } + } else if (rule instanceof CSSStyleRule) { + if (element.matches(rule.selectorText) && rule.selectorText.includes(`.${className}`)) { + rules.push(rule); + } + } + } + } + + return rules; + } +} diff --git a/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts b/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts index a57f6c9f085fe..473e662a37b64 100644 --- a/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts +++ b/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getActiveDocument, getActiveWindow } from '../../../base/browser/dom.js'; +import { getActiveWindow } from '../../../base/browser/dom.js'; import { BugIndicatingError } from '../../../base/common/errors.js'; import { EditorOption } from '../../common/config/editorOptions.js'; import { CursorColumns } from '../../common/core/cursorColumns.js'; @@ -17,7 +17,6 @@ import type { ViewLineRenderingData } from '../../common/viewModel.js'; import type { ViewContext } from '../../common/viewModel/viewContext.js'; import type { ViewLineOptions } from '../viewParts/viewLines/viewLineOptions.js'; import type { ITextureAtlasPageGlyph } from './atlas/atlas.js'; -import { CssRuleExtractor } from './cssRuleExtractor.js'; import { fullFileRenderStrategyWgsl } from './fullFileRenderStrategy.wgsl.js'; import { BindingId, type IGpuRenderStrategy } from './gpu.js'; import { GPULifecycle } from './gpuDisposable.js'; @@ -48,7 +47,6 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend readonly wgsl: string = fullFileRenderStrategyWgsl; private readonly _glyphRasterizer: GlyphRasterizer; - private readonly _cssRuleExtractor: CssRuleExtractor; private _cellBindBuffer!: GPUBuffer; @@ -90,7 +88,6 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend const fontSize = this._context.configuration.options.get(EditorOption.fontSize); this._glyphRasterizer = this._register(new GlyphRasterizer(fontSize, fontFamily)); - this._cssRuleExtractor = this._register(new CssRuleExtractor(this._viewGpuContext)); const bufferSize = this._viewGpuContext.maxGpuLines * this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell * Float32Array.BYTES_PER_ELEMENT; this._cellBindBuffer = this._register(GPULifecycle.createBuffer(this._device, { @@ -291,7 +288,7 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend for (y = viewportData.startLineNumber; y <= viewportData.endLineNumber; y++) { // Only attempt to render lines that the GPU renderer can handle - if (!ViewGpuContext.canRender(viewLineOptions, viewportData, y)) { + if (!ViewGpuContext.canRender(this._viewGpuContext.canvas.domNode, viewLineOptions, viewportData, y)) { fillStartIndex = ((y - 1) * this._viewGpuContext.maxGpuCols) * Constants.IndicesPerCell; fillEndIndex = (y * this._viewGpuContext.maxGpuCols) * Constants.IndicesPerCell; cellBuffer.fill(0, fillStartIndex, fillEndIndex); @@ -351,26 +348,10 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend } return true; }); + + // Only lines containing fully supported inline decorations should have made it + // this far. for (const decoration of cellDecorations) { - if (!decoration.options.inlineClassName) { - throw new BugIndicatingError('Expected inlineClassName on decoration'); - } - const rules = this._cssRuleExtractor.getStyleRules(decoration.options.inlineClassName); - const supportedCssRules = [ - 'text-decoration-line', - 'text-decoration-thickness', - 'text-decoration-style', - 'text-decoration-color', - ]; - const supported = rules.every(rule => { - for (const r of rule.style) { - if (!supportedCssRules.includes(r)) { - return false; - } - } - return true; - }); - console.log('rules supported?', supported, rules); switch (decoration.options.inlineClassName) { case (ClassName.EditorDeprecatedInlineDecoration): { // HACK: We probably shouldn't override tokenMetadata diff --git a/src/vs/editor/browser/gpu/viewGpuContext.ts b/src/vs/editor/browser/gpu/viewGpuContext.ts index 310a6f97f5d1e..70aff96eaaa33 100644 --- a/src/vs/editor/browser/gpu/viewGpuContext.ts +++ b/src/vs/editor/browser/gpu/viewGpuContext.ts @@ -19,7 +19,7 @@ import { GPULifecycle } from './gpuDisposable.js'; import { ensureNonNullable, observeDevicePixelDimensions } from './gpuUtils.js'; import { RectangleRenderer } from './rectangleRenderer.js'; import type { ViewContext } from '../../common/viewModel/viewContext.js'; -import { ClassName } from '../../common/model/intervalTree.js'; +import { DecorationCssRuleExtractor } from './decorationCssRuleExtractor.js'; const enum GpuRenderLimits { maxGpuLines = 3000, @@ -48,6 +48,7 @@ export class ViewGpuContext extends Disposable { private static _atlas: TextureAtlas | undefined; + private static readonly _decorationCssRuleExtractor = new DecorationCssRuleExtractor(); /** * The shared texture atlas to use across all views. @@ -126,25 +127,52 @@ export class ViewGpuContext extends Disposable { * renderer. Eventually this should trend all lines, except maybe exceptional cases like * decorations that use class names. */ - public static canRender(options: ViewLineOptions, viewportData: ViewportData, lineNumber: number): boolean { + public static canRender(container: HTMLElement, options: ViewLineOptions, viewportData: ViewportData, lineNumber: number): boolean { const data = viewportData.getViewLineRenderingData(lineNumber); + + // Check if the line has simple attributes that aren't supported if ( data.containsRTL || data.maxColumn > GpuRenderLimits.maxGpuCols || data.continuesWithWrappedLine || - // HACK: ... - data.inlineDecorations.length > 0 && data.inlineDecorations[0].inlineClassName !== ClassName.EditorDeprecatedInlineDecoration || lineNumber >= GpuRenderLimits.maxGpuLines ) { return false; } + + // Check if all inline decorations are supported + if (data.inlineDecorations.length > 0) { + let supported = true; + for (const decoration of data.inlineDecorations) { + const styleRules = ViewGpuContext._decorationCssRuleExtractor.getStyleRules(container, decoration.inlineClassName); + const supportedCssRules = [ + 'text-decoration-line', + 'text-decoration-thickness', + 'text-decoration-style', + 'text-decoration-color', + ]; + supported &&= styleRules.every(rule => { + for (const r of rule.style) { + if (!supportedCssRules.includes(r)) { + return false; + } + } + return true; + }); + if (!supported) { + break; + } + } + return supported; + } + return true; } /** * Like {@link canRender} but returned detailed information about why the line cannot be rendered. */ - public static canRenderDetailed(options: ViewLineOptions, viewportData: ViewportData, lineNumber: number): string[] { + public static canRenderDetailed(container: HTMLElement, options: ViewLineOptions, viewportData: ViewportData, lineNumber: number): string[] { const data = viewportData.getViewLineRenderingData(lineNumber); const reasons: string[] = []; if (data.containsRTL) { @@ -157,9 +185,31 @@ export class ViewGpuContext extends Disposable { reasons.push('continuesWithWrappedLine'); } if (data.inlineDecorations.length > 0) { - // HACK: ... - if (data.inlineDecorations[0].inlineClassName !== ClassName.EditorDeprecatedInlineDecoration) { - reasons.push('inlineDecorations > 0'); + let supported = true; + const problemRules: string[] = []; + for (const decoration of data.inlineDecorations) { + const styleRules = ViewGpuContext._decorationCssRuleExtractor.getStyleRules(container, decoration.inlineClassName); + const supportedCssRules = [ + 'text-decoration-line', + 'text-decoration-thickness', + 'text-decoration-style', + 'text-decoration-color', + ]; + supported &&= styleRules.every(rule => { + for (const r of rule.style) { + if (!supportedCssRules.includes(r)) { + problemRules.push(r); + return false; + } + } + return true; + }); + if (!supported) { + break; + } + } + if (problemRules.length > 0) { + reasons.push(`inlineDecorations with unsupported CSS rules (\`${problemRules.join(', ')}\`)`); } } if (lineNumber >= GpuRenderLimits.maxGpuLines) { diff --git a/src/vs/editor/browser/viewParts/gpuMark/gpuMark.ts b/src/vs/editor/browser/viewParts/gpuMark/gpuMark.ts index 7019eff5e007e..861ff529cdc47 100644 --- a/src/vs/editor/browser/viewParts/gpuMark/gpuMark.ts +++ b/src/vs/editor/browser/viewParts/gpuMark/gpuMark.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getActiveDocument } from '../../../../base/browser/dom.js'; import * as viewEvents from '../../../common/viewEvents.js'; import { ViewContext } from '../../../common/viewModel/viewContext.js'; import { ViewGpuContext } from '../../gpu/viewGpuContext.js'; @@ -77,7 +78,8 @@ export class GpuMarkOverlay extends DynamicViewOverlay { const output: string[] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { const lineIndex = lineNumber - visibleStartLineNumber; - const cannotRenderReasons = ViewGpuContext.canRenderDetailed(options, viewportData, lineNumber); + // TODO: How to get the container? + const cannotRenderReasons = ViewGpuContext.canRenderDetailed(getActiveDocument().querySelector('.view-lines')!, options, viewportData, lineNumber); output[lineIndex] = cannotRenderReasons.length ? `
` : ''; } diff --git a/src/vs/editor/browser/viewParts/viewLines/viewLine.ts b/src/vs/editor/browser/viewParts/viewLines/viewLine.ts index 5e555246cea86..d25238bb4cf54 100644 --- a/src/vs/editor/browser/viewParts/viewLines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/viewLines/viewLine.ts @@ -19,6 +19,7 @@ import { EditorFontLigatures } from '../../../common/config/editorOptions.js'; import { DomReadingContext } from './domReadingContext.js'; import type { ViewLineOptions } from './viewLineOptions.js'; import { ViewGpuContext } from '../../gpu/viewGpuContext.js'; +import { getActiveDocument } from '../../../../base/browser/dom.js'; const canUseFastRenderedViewLine = (function () { if (platform.isNative) { @@ -98,7 +99,8 @@ export class ViewLine implements IVisibleLine { } public renderLine(lineNumber: number, deltaTop: number, lineHeight: number, viewportData: ViewportData, sb: StringBuilder): boolean { - if (this._options.useGpu && ViewGpuContext.canRender(this._options, viewportData, lineNumber)) { + // TODO: How to get the container? + if (this._options.useGpu && ViewGpuContext.canRender(getActiveDocument().querySelector('.view-lines')!, this._options, viewportData, lineNumber)) { this._renderedViewLine?.domNode?.domNode.remove(); this._renderedViewLine = null; return false; diff --git a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts index c4d8c0400d214..cfe4a974bd78f 100644 --- a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts +++ b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts @@ -551,7 +551,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { if (!this._lastViewportData || !this._lastViewLineOptions) { return undefined; } - if (!ViewGpuContext.canRender(this._lastViewLineOptions, this._lastViewportData, lineNumber)) { + if (!ViewGpuContext.canRender(this._viewGpuContext.canvas.domNode, this._lastViewLineOptions, this._lastViewportData, lineNumber)) { return undefined; } @@ -569,7 +569,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { if (!this._lastViewportData || !this._lastViewLineOptions) { return undefined; } - if (!ViewGpuContext.canRender(this._lastViewLineOptions, this._lastViewportData, lineNumber)) { + if (!ViewGpuContext.canRender(this._viewGpuContext.canvas.domNode, this._lastViewLineOptions, this._lastViewportData, lineNumber)) { return undefined; } const lineData = this._lastViewportData.getViewLineRenderingData(lineNumber); From f536f763b2ac08658fd0c7473325e42710083776 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:33:24 -0800 Subject: [PATCH 004/118] Apply CSS styles in gpu lines --- .../browser/gpu/fullFileRenderStrategy.ts | 32 ++++++++++++++++--- src/vs/editor/browser/gpu/viewGpuContext.ts | 31 +++++++++--------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts b/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts index 473e662a37b64..2969cbe23ea53 100644 --- a/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts +++ b/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts @@ -8,7 +8,6 @@ import { BugIndicatingError } from '../../../base/common/errors.js'; import { EditorOption } from '../../common/config/editorOptions.js'; import { CursorColumns } from '../../common/core/cursorColumns.js'; import { MetadataConsts } from '../../common/encodedTokenAttributes.js'; -import { ClassName } from '../../common/model/intervalTree.js'; import type { IViewLineTokens } from '../../common/tokens/lineTokens.js'; import { ViewEventHandler } from '../../common/viewEventHandler.js'; import { ViewEventType, type ViewConfigurationChangedEvent, type ViewDecorationsChangedEvent, type ViewLinesChangedEvent, type ViewLinesDeletedEvent, type ViewLinesInsertedEvent, type ViewScrollChangedEvent, type ViewTokensChangedEvent, type ViewZonesChangedEvent } from '../../common/viewEvents.js'; @@ -351,14 +350,37 @@ export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRend // Only lines containing fully supported inline decorations should have made it // this far. + const inlineStyles: Map {
const { serverProcess, driver } = await measureAndLog(() => launchPlaywrightBrowser(options), 'launch playwright (browser)', options.logger);
registerInstance(serverProcess, options.logger, 'server');
- return new Code(driver, options.logger, serverProcess);
+ return new Code(driver, options.logger, serverProcess, options.quality);
}
// Electron smoke tests (playwright)
@@ -85,7 +87,7 @@ export async function launch(options: LaunchOptions): Promise {
const { electronProcess, driver } = await measureAndLog(() => launchPlaywrightElectron(options), 'launch playwright (electron)', options.logger);
registerInstance(electronProcess, options.logger, 'electron');
- return new Code(driver, options.logger, electronProcess);
+ return new Code(driver, options.logger, electronProcess, options.quality);
}
}
@@ -96,7 +98,8 @@ export class Code {
constructor(
driver: PlaywrightDriver,
readonly logger: Logger,
- private readonly mainProcess: cp.ChildProcess
+ private readonly mainProcess: cp.ChildProcess,
+ readonly quality: Quality
) {
this.driver = new Proxy(driver, {
get(target, prop) {
diff --git a/test/automation/src/debug.ts b/test/automation/src/debug.ts
index b7b7d427f4b0f..e2e227fc35e14 100644
--- a/test/automation/src/debug.ts
+++ b/test/automation/src/debug.ts
@@ -9,6 +9,7 @@ import { Code, findElement } from './code';
import { Editors } from './editors';
import { Editor } from './editor';
import { IElement } from './driver';
+import { Quality } from './application';
const VIEWLET = 'div[id="workbench.view.debug"]';
const DEBUG_VIEW = `${VIEWLET}`;
@@ -31,7 +32,8 @@ const CONSOLE_OUTPUT = `.repl .output.expression .value`;
const CONSOLE_EVALUATION_RESULT = `.repl .evaluation-result.expression .value`;
const CONSOLE_LINK = `.repl .value a.link`;
-const REPL_FOCUSED = '.repl-input-wrapper .monaco-editor textarea';
+const REPL_FOCUSED_NATIVE_EDIT_CONTEXT = '.repl-input-wrapper .monaco-editor .native-edit-context';
+const REPL_FOCUSED_TEXTAREA = '.repl-input-wrapper .monaco-editor textarea';
export interface IStackFrame {
name: string;
@@ -127,8 +129,9 @@ export class Debug extends Viewlet {
async waitForReplCommand(text: string, accept: (result: string) => boolean): Promise {
await this.commands.runCommand('Debug: Focus on Debug Console View');
- await this.code.waitForActiveElement(REPL_FOCUSED);
- await this.code.waitForSetValue(REPL_FOCUSED, text);
+ const selector = this.code.quality === Quality.Stable ? REPL_FOCUSED_TEXTAREA : REPL_FOCUSED_NATIVE_EDIT_CONTEXT;
+ await this.code.waitForActiveElement(selector);
+ await this.code.waitForSetValue(selector, text);
// Wait for the keys to be picked up by the editor model such that repl evaluates what just got typed
await this.editor.waitForEditorContents('debug:replinput', s => s.indexOf(text) >= 0);
diff --git a/test/automation/src/editor.ts b/test/automation/src/editor.ts
index 538866bfc0603..dd6160795650c 100644
--- a/test/automation/src/editor.ts
+++ b/test/automation/src/editor.ts
@@ -6,6 +6,7 @@
import { References } from './peek';
import { Commands } from './workbench';
import { Code } from './code';
+import { Quality } from './application';
const RENAME_BOX = '.monaco-editor .monaco-editor.rename-box';
const RENAME_INPUT = `${RENAME_BOX} .rename-input`;
@@ -78,10 +79,10 @@ export class Editor {
async waitForEditorFocus(filename: string, lineNumber: number, selectorPrefix = ''): Promise {
const editor = [selectorPrefix || '', EDITOR(filename)].join(' ');
const line = `${editor} .view-lines > .view-line:nth-child(${lineNumber})`;
- const textarea = `${editor} textarea`;
+ const editContext = `${editor} ${this._editContextSelector()}`;
await this.code.waitAndClick(line, 1, 1);
- await this.code.waitForActiveElement(textarea);
+ await this.code.waitForActiveElement(editContext);
}
async waitForTypeInEditor(filename: string, text: string, selectorPrefix = ''): Promise {
@@ -92,14 +93,18 @@ export class Editor {
await this.code.waitForElement(editor);
- const textarea = `${editor} textarea`;
- await this.code.waitForActiveElement(textarea);
+ const editContext = `${editor} ${this._editContextSelector()}`;
+ await this.code.waitForActiveElement(editContext);
- await this.code.waitForTypeInEditor(textarea, text);
+ await this.code.waitForTypeInEditor(editContext, text);
await this.waitForEditorContents(filename, c => c.indexOf(text) > -1, selectorPrefix);
}
+ private _editContextSelector() {
+ return this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context';
+ }
+
async waitForEditorContents(filename: string, accept: (contents: string) => boolean, selectorPrefix = ''): Promise {
const selector = [selectorPrefix || '', `${EDITOR(filename)} .view-lines`].join(' ');
return this.code.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' ')));
diff --git a/test/automation/src/editors.ts b/test/automation/src/editors.ts
index b3a914ffff026..472385c8534d1 100644
--- a/test/automation/src/editors.ts
+++ b/test/automation/src/editors.ts
@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import { Quality } from './application';
import { Code } from './code';
export class Editors {
@@ -53,7 +54,7 @@ export class Editors {
}
async waitForActiveEditor(fileName: string, retryCount?: number): Promise {
- const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] textarea`;
+ const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] ${this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'}`;
return this.code.waitForActiveElement(selector, retryCount);
}
diff --git a/test/automation/src/extensions.ts b/test/automation/src/extensions.ts
index 2a481f9fe766e..c881e4fd8dc6d 100644
--- a/test/automation/src/extensions.ts
+++ b/test/automation/src/extensions.ts
@@ -8,6 +8,7 @@ import { Code } from './code';
import { ncp } from 'ncp';
import { promisify } from 'util';
import { Commands } from './workbench';
+import { Quality } from './application';
import path = require('path');
import fs = require('fs');
@@ -20,7 +21,7 @@ export class Extensions extends Viewlet {
async searchForExtension(id: string): Promise {
await this.commands.runCommand('Extensions: Focus on Extensions View', { exactLabelMatch: true });
- await this.code.waitForTypeInEditor('div.extensions-viewlet[id="workbench.view.extensions"] .monaco-editor textarea', `@id:${id}`);
+ await this.code.waitForTypeInEditor(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-editor ${this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'}`, `@id:${id}`);
await this.code.waitForTextContent(`div.part.sidebar div.composite.title h2`, 'Extensions: Marketplace');
let retrials = 1;
diff --git a/test/automation/src/notebook.ts b/test/automation/src/notebook.ts
index dff250027db72..cd46cbdb0dd49 100644
--- a/test/automation/src/notebook.ts
+++ b/test/automation/src/notebook.ts
@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import { Quality } from './application';
import { Code } from './code';
import { QuickAccess } from './quickaccess';
import { QuickInput } from './quickinput';
@@ -46,10 +47,10 @@ export class Notebook {
await this.code.waitForElement(editor);
- const textarea = `${editor} textarea`;
- await this.code.waitForActiveElement(textarea);
+ const editContext = `${editor} ${this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'}`;
+ await this.code.waitForActiveElement(editContext);
- await this.code.waitForTypeInEditor(textarea, text);
+ await this.code.waitForTypeInEditor(editContext, text);
await this._waitForActiveCellEditorContents(c => c.indexOf(text) > -1);
}
diff --git a/test/automation/src/scm.ts b/test/automation/src/scm.ts
index 9f950f2b16a77..6489badbe8a41 100644
--- a/test/automation/src/scm.ts
+++ b/test/automation/src/scm.ts
@@ -6,9 +6,11 @@
import { Viewlet } from './viewlet';
import { IElement } from './driver';
import { findElement, findElements, Code } from './code';
+import { Quality } from './application';
const VIEWLET = 'div[id="workbench.view.scm"]';
-const SCM_INPUT = `${VIEWLET} .scm-editor textarea`;
+const SCM_INPUT_NATIVE_EDIT_CONTEXT = `${VIEWLET} .scm-editor .native-edit-context`;
+const SCM_INPUT_TEXTAREA = `${VIEWLET} .scm-editor textarea`;
const SCM_RESOURCE = `${VIEWLET} .monaco-list-row .resource`;
const REFRESH_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[aria-label="Refresh"]`;
const COMMIT_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[aria-label="Commit"]`;
@@ -44,7 +46,7 @@ export class SCM extends Viewlet {
async openSCMViewlet(): Promise {
await this.code.dispatchKeybinding('ctrl+shift+g');
- await this.code.waitForElement(SCM_INPUT);
+ await this.code.waitForElement(this._editContextSelector());
}
async waitForChange(name: string, type?: string): Promise {
@@ -71,9 +73,13 @@ export class SCM extends Viewlet {
}
async commit(message: string): Promise {
- await this.code.waitAndClick(SCM_INPUT);
- await this.code.waitForActiveElement(SCM_INPUT);
- await this.code.waitForSetValue(SCM_INPUT, message);
+ await this.code.waitAndClick(this._editContextSelector());
+ await this.code.waitForActiveElement(this._editContextSelector());
+ await this.code.waitForSetValue(this._editContextSelector(), message);
await this.code.waitAndClick(COMMIT_COMMAND);
}
+
+ private _editContextSelector(): string {
+ return this.code.quality === Quality.Stable ? SCM_INPUT_TEXTAREA : SCM_INPUT_NATIVE_EDIT_CONTEXT;
+ }
}
diff --git a/test/automation/src/settings.ts b/test/automation/src/settings.ts
index 68401eb0edaa3..8cf221b1487b6 100644
--- a/test/automation/src/settings.ts
+++ b/test/automation/src/settings.ts
@@ -7,8 +7,10 @@ import { Editor } from './editor';
import { Editors } from './editors';
import { Code } from './code';
import { QuickAccess } from './quickaccess';
+import { Quality } from './application';
-const SEARCH_BOX = '.settings-editor .suggest-input-container .monaco-editor textarea';
+const SEARCH_BOX_NATIVE_EDIT_CONTEXT = '.settings-editor .suggest-input-container .monaco-editor .native-edit-context';
+const SEARCH_BOX_TEXTAREA = '.settings-editor .suggest-input-container .monaco-editor textarea';
export class SettingsEditor {
constructor(private code: Code, private editors: Editors, private editor: Editor, private quickaccess: QuickAccess) { }
@@ -57,13 +59,13 @@ export class SettingsEditor {
async openUserSettingsUI(): Promise {
await this.quickaccess.runCommand('workbench.action.openSettings2');
- await this.code.waitForActiveElement(SEARCH_BOX);
+ await this.code.waitForActiveElement(this._editContextSelector());
}
async searchSettingsUI(query: string): Promise {
await this.openUserSettingsUI();
- await this.code.waitAndClick(SEARCH_BOX);
+ await this.code.waitAndClick(this._editContextSelector());
if (process.platform === 'darwin') {
await this.code.dispatchKeybinding('cmd+a');
} else {
@@ -71,7 +73,11 @@ export class SettingsEditor {
}
await this.code.dispatchKeybinding('Delete');
await this.code.waitForElements('.settings-editor .settings-count-widget', false, results => !results || (results?.length === 1 && !results[0].textContent));
- await this.code.waitForTypeInEditor('.settings-editor .suggest-input-container .monaco-editor textarea', query);
+ await this.code.waitForTypeInEditor(this._editContextSelector(), query);
await this.code.waitForElements('.settings-editor .settings-count-widget', false, results => results?.length === 1 && results[0].textContent.includes('Found'));
}
+
+ private _editContextSelector() {
+ return this.code.quality === Quality.Stable ? SEARCH_BOX_TEXTAREA : SEARCH_BOX_NATIVE_EDIT_CONTEXT;
+ }
}
From eeec4c35abc14d7c28c1540228adcb268adee136 Mon Sep 17 00:00:00 2001
From: Rob Lourens
Date: Thu, 21 Nov 2024 03:05:23 -0800
Subject: [PATCH 031/118] Persist selected chat model globally, not
per-workspace (#234335)
* Persist selected chat model globally, not per-workspace
* Cleanup
---
.../contrib/chat/browser/chatEditor.ts | 1 -
.../contrib/chat/browser/chatInputPart.ts | 53 ++++++++++++-------
.../contrib/chat/browser/chatWidget.ts | 4 +-
.../browser/inlineChatController.ts | 36 ++++---------
4 files changed, 43 insertions(+), 51 deletions(-)
diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts
index 69774d4b6db0c..46c63ff9344e1 100644
--- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts
@@ -128,7 +128,6 @@ export class ChatEditor extends EditorPane {
// Need to set props individually on the memento
this._viewState.inputValue = widgetViewState.inputValue;
- this._viewState.selectedLanguageModelId = widgetViewState.selectedLanguageModelId;
this._memento.saveMemento();
}
}
diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts
index 76bcd49b9b3b2..b731c63c9d9e3 100644
--- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts
@@ -65,6 +65,7 @@ import { WorkbenchList } from '../../../../platform/list/browser/listService.js'
import { ILogService } from '../../../../platform/log/common/log.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
import { IOpenerService, type OpenInternalOptions } from '../../../../platform/opener/common/opener.js';
+import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
import { FolderThemeIcon, IThemeService } from '../../../../platform/theme/common/themeService.js';
import { fillEditorsDragData } from '../../../browser/dnd.js';
import { IFileLabelOptions, ResourceLabels } from '../../../browser/labels.js';
@@ -281,6 +282,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
@IMenuService private readonly menuService: IMenuService,
@ILanguageService private readonly languageService: ILanguageService,
@IThemeService private readonly themeService: IThemeService,
+ @IStorageService private readonly storageService: IStorageService,
) {
super();
@@ -316,6 +318,35 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
this._chatEditsListPool = this._register(this.instantiationService.createInstance(CollapsibleListPool, this._onDidChangeVisibility.event, MenuId.ChatEditingWidgetModifiedFilesToolbar));
this._hasFileAttachmentContextKey = ChatContextKeys.hasFileAttachments.bindTo(contextKeyService);
+
+ this.initSelectedModel();
+ }
+
+ private getSelectedModelStorageKey(): string {
+ return `chat.currentLanguageModel.${this.location}`;
+ }
+
+ private initSelectedModel() {
+ const persistedSelection = this.storageService.get(this.getSelectedModelStorageKey(), StorageScope.APPLICATION);
+ if (persistedSelection) {
+ const model = this.languageModelsService.lookupLanguageModel(persistedSelection);
+ if (model) {
+ this._currentLanguageModel = persistedSelection;
+ this._onDidChangeCurrentLanguageModel.fire(this._currentLanguageModel);
+ } else {
+ this._waitForPersistedLanguageModel.value = this.languageModelsService.onDidChangeLanguageModels(e => {
+ const persistedModel = e.added?.find(m => m.identifier === persistedSelection);
+ if (persistedModel) {
+ this._waitForPersistedLanguageModel.clear();
+
+ if (persistedModel.metadata.isUserSelectable) {
+ this._currentLanguageModel = persistedSelection;
+ this._onDidChangeCurrentLanguageModel.fire(this._currentLanguageModel!);
+ }
+ }
+ });
+ }
+ }
}
private setCurrentLanguageModelToDefault() {
@@ -335,6 +366,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
if (this.cachedDimensions) {
this.layout(this.cachedDimensions.height, this.cachedDimensions.width);
}
+
+ this.storageService.store(this.getSelectedModelStorageKey(), modelId, StorageScope.APPLICATION, StorageTarget.USER);
}
private loadHistory(): HistoryNavigator2 {
@@ -367,26 +400,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
if (state.inputValue) {
this.setValue(state.inputValue, false);
}
-
- if (state.selectedLanguageModelId) {
- const model = this.languageModelsService.lookupLanguageModel(state.selectedLanguageModelId);
- if (model) {
- this._currentLanguageModel = state.selectedLanguageModelId;
- this._onDidChangeCurrentLanguageModel.fire(this._currentLanguageModel);
- } else {
- this._waitForPersistedLanguageModel.value = this.languageModelsService.onDidChangeLanguageModels(e => {
- const persistedModel = e.added?.find(m => m.identifier === state.selectedLanguageModelId);
- if (persistedModel) {
- this._waitForPersistedLanguageModel.clear();
-
- if (persistedModel.metadata.isUserSelectable) {
- this._currentLanguageModel = state.selectedLanguageModelId;
- this._onDidChangeCurrentLanguageModel.fire(this._currentLanguageModel!);
- }
- }
- });
- }
- }
}
logInputHistory(): void {
diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts
index 75eaaa6669736..2ce9404d4e231 100644
--- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts
@@ -60,7 +60,6 @@ const $ = dom.$;
export interface IChatViewState {
inputValue?: string;
inputState?: IChatInputState;
- selectedLanguageModelId?: string;
}
export interface IChatWidgetStyles extends IChatInputStyles {
@@ -1261,8 +1260,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
getViewState(): IChatViewState {
return {
inputValue: this.getInput(),
- inputState: this.inputPart.getViewState(),
- selectedLanguageModelId: this.inputPart.currentLanguageModel,
+ inputState: this.inputPart.getViewState()
};
}
diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts
index 17d1b391b3b0d..796aedb719fd6 100644
--- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts
+++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts
@@ -33,23 +33,22 @@ import { IContextKey, IContextKeyService } from '../../../../platform/contextkey
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { ILogService } from '../../../../platform/log/common/log.js';
+import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js';
+import { IViewsService } from '../../../services/views/common/viewsService.js';
import { showChatView } from '../../chat/browser/chat.js';
-import { IChatViewState, IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js';
+import { IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js';
import { ChatAgentLocation } from '../../chat/common/chatAgents.js';
+import { ChatContextKeys } from '../../chat/common/chatContextKeys.js';
import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js';
import { IChatService } from '../../chat/common/chatService.js';
-import { HunkInformation, Session, StashedSession } from './inlineChatSession.js';
-import { InlineChatError } from './inlineChatSessionServiceImpl.js';
-import { EditModeStrategy, HunkAction, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from './inlineChatStrategies.js';
-import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from '../common/inlineChat.js';
import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js';
-import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js';
-import { IViewsService } from '../../../services/views/common/viewsService.js';
+import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from '../common/inlineChat.js';
import { IInlineChatSavingService } from './inlineChatSavingService.js';
+import { HunkInformation, Session, StashedSession } from './inlineChatSession.js';
import { IInlineChatSessionService } from './inlineChatSessionService.js';
+import { InlineChatError } from './inlineChatSessionServiceImpl.js';
+import { EditModeStrategy, HunkAction, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from './inlineChatStrategies.js';
import { InlineChatZoneWidget } from './inlineChatZoneWidget.js';
-import { ChatContextKeys } from '../../chat/common/chatContextKeys.js';
-import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
export const enum State {
CREATE_SESSION = 'CREATE_SESSION',
@@ -104,8 +103,6 @@ export class InlineChatController implements IEditorContribution {
return editor.getContribution(INLINE_CHAT_ID);
}
- private static readonly _storageKey = 'inlineChatController.state';
-
private _isDisposed: boolean = false;
private readonly _store = new DisposableStore();
@@ -147,7 +144,6 @@ export class InlineChatController implements IEditorContribution {
@IContextKeyService contextKeyService: IContextKeyService,
@IChatService private readonly _chatService: IChatService,
@IEditorService private readonly _editorService: IEditorService,
- @IStorageService private readonly _storageService: IStorageService,
@INotebookEditorService notebookEditorService: INotebookEditorService,
) {
this._ctxVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService);
@@ -411,7 +407,7 @@ export class InlineChatController implements IEditorContribution {
this._sessionStore.add(this._session.wholeRange.onDidChange(handleWholeRangeChange));
handleWholeRangeChange();
- this._ui.value.widget.setChatModel(this._session.chatModel, this._retrieveWidgetState());
+ this._ui.value.widget.setChatModel(this._session.chatModel);
this._updatePlaceholder();
const isModelEmpty = !this._session.chatModel.hasRequests;
@@ -893,11 +889,6 @@ export class InlineChatController implements IEditorContribution {
this._ctxUserDidEdit.reset();
if (this._ui.rawValue) {
- // persist selected LM in memento
- const { selectedLanguageModelId } = this._ui.rawValue.widget.chatWidget.getViewState();
- const state = { selectedLanguageModelId };
- this._storageService.store(InlineChatController._storageKey, state, StorageScope.PROFILE, StorageTarget.USER);
-
this._ui.rawValue.hide();
}
@@ -907,15 +898,6 @@ export class InlineChatController implements IEditorContribution {
}
}
- private _retrieveWidgetState(): IChatViewState | undefined {
- try {
- const state = JSON.parse(this._storageService.get(InlineChatController._storageKey, StorageScope.PROFILE) ?? '{}');
- return state;
- } catch {
- return undefined;
- }
- }
-
private _updateCtxResponseType(): void {
if (!this._session) {
From 7bb46ece483cf5f8e4f66983abdbc5d1bba9ab9e Mon Sep 17 00:00:00 2001
From: Benjamin Pasero
Date: Thu, 21 Nov 2024 12:18:02 +0100
Subject: [PATCH 032/118] chat - welcome updates (#234329)
---
package.json | 2 +-
src/vs/base/common/product.ts | 1 +
.../contrib/chat/browser/chatSetup.contribution.ts | 11 +++++++----
.../contrib/chat/browser/media/chatViewWelcome.css | 3 ---
4 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/package.json b/package.json
index 39a7ab93e251f..9c08b11f0c0f0 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.96.0",
- "distro": "f656a837bb4aa14329dbe98d58c178615e959ae1",
+ "distro": "7d14bf7e9a283e1c9ca8b18ccb0c13274a3757cf",
"author": {
"name": "Microsoft Corporation"
},
diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts
index 3cf230772d624..459ffb75a1021 100644
--- a/src/vs/base/common/product.ts
+++ b/src/vs/base/common/product.ts
@@ -310,6 +310,7 @@ export interface IDefaultChatAgent {
readonly chatWelcomeTitle: string;
readonly documentationUrl: string;
readonly privacyStatementUrl: string;
+ readonly collectionDocumentationUrl: string;
readonly providerId: string;
readonly providerName: string;
readonly providerScopes: string[];
diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
index affafca838a8c..8a24d1e2dd104 100644
--- a/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
@@ -6,7 +6,7 @@
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
-import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
+import { ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js';
import { AuthenticationSession, IAuthenticationService } from '../../../services/authentication/common/authentication.js';
import { IProductService } from '../../../../platform/product/common/productService.js';
import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js';
@@ -47,6 +47,7 @@ const defaultChat = {
chatWelcomeTitle: product.defaultChatAgent?.chatWelcomeTitle ?? '',
documentationUrl: product.defaultChatAgent?.documentationUrl ?? '',
privacyStatementUrl: product.defaultChatAgent?.privacyStatementUrl ?? '',
+ collectionDocumentationUrl: product.defaultChatAgent?.collectionDocumentationUrl ?? '',
providerId: product.defaultChatAgent?.providerId ?? '',
providerName: product.defaultChatAgent?.providerName ?? '',
providerScopes: product.defaultChatAgent?.providerScopes ?? [],
@@ -102,7 +103,7 @@ class ChatSetupContribution extends Disposable implements IWorkbenchContribution
@IProductService private readonly productService: IProductService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionService private readonly extensionService: IExtensionService,
- @IInstantiationService private readonly instantiationService: IInstantiationService
+ @IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
@@ -119,8 +120,10 @@ class ChatSetupContribution extends Disposable implements IWorkbenchContribution
}
private registerChatWelcome(): void {
- const header = localize('setupPreamble1', "{0} is your AI pair programmer that helps you write code faster and smarter.", defaultChat.name);
- const footer = localize('setupPreamble2', "By proceeding you agree to the [Privacy Statement]({0}).", defaultChat.privacyStatementUrl);
+ const header = localize('setupPreamble1', "{0} is your AI pair programmer.", defaultChat.name);
+ const footer = this.telemetryService.telemetryLevel !== TelemetryLevel.NONE ?
+ localize({ key: 'setupPreambleWithOptOut', comment: ['{Locked="]({0})"}'] }, "{0} may use your code snippets for product improvements. Read our [privacy statement]({1}) and learn how to [opt out]({2}).", defaultChat.name, defaultChat.privacyStatementUrl, defaultChat.collectionDocumentationUrl) :
+ localize({ key: 'setupPreambleWithoutOptOut', comment: ['{Locked="]({0})"}'] }, "By proceeding you agree to our [privacy statement]({0}).", defaultChat.privacyStatementUrl);
// Setup: Triggered (signed-out)
Registry.as(ChatViewsWelcomeExtensions.ChatViewsWelcomeRegistry).register({
diff --git a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css
index 4e54182995cc0..e7024c5ba1746 100644
--- a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css
+++ b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css
@@ -62,7 +62,6 @@ div.chat-welcome-view {
& > .chat-welcome-view-progress {
display: flex;
gap: 6px;
- color: var(--vscode-descriptionForeground);
text-align: center;
max-width: 350px;
padding: 0 20px;
@@ -70,7 +69,6 @@ div.chat-welcome-view {
}
& > .chat-welcome-view-message {
- color: var(--vscode-descriptionForeground);
text-align: center;
max-width: 350px;
padding: 0 20px;
@@ -88,7 +86,6 @@ div.chat-welcome-view {
}
& > .chat-welcome-view-tips {
- color: var(--vscode-descriptionForeground);
max-width: 250px;
margin-top: 10px;
From 157567fd8f3fbfc09de0ca643d4b51ce28fb76df Mon Sep 17 00:00:00 2001
From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com>
Date: Thu, 21 Nov 2024 12:21:34 +0100
Subject: [PATCH 033/118] Git - add git blame hover (status bar item, editor
decoration) (#234338)
---
extensions/git/src/blame.ts | 50 +++++++++++++++++++++++++++++++---
extensions/git/src/commands.ts | 11 +++++++-
2 files changed, 56 insertions(+), 5 deletions(-)
diff --git a/extensions/git/src/blame.ts b/extensions/git/src/blame.ts
index d116922d9bf4a..10c8e0d98f21e 100644
--- a/extensions/git/src/blame.ts
+++ b/extensions/git/src/blame.ts
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command } from 'vscode';
+import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command, MarkdownString } from 'vscode';
import { Model } from './model';
import { dispose, fromNow, IDisposable, pathEquals } from './util';
import { Repository } from './repository';
@@ -86,6 +86,41 @@ function processTextEditorChangesWithBlameInformation(blameInformation: BlameInf
return changesWithBlameInformation;
}
+function getBlameInformationHover(documentUri: Uri, blameInformation: BlameInformation | string): MarkdownString {
+ if (typeof blameInformation === 'string') {
+ return new MarkdownString(blameInformation, true);
+ }
+
+ const markdownString = new MarkdownString();
+ markdownString.supportThemeIcons = true;
+ markdownString.isTrusted = true;
+
+ if (blameInformation.authorName) {
+ markdownString.appendMarkdown(`$(account) **${blameInformation.authorName}**`);
+
+ if (blameInformation.date) {
+ const dateString = new Date(blameInformation.date).toLocaleString(undefined, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' });
+ markdownString.appendMarkdown(`, $(history) ${fromNow(blameInformation.date, true, true)} (${dateString})`);
+ }
+
+ markdownString.appendMarkdown('\n\n');
+ }
+
+ markdownString.appendMarkdown(`${blameInformation.message}\n\n`);
+ markdownString.appendMarkdown(`---\n\n`);
+
+ markdownString.appendMarkdown(`[$(eye) View Commit](command:git.blameStatusBarItem.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, blameInformation.id]))})`);
+ markdownString.appendMarkdown(' | ');
+ markdownString.appendMarkdown(`[$(copy) ${blameInformation.id.substring(0, 8)}](command:git.blameStatusBarItem.copyContent?${encodeURIComponent(JSON.stringify(blameInformation.id))})`);
+
+ if (blameInformation.message) {
+ markdownString.appendMarkdown(' ');
+ markdownString.appendMarkdown(`[$(copy) Message](command:git.blameStatusBarItem.copyContent?${encodeURIComponent(JSON.stringify(blameInformation.message))})`);
+ }
+
+ return markdownString;
+}
+
interface RepositoryBlameInformation {
readonly commit: string; /* commit used for blame information */
readonly blameInformation: Map;
@@ -314,16 +349,19 @@ class GitBlameEditorDecoration {
const contentText = typeof blame.blameInformation === 'string'
? blame.blameInformation
: `${blame.blameInformation.message ?? ''}, ${blame.blameInformation.authorName ?? ''} (${fromNow(blame.blameInformation.date ?? Date.now(), true, true)})`;
- return this._createDecoration(blame.lineNumber, contentText);
+ const hoverMessage = getBlameInformationHover(textEditor.document.uri, blame.blameInformation);
+
+ return this._createDecoration(blame.lineNumber, contentText, hoverMessage);
});
textEditor.setDecorations(this._decorationType, decorations);
}
- private _createDecoration(lineNumber: number, contentText: string): DecorationOptions {
+ private _createDecoration(lineNumber: number, contentText: string, hoverMessage: MarkdownString): DecorationOptions {
const position = new Position(lineNumber, Number.MAX_SAFE_INTEGER);
return {
+ hoverMessage,
range: new Range(position, position),
renderOptions: {
after: {
@@ -389,6 +427,7 @@ class GitBlameStatusBarItem {
if (!this._statusBarItem) {
this._statusBarItem = window.createStatusBarItem('git.blame', StatusBarAlignment.Right, 200);
+ this._statusBarItem.name = l10n.t('Git Blame Information');
this._disposables.push(this._statusBarItem);
}
@@ -400,11 +439,14 @@ class GitBlameStatusBarItem {
if (typeof blameInformation[0].blameInformation === 'string') {
this._statusBarItem.text = `$(git-commit) ${blameInformation[0].blameInformation}`;
+ this._statusBarItem.tooltip = getBlameInformationHover(textEditor.document.uri, blameInformation[0].blameInformation);
+ this._statusBarItem.command = undefined;
} else {
this._statusBarItem.text = `$(git-commit) ${blameInformation[0].blameInformation.authorName ?? ''} (${fromNow(blameInformation[0].blameInformation.date ?? new Date(), true, true)})`;
+ this._statusBarItem.tooltip = getBlameInformationHover(textEditor.document.uri, blameInformation[0].blameInformation);
this._statusBarItem.command = {
title: l10n.t('View Commit'),
- command: 'git.statusBar.viewCommit',
+ command: 'git.blameStatusBarItem.viewCommit',
arguments: [textEditor.document.uri, blameInformation[0].blameInformation.id]
} satisfies Command;
}
diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts
index 84d9b30f7b722..43518a7784393 100644
--- a/extensions/git/src/commands.ts
+++ b/extensions/git/src/commands.ts
@@ -4307,7 +4307,7 @@ export class CommandCenter {
env.clipboard.writeText(historyItem.message);
}
- @command('git.statusBar.viewCommit', { repository: true })
+ @command('git.blameStatusBarItem.viewCommit', { repository: true })
async viewStatusBarCommit(repository: Repository, historyItemId: string): Promise {
if (!repository || !historyItemId) {
return;
@@ -4325,6 +4325,15 @@ export class CommandCenter {
await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources });
}
+ @command('git.blameStatusBarItem.copyContent')
+ async blameStatusBarCopyContent(content: string): Promise {
+ if (typeof content !== 'string') {
+ return;
+ }
+
+ env.clipboard.writeText(content);
+ }
+
private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any {
const result = (...args: any[]) => {
let result: Promise;
From ec8a0199a155a768456529fb123c8e5c81e7216c Mon Sep 17 00:00:00 2001
From: Benjamin Christopher Simmonds
<44439583+benibenj@users.noreply.github.com>
Date: Thu, 21 Nov 2024 12:27:50 +0100
Subject: [PATCH 034/118] Restore tab newline fix in editor tabs (#234340)
bring back tab newline fix
---
.../browser/parts/editor/media/editortabscontrol.css | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css
index 39d489d4c41dc..aa684a520bb43 100644
--- a/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css
+++ b/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css
@@ -15,6 +15,11 @@
flex: 1;
}
+.monaco-workbench .part.editor > .content .editor-group-container > .title .title-label .label-name,
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label .label-name {
+ white-space: nowrap;
+}
+
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-label a,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label a {
font-size: 13px;
From 268d4cf13befe845b59e2adbe213aae14c1b54d4 Mon Sep 17 00:00:00 2001
From: Benjamin Pasero
Date: Thu, 21 Nov 2024 13:15:29 +0100
Subject: [PATCH 035/118] chat - tweaks to welcome (#234343)
---
.../chat/browser/chatSetup.contribution.ts | 10 ++++++----
.../chat/browser/media/chatViewWelcome.css | 15 ++++++---------
.../viewsWelcome/chatViewWelcomeController.ts | 15 +++------------
.../chat/browser/viewsWelcome/chatViewsWelcome.ts | 2 +-
4 files changed, 16 insertions(+), 26 deletions(-)
diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
index 8a24d1e2dd104..fb6f22da1105a 100644
--- a/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
@@ -171,12 +171,13 @@ class ChatSetupContribution extends Disposable implements IWorkbenchContribution
ChatContextKeys.Setup.installed.negate()
)!,
icon: defaultChat.icon,
- progress: localize('setupChatSigningIn', "Signing in to {0}...", defaultChat.providerName),
+ disableFirstLinkToButton: true,
content: new MarkdownString([
header,
+ localize('setupChatSigningIn', "$(loading~spin) Signing in to {0}...", defaultChat.providerName),
footer,
`[${localize('learnMore', "Learn More")}](${defaultChat.documentationUrl})`,
- ].join('\n\n'), { isTrusted: true }),
+ ].join('\n\n'), { isTrusted: true, supportThemeIcons: true }),
});
// Setup: Installing
@@ -184,12 +185,13 @@ class ChatSetupContribution extends Disposable implements IWorkbenchContribution
title: defaultChat.chatWelcomeTitle,
when: ChatContextKeys.Setup.installing,
icon: defaultChat.icon,
- progress: localize('setupChatInstalling', "Setting up Chat for you..."),
+ disableFirstLinkToButton: true,
content: new MarkdownString([
header,
+ localize('setupChatInstalling', "$(loading~spin) Setting up Chat for you..."),
footer,
`[${localize('learnMore', "Learn More")}](${defaultChat.documentationUrl})`,
- ].join('\n\n'), { isTrusted: true }),
+ ].join('\n\n'), { isTrusted: true, supportThemeIcons: true }),
});
}
diff --git a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css
index e7024c5ba1746..dffc746a0072c 100644
--- a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css
+++ b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css
@@ -59,15 +59,6 @@ div.chat-welcome-view {
font-size: 11px;
}
- & > .chat-welcome-view-progress {
- display: flex;
- gap: 6px;
- text-align: center;
- max-width: 350px;
- padding: 0 20px;
- margin-top: 20px;
- }
-
& > .chat-welcome-view-message {
text-align: center;
max-width: 350px;
@@ -77,6 +68,12 @@ div.chat-welcome-view {
a {
color: var(--vscode-textLink-foreground);
}
+
+ .codicon[class*='codicon-'] {
+ font-size: 13px;
+ line-height: 1.4em;
+ vertical-align: bottom;
+ }
}
.monaco-button {
diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts
index 09cbdccb926f2..c68a5442d8cca 100644
--- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts
+++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts
@@ -17,7 +17,6 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com
import { ILogService } from '../../../../../platform/log/common/log.js';
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js';
-import { spinningLoading } from '../../../../../platform/theme/common/iconRegistry.js';
import { ChatAgentLocation } from '../../common/chatAgents.js';
import { chatViewsWelcomeRegistry, IChatViewsWelcomeDescriptor } from './chatViewsWelcome.js';
@@ -89,7 +88,7 @@ export class ChatViewWelcomeController extends Disposable {
icon: enabledDescriptor.icon,
title: enabledDescriptor.title,
message: enabledDescriptor.content,
- progress: enabledDescriptor.progress
+ disableFirstLinkToButton: enabledDescriptor.disableFirstLinkToButton,
};
const welcomeView = this.renderDisposables.add(this.instantiationService.createInstance(ChatViewWelcomePart, content, { firstLinkToButton: true, location: this.location }));
this.element!.appendChild(welcomeView.element);
@@ -104,7 +103,7 @@ export interface IChatViewWelcomeContent {
icon?: ThemeIcon;
title: string;
message: IMarkdownString;
- progress?: string;
+ disableFirstLinkToButton?: boolean;
tips?: IMarkdownString;
}
@@ -144,7 +143,7 @@ export class ChatViewWelcomePart extends Disposable {
title.textContent = content.title;
const renderer = this.instantiationService.createInstance(MarkdownRenderer, {});
const messageResult = this._register(renderer.render(content.message));
- const firstLink = (options?.firstLinkToButton && !content.progress) ? messageResult.element.querySelector('a') : undefined;
+ const firstLink = options?.firstLinkToButton && !content.disableFirstLinkToButton ? messageResult.element.querySelector('a') : undefined;
if (firstLink) {
const target = firstLink.getAttribute('data-href');
const button = this._register(new Button(firstLink.parentElement!, defaultButtonStyles));
@@ -159,14 +158,6 @@ export class ChatViewWelcomePart extends Disposable {
dom.append(message, messageResult.element);
- if (content.progress) {
- const progress = dom.append(this.element, $('.chat-welcome-view-progress'));
- progress.appendChild(renderIcon(spinningLoading));
-
- const progressLabel = dom.append(progress, $('span'));
- progressLabel.textContent = content.progress;
- }
-
if (content.tips) {
const tips = dom.append(this.element, $('.chat-welcome-view-tips'));
const tipsResult = this._register(renderer.render(content.tips));
diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcome.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcome.ts
index d522a1029d2bc..b08baae1460bc 100644
--- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcome.ts
+++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcome.ts
@@ -17,7 +17,7 @@ export interface IChatViewsWelcomeDescriptor {
icon?: ThemeIcon;
title: string;
content: IMarkdownString;
- progress?: string;
+ disableFirstLinkToButton?: boolean;
when: ContextKeyExpression;
}
From 9508be851891834c4036da28461824c664dfa2c0 Mon Sep 17 00:00:00 2001
From: Martin Aeschlimann
Date: Thu, 21 Nov 2024 13:23:29 +0100
Subject: [PATCH 036/118] edits undo: chat entries are no longer hidden after
reload (#234345)
edits: chat entries are no longer hidden after reload
---
.../chat/browser/actions/chatClearActions.ts | 8 ---
.../chatEditingModifiedFileEntry.ts | 25 ++++----
.../browser/chatEditing/chatEditingSession.ts | 63 ++++++++++---------
.../contrib/chat/common/chatEditingService.ts | 1 -
4 files changed, 45 insertions(+), 52 deletions(-)
diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts
index b84f3df93e0f7..746d4d74e3dc0 100644
--- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts
+++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts
@@ -265,15 +265,11 @@ export function registerNewChatActions() {
async run(accessor: ServicesAccessor, ...args: any[]) {
const chatEditingService = accessor.get(IChatEditingService);
- const chatWidgetService = accessor.get(IChatWidgetService);
const currentEditingSession = chatEditingService.currentEditingSession;
if (!currentEditingSession) {
return;
}
-
- const widget = chatWidgetService.getWidgetBySessionId(currentEditingSession.chatSessionId);
await currentEditingSession.undoInteraction();
- widget?.viewModel?.model.disableRequests(currentEditingSession.hiddenRequestIds.get());
}
});
@@ -297,15 +293,11 @@ export function registerNewChatActions() {
async run(accessor: ServicesAccessor, ...args: any[]) {
const chatEditingService = accessor.get(IChatEditingService);
- const chatWidgetService = accessor.get(IChatWidgetService);
const currentEditingSession = chatEditingService.currentEditingSession;
if (!currentEditingSession) {
return;
}
-
- const widget = chatWidgetService.getWidgetBySessionId(currentEditingSession.chatSessionId);
await chatEditingService.currentEditingSession?.redoInteraction();
- widget?.viewModel?.model.disableRequests(currentEditingSession.hiddenRequestIds.get());
}
});
diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts
index 83766889f96d4..defe157b7b7fc 100644
--- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts
@@ -40,7 +40,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie
public readonly entryId = `${ChatEditingModifiedFileEntry.scheme}::${++ChatEditingModifiedFileEntry.lastEntryId}`;
private readonly docSnapshot: ITextModel;
- private readonly originalContent;
+ public readonly initialContent: string;
private readonly doc: ITextModel;
private readonly docFileEditorModel: IResolvedTextFileEditorModel;
private _allEditsAreFromUs: boolean = true;
@@ -121,7 +121,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie
private readonly _multiDiffEntryDelegate: { collapse: (transaction: ITransaction | undefined) => void },
private _telemetryInfo: IModifiedEntryTelemetryInfo,
kind: ChatEditKind,
- originalContent: string | undefined,
+ initialContent: string | undefined,
@IModelService modelService: IModelService,
@ITextModelService textModelService: ITextModelService,
@ILanguageService languageService: ILanguageService,
@@ -137,10 +137,10 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie
this.docFileEditorModel = this._register(resourceRef).object as IResolvedTextFileEditorModel;
this.doc = resourceRef.object.textEditorModel;
- this.originalContent = originalContent ?? this.doc.getValue();
+ this.initialContent = initialContent ?? this.doc.getValue();
const docSnapshot = this.docSnapshot = this._register(
modelService.createModel(
- createTextBufferFactoryFromSnapshot(originalContent ? stringToSnapshot(originalContent) : this.doc.createSnapshot()),
+ createTextBufferFactoryFromSnapshot(initialContent ? stringToSnapshot(initialContent) : this.doc.createSnapshot()),
languageService.createById(this.doc.getLanguageId()),
ChatEditingTextModelContentProvider.getFileURI(this.entryId, this.modifiedURI.path),
false
@@ -192,7 +192,6 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie
telemetryInfo: this._telemetryInfo
};
}
-
restoreFromSnapshot(snapshot: ISnapshotEntry) {
this._stateObs.set(snapshot.state, undefined);
this.docSnapshot.setValue(snapshot.original);
@@ -200,8 +199,8 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie
this._edit = snapshot.originalToCurrentEdit;
}
- resetToInitialValue(value: string) {
- this._setDocValue(value);
+ resetToInitialValue() {
+ this._setDocValue(this.initialContent);
}
acceptStreamingEditsStart(tx: ITransaction) {
@@ -262,7 +261,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie
}
if (!this.isCurrentlyBeingModified.get()) {
- const didResetToOriginalContent = this.doc.getValue() === this.originalContent;
+ const didResetToOriginalContent = this.doc.getValue() === this.initialContent;
const currentState = this._stateObs.get();
switch (currentState) {
case WorkingSetEntryState.Modified:
@@ -427,11 +426,11 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie
}
export interface IModifiedEntryTelemetryInfo {
- agentId: string | undefined;
- command: string | undefined;
- sessionId: string;
- requestId: string;
- result: IChatAgentResult | undefined;
+ readonly agentId: string | undefined;
+ readonly command: string | undefined;
+ readonly sessionId: string;
+ readonly requestId: string;
+ readonly result: IChatAgentResult | undefined;
}
export interface ISnapshotEntry {
diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts
index d2df122bba607..261f2b54db498 100644
--- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts
@@ -43,6 +43,7 @@ import { IEnvironmentService } from '../../../../../platform/environment/common/
import { VSBuffer } from '../../../../../base/common/buffer.js';
import { IOffsetEdit, ISingleOffsetEdit, OffsetEdit } from '../../../../../editor/common/core/offsetEdit.js';
import { ILogService } from '../../../../../platform/log/common/log.js';
+import { IChatService } from '../../common/chatService.js';
const STORAGE_CONTENTS_FOLDER = 'contents';
const STORAGE_STATE_FILE = 'state.json';
@@ -56,8 +57,6 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
* Contains the contents of a file when the AI first began doing edits to it.
*/
private readonly _initialFileContents = new ResourceMap();
- private readonly _snapshots = new Map();
-
private readonly _filesToSkipCreating = new ResourceSet();
private readonly _entriesObs = observableValue(this, []);
@@ -144,6 +143,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
@IChatAgentService private readonly _chatAgentService: IChatAgentService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@ILogService private readonly _logService: ILogService,
+ @IChatService private readonly _chatService: IChatService,
) {
super();
@@ -211,10 +211,13 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
}
}
+ private _findSnapshot(requestId: string): IChatEditingSessionSnapshot | undefined {
+ return this._linearHistory.get().find(s => s.requestId === requestId);
+ }
+
public createSnapshot(requestId: string | undefined): void {
const snapshot = this._createSnapshot(requestId);
if (requestId) {
- this._snapshots.set(requestId, snapshot);
for (const workingSetItem of this._workingSet.keys()) {
this._workingSet.set(workingSetItem, { state: WorkingSetEntryState.Sent });
}
@@ -248,7 +251,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
}
public async getSnapshotModel(requestId: string, snapshotUri: URI): Promise {
- const entries = this._snapshots.get(requestId)?.entries;
+ const entries = this._findSnapshot(requestId)?.entries;
if (!entries) {
return null;
}
@@ -262,7 +265,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
}
public getSnapshot(requestId: string, uri: URI) {
- const snapshot = this._snapshots.get(requestId);
+ const snapshot = this._findSnapshot(requestId);
const snapshotEntries = snapshot?.entries;
return snapshotEntries?.get(uri);
}
@@ -273,7 +276,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
private _pendingSnapshot: IChatEditingSessionSnapshot | undefined;
public async restoreSnapshot(requestId: string | undefined): Promise {
if (requestId !== undefined) {
- const snapshot = this._snapshots.get(requestId);
+ const snapshot = this._findSnapshot(requestId);
if (snapshot) {
if (!this._pendingSnapshot) {
// Create and save a pending snapshot
@@ -301,10 +304,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
for (const entry of this._entriesObs.get()) {
const snapshotEntry = snapshot.entries.get(entry.modifiedURI);
if (!snapshotEntry) {
- const initialContents = this._initialFileContents.get(entry.modifiedURI);
- if (typeof initialContents === 'string') {
- entry.resetToInitialValue(initialContents);
- }
+ entry.resetToInitialValue();
entry.dispose();
}
}
@@ -313,8 +313,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
// Restore all entries from the snapshot
for (const snapshotEntry of snapshot.entries.values()) {
const entry = await this._getOrCreateModifiedFileEntry(snapshotEntry.resource, snapshotEntry.telemetryInfo);
- entry.restoreFromSnapshot(snapshotEntry);
- entriesArr.push(entry);
+ entry.restoreFromSnapshot(snapshotEntry); entriesArr.push(entry);
}
this._entriesObs.set(entriesArr, undefined);
@@ -412,12 +411,14 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
async _performStop(): Promise {
// Close out all open files
- await Promise.allSettled(this._editorGroupsService.groups.map(async (g) => {
- return Promise.allSettled(g.editors.map(async (e) => {
- if (e instanceof MultiDiffEditorInput || e instanceof DiffEditorInput && (e.original.resource?.scheme === ChatEditingModifiedFileEntry.scheme || e.original.resource?.scheme === ChatEditingTextModelContentProvider.scheme)) {
+ const schemes = [ChatEditingModifiedFileEntry.scheme, ChatEditingTextModelContentProvider.scheme];
+ await Promise.allSettled(this._editorGroupsService.groups.flatMap(async (g) => {
+ return g.editors.map(async (e) => {
+ if ((e instanceof MultiDiffEditorInput && e.initialResources?.some(r => r.originalUri && schemes.indexOf(r.originalUri.scheme) !== -1))
+ || (e instanceof DiffEditorInput && e.original.resource && schemes.indexOf(e.original.resource.scheme) !== -1)) {
await g.closeEditor(e);
}
- }));
+ });
}));
// delete the persisted editing session state
@@ -501,6 +502,8 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
const previousSnapshot = linearHistory[linearHistoryIndex - 1];
await this.restoreSnapshot(previousSnapshot.requestId);
this._linearHistoryIndex.set(linearHistoryIndex - 1, undefined);
+ this._updateRequestHiddenState();
+
}
async redoInteraction(): Promise {
@@ -515,6 +518,12 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
}
await this.restoreSnapshot(nextSnapshot.requestId);
this._linearHistoryIndex.set(linearHistoryIndex + 1, undefined);
+ this._updateRequestHiddenState();
+ }
+
+ private _updateRequestHiddenState() {
+ const hiddenRequestIds = this._linearHistory.get().slice(this._linearHistoryIndex.get()).map(s => s.requestId).filter((r): r is string => !!r);
+ this._chatService.getSession(this.chatSessionId)?.disableRequests(hiddenRequestIds);
}
private async _acceptStreamingEditsStart(): Promise {
@@ -578,12 +587,11 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
}
return existingEntry;
}
-
- const originalContent = this._initialFileContents.get(resource);
+ const initialContent = this._initialFileContents.get(resource);
// This gets manually disposed in .dispose() or in .restoreSnapshot()
- const entry = await this._createModifiedFileEntry(resource, responseModel, false, originalContent);
- if (!originalContent) {
- this._initialFileContents.set(resource, entry.modifiedModel.getValue());
+ const entry = await this._createModifiedFileEntry(resource, responseModel, false, initialContent);
+ if (!initialContent) {
+ this._initialFileContents.set(resource, entry.initialContent);
}
// If an entry is deleted e.g. reverting a created file,
// remove it from the entries and don't show it in the working set anymore
@@ -602,11 +610,11 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
return entry;
}
- private async _createModifiedFileEntry(resource: URI, responseModel: IModifiedEntryTelemetryInfo, mustExist = false, originalContent: string | undefined): Promise {
+ private async _createModifiedFileEntry(resource: URI, responseModel: IModifiedEntryTelemetryInfo, mustExist = false, initialContent: string | undefined): Promise {
try {
const ref = await this._textModelService.createModelReference(resource);
- return this._instantiationService.createInstance(ChatEditingModifiedFileEntry, ref, { collapse: (transaction: ITransaction | undefined) => this._collapse(resource, transaction) }, responseModel, mustExist ? ChatEditKind.Created : ChatEditKind.Modified, originalContent);
+ return this._instantiationService.createInstance(ChatEditingModifiedFileEntry, ref, { collapse: (transaction: ITransaction | undefined) => this._collapse(resource, transaction) }, responseModel, mustExist ? ChatEditKind.Created : ChatEditKind.Modified, initialContent);
} catch (err) {
if (mustExist) {
throw err;
@@ -614,7 +622,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
// this file does not exist yet, create it and try again
await this._bulkEditService.apply({ edits: [{ newResource: resource }] });
this._editorService.openEditor({ resource, options: { inactive: true, preserveFocus: true, pinned: true } });
- return this._createModifiedFileEntry(resource, responseModel, true, originalContent);
+ return this._createModifiedFileEntry(resource, responseModel, true, initialContent);
}
}
@@ -680,15 +688,9 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
this._filesToSkipCreating.clear();
this._initialFileContents.clear();
- this._snapshots.clear();
this._pendingSnapshot = undefined;
const snapshotsFromHistory = await Promise.all(data.linearHistory.map(deserializeChatEditingSessionSnapshot));
- for (const snapshot of snapshotsFromHistory) {
- if (snapshot.requestId) {
- this._snapshots.set(snapshot.requestId, snapshot);
- }
- }
data.filesToSkipCreating.forEach((uriStr: string) => {
this._filesToSkipCreating.add(URI.parse(uriStr));
});
@@ -700,6 +702,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
const pendingSnapshot = await deserializeChatEditingSessionSnapshot(data.pendingSnapshot);
this._restoreSnapshot(pendingSnapshot);
this._state.set(ChatEditingSessionState.Idle, undefined);
+ this._updateRequestHiddenState();
return true;
} catch (e) {
this._logService.error(`Error restoring chat editing session from ${storageLocation.toString()}`, e);
diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts
index 731d6d0bfb172..5b3b80e085283 100644
--- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts
+++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts
@@ -74,7 +74,6 @@ export interface IChatEditingSession {
readonly onDidDispose: Event;
readonly state: IObservable;
readonly entries: IObservable;
- readonly hiddenRequestIds: IObservable;
readonly workingSet: ResourceMap;
readonly isVisible: boolean;
addFileToWorkingSet(uri: URI, description?: string, kind?: WorkingSetEntryState.Transient | WorkingSetEntryState.Suggested): void;
From 07d84aae4a81d32cf66954a32564c680f7584d46 Mon Sep 17 00:00:00 2001
From: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
Date: Thu, 21 Nov 2024 07:03:04 -0800
Subject: [PATCH 037/118] Add CSS named color support to parse function
---
src/vs/base/common/color.ts | 157 +++++++++++++++++++++++++-
src/vs/base/test/common/color.test.ts | 153 +++++++++++++++++++++++++
2 files changed, 309 insertions(+), 1 deletion(-)
diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts
index a4faedd349956..02d98667fb71d 100644
--- a/src/vs/base/common/color.ts
+++ b/src/vs/base/common/color.ts
@@ -664,7 +664,162 @@ export namespace Color {
return new Color(new RGBA(r, g, b));
}
// TODO: Support more formats
- return null;
+ return parseNamedKeyword(css);
+ }
+
+ function parseNamedKeyword(css: string): Color | null {
+ // https://drafts.csswg.org/css-color/#named-colors
+ switch (css) {
+ case 'aliceblue': return new Color(new RGBA(240, 248, 255, 1));
+ case 'antiquewhite': return new Color(new RGBA(250, 235, 215, 1));
+ case 'aqua': return new Color(new RGBA(0, 255, 255, 1));
+ case 'aquamarine': return new Color(new RGBA(127, 255, 212, 1));
+ case 'azure': return new Color(new RGBA(240, 255, 255, 1));
+ case 'beige': return new Color(new RGBA(245, 245, 220, 1));
+ case 'bisque': return new Color(new RGBA(255, 228, 196, 1));
+ case 'black': return new Color(new RGBA(0, 0, 0, 1));
+ case 'blanchedalmond': return new Color(new RGBA(255, 235, 205, 1));
+ case 'blue': return new Color(new RGBA(0, 0, 255, 1));
+ case 'blueviolet': return new Color(new RGBA(138, 43, 226, 1));
+ case 'brown': return new Color(new RGBA(165, 42, 42, 1));
+ case 'burlywood': return new Color(new RGBA(222, 184, 135, 1));
+ case 'cadetblue': return new Color(new RGBA(95, 158, 160, 1));
+ case 'chartreuse': return new Color(new RGBA(127, 255, 0, 1));
+ case 'chocolate': return new Color(new RGBA(210, 105, 30, 1));
+ case 'coral': return new Color(new RGBA(255, 127, 80, 1));
+ case 'cornflowerblue': return new Color(new RGBA(100, 149, 237, 1));
+ case 'cornsilk': return new Color(new RGBA(255, 248, 220, 1));
+ case 'crimson': return new Color(new RGBA(220, 20, 60, 1));
+ case 'cyan': return new Color(new RGBA(0, 255, 255, 1));
+ case 'darkblue': return new Color(new RGBA(0, 0, 139, 1));
+ case 'darkcyan': return new Color(new RGBA(0, 139, 139, 1));
+ case 'darkgoldenrod': return new Color(new RGBA(184, 134, 11, 1));
+ case 'darkgray': return new Color(new RGBA(169, 169, 169, 1));
+ case 'darkgreen': return new Color(new RGBA(0, 100, 0, 1));
+ case 'darkgrey': return new Color(new RGBA(169, 169, 169, 1));
+ case 'darkkhaki': return new Color(new RGBA(189, 183, 107, 1));
+ case 'darkmagenta': return new Color(new RGBA(139, 0, 139, 1));
+ case 'darkolivegreen': return new Color(new RGBA(85, 107, 47, 1));
+ case 'darkorange': return new Color(new RGBA(255, 140, 0, 1));
+ case 'darkorchid': return new Color(new RGBA(153, 50, 204, 1));
+ case 'darkred': return new Color(new RGBA(139, 0, 0, 1));
+ case 'darksalmon': return new Color(new RGBA(233, 150, 122, 1));
+ case 'darkseagreen': return new Color(new RGBA(143, 188, 143, 1));
+ case 'darkslateblue': return new Color(new RGBA(72, 61, 139, 1));
+ case 'darkslategray': return new Color(new RGBA(47, 79, 79, 1));
+ case 'darkslategrey': return new Color(new RGBA(47, 79, 79, 1));
+ case 'darkturquoise': return new Color(new RGBA(0, 206, 209, 1));
+ case 'darkviolet': return new Color(new RGBA(148, 0, 211, 1));
+ case 'deeppink': return new Color(new RGBA(255, 20, 147, 1));
+ case 'deepskyblue': return new Color(new RGBA(0, 191, 255, 1));
+ case 'dimgray': return new Color(new RGBA(105, 105, 105, 1));
+ case 'dimgrey': return new Color(new RGBA(105, 105, 105, 1));
+ case 'dodgerblue': return new Color(new RGBA(30, 144, 255, 1));
+ case 'firebrick': return new Color(new RGBA(178, 34, 34, 1));
+ case 'floralwhite': return new Color(new RGBA(255, 250, 240, 1));
+ case 'forestgreen': return new Color(new RGBA(34, 139, 34, 1));
+ case 'fuchsia': return new Color(new RGBA(255, 0, 255, 1));
+ case 'gainsboro': return new Color(new RGBA(220, 220, 220, 1));
+ case 'ghostwhite': return new Color(new RGBA(248, 248, 255, 1));
+ case 'gold': return new Color(new RGBA(255, 215, 0, 1));
+ case 'goldenrod': return new Color(new RGBA(218, 165, 32, 1));
+ case 'gray': return new Color(new RGBA(128, 128, 128, 1));
+ case 'green': return new Color(new RGBA(0, 128, 0, 1));
+ case 'greenyellow': return new Color(new RGBA(173, 255, 47, 1));
+ case 'grey': return new Color(new RGBA(128, 128, 128, 1));
+ case 'honeydew': return new Color(new RGBA(240, 255, 240, 1));
+ case 'hotpink': return new Color(new RGBA(255, 105, 180, 1));
+ case 'indianred': return new Color(new RGBA(205, 92, 92, 1));
+ case 'indigo': return new Color(new RGBA(75, 0, 130, 1));
+ case 'ivory': return new Color(new RGBA(255, 255, 240, 1));
+ case 'khaki': return new Color(new RGBA(240, 230, 140, 1));
+ case 'lavender': return new Color(new RGBA(230, 230, 250, 1));
+ case 'lavenderblush': return new Color(new RGBA(255, 240, 245, 1));
+ case 'lawngreen': return new Color(new RGBA(124, 252, 0, 1));
+ case 'lemonchiffon': return new Color(new RGBA(255, 250, 205, 1));
+ case 'lightblue': return new Color(new RGBA(173, 216, 230, 1));
+ case 'lightcoral': return new Color(new RGBA(240, 128, 128, 1));
+ case 'lightcyan': return new Color(new RGBA(224, 255, 255, 1));
+ case 'lightgoldenrodyellow': return new Color(new RGBA(250, 250, 210, 1));
+ case 'lightgray': return new Color(new RGBA(211, 211, 211, 1));
+ case 'lightgreen': return new Color(new RGBA(144, 238, 144, 1));
+ case 'lightgrey': return new Color(new RGBA(211, 211, 211, 1));
+ case 'lightpink': return new Color(new RGBA(255, 182, 193, 1));
+ case 'lightsalmon': return new Color(new RGBA(255, 160, 122, 1));
+ case 'lightseagreen': return new Color(new RGBA(32, 178, 170, 1));
+ case 'lightskyblue': return new Color(new RGBA(135, 206, 250, 1));
+ case 'lightslategray': return new Color(new RGBA(119, 136, 153, 1));
+ case 'lightslategrey': return new Color(new RGBA(119, 136, 153, 1));
+ case 'lightsteelblue': return new Color(new RGBA(176, 196, 222, 1));
+ case 'lightyellow': return new Color(new RGBA(255, 255, 224, 1));
+ case 'lime': return new Color(new RGBA(0, 255, 0, 1));
+ case 'limegreen': return new Color(new RGBA(50, 205, 50, 1));
+ case 'linen': return new Color(new RGBA(250, 240, 230, 1));
+ case 'magenta': return new Color(new RGBA(255, 0, 255, 1));
+ case 'maroon': return new Color(new RGBA(128, 0, 0, 1));
+ case 'mediumaquamarine': return new Color(new RGBA(102, 205, 170, 1));
+ case 'mediumblue': return new Color(new RGBA(0, 0, 205, 1));
+ case 'mediumorchid': return new Color(new RGBA(186, 85, 211, 1));
+ case 'mediumpurple': return new Color(new RGBA(147, 112, 219, 1));
+ case 'mediumseagreen': return new Color(new RGBA(60, 179, 113, 1));
+ case 'mediumslateblue': return new Color(new RGBA(123, 104, 238, 1));
+ case 'mediumspringgreen': return new Color(new RGBA(0, 250, 154, 1));
+ case 'mediumturquoise': return new Color(new RGBA(72, 209, 204, 1));
+ case 'mediumvioletred': return new Color(new RGBA(199, 21, 133, 1));
+ case 'midnightblue': return new Color(new RGBA(25, 25, 112, 1));
+ case 'mintcream': return new Color(new RGBA(245, 255, 250, 1));
+ case 'mistyrose': return new Color(new RGBA(255, 228, 225, 1));
+ case 'moccasin': return new Color(new RGBA(255, 228, 181, 1));
+ case 'navajowhite': return new Color(new RGBA(255, 222, 173, 1));
+ case 'navy': return new Color(new RGBA(0, 0, 128, 1));
+ case 'oldlace': return new Color(new RGBA(253, 245, 230, 1));
+ case 'olive': return new Color(new RGBA(128, 128, 0, 1));
+ case 'olivedrab': return new Color(new RGBA(107, 142, 35, 1));
+ case 'orange': return new Color(new RGBA(255, 165, 0, 1));
+ case 'orangered': return new Color(new RGBA(255, 69, 0, 1));
+ case 'orchid': return new Color(new RGBA(218, 112, 214, 1));
+ case 'palegoldenrod': return new Color(new RGBA(238, 232, 170, 1));
+ case 'palegreen': return new Color(new RGBA(152, 251, 152, 1));
+ case 'paleturquoise': return new Color(new RGBA(175, 238, 238, 1));
+ case 'palevioletred': return new Color(new RGBA(219, 112, 147, 1));
+ case 'papayawhip': return new Color(new RGBA(255, 239, 213, 1));
+ case 'peachpuff': return new Color(new RGBA(255, 218, 185, 1));
+ case 'peru': return new Color(new RGBA(205, 133, 63, 1));
+ case 'pink': return new Color(new RGBA(255, 192, 203, 1));
+ case 'plum': return new Color(new RGBA(221, 160, 221, 1));
+ case 'powderblue': return new Color(new RGBA(176, 224, 230, 1));
+ case 'purple': return new Color(new RGBA(128, 0, 128, 1));
+ case 'rebeccapurple': return new Color(new RGBA(102, 51, 153, 1));
+ case 'red': return new Color(new RGBA(255, 0, 0, 1));
+ case 'rosybrown': return new Color(new RGBA(188, 143, 143, 1));
+ case 'royalblue': return new Color(new RGBA(65, 105, 225, 1));
+ case 'saddlebrown': return new Color(new RGBA(139, 69, 19, 1));
+ case 'salmon': return new Color(new RGBA(250, 128, 114, 1));
+ case 'sandybrown': return new Color(new RGBA(244, 164, 96, 1));
+ case 'seagreen': return new Color(new RGBA(46, 139, 87, 1));
+ case 'seashell': return new Color(new RGBA(255, 245, 238, 1));
+ case 'sienna': return new Color(new RGBA(160, 82, 45, 1));
+ case 'silver': return new Color(new RGBA(192, 192, 192, 1));
+ case 'skyblue': return new Color(new RGBA(135, 206, 235, 1));
+ case 'slateblue': return new Color(new RGBA(106, 90, 205, 1));
+ case 'slategray': return new Color(new RGBA(112, 128, 144, 1));
+ case 'slategrey': return new Color(new RGBA(112, 128, 144, 1));
+ case 'snow': return new Color(new RGBA(255, 250, 250, 1));
+ case 'springgreen': return new Color(new RGBA(0, 255, 127, 1));
+ case 'steelblue': return new Color(new RGBA(70, 130, 180, 1));
+ case 'tan': return new Color(new RGBA(210, 180, 140, 1));
+ case 'teal': return new Color(new RGBA(0, 128, 128, 1));
+ case 'thistle': return new Color(new RGBA(216, 191, 216, 1));
+ case 'tomato': return new Color(new RGBA(255, 99, 71, 1));
+ case 'turquoise': return new Color(new RGBA(64, 224, 208, 1));
+ case 'violet': return new Color(new RGBA(238, 130, 238, 1));
+ case 'wheat': return new Color(new RGBA(245, 222, 179, 1));
+ case 'white': return new Color(new RGBA(255, 255, 255, 1));
+ case 'whitesmoke': return new Color(new RGBA(245, 245, 245, 1));
+ case 'yellow': return new Color(new RGBA(255, 255, 0, 1));
+ case 'yellowgreen': return new Color(new RGBA(154, 205, 50, 1));
+ default: return null;
+ }
}
/**
diff --git a/src/vs/base/test/common/color.test.ts b/src/vs/base/test/common/color.test.ts
index 3ae8353bcdff3..ca5db568ca67f 100644
--- a/src/vs/base/test/common/color.test.ts
+++ b/src/vs/base/test/common/color.test.ts
@@ -210,9 +210,162 @@ suite('Color', () => {
assert.deepStrictEqual(Color.Format.CSS.parse('#'), null);
assert.deepStrictEqual(Color.Format.CSS.parse('#0102030'), null);
});
+
test('transparent', () => {
assert.deepStrictEqual(Color.Format.CSS.parse('transparent'), new Color(new RGBA(0, 0, 0, 0)));
});
+
+ test('named keyword', () => {
+ assert.deepStrictEqual(Color.Format.CSS.parse('aliceblue')!.rgba, new RGBA(240, 248, 255, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('antiquewhite')!.rgba, new RGBA(250, 235, 215, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('aqua')!.rgba, new RGBA(0, 255, 255, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('aquamarine')!.rgba, new RGBA(127, 255, 212, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('azure')!.rgba, new RGBA(240, 255, 255, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('beige')!.rgba, new RGBA(245, 245, 220, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('bisque')!.rgba, new RGBA(255, 228, 196, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('black')!.rgba, new RGBA(0, 0, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('blanchedalmond')!.rgba, new RGBA(255, 235, 205, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('blue')!.rgba, new RGBA(0, 0, 255, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('blueviolet')!.rgba, new RGBA(138, 43, 226, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('brown')!.rgba, new RGBA(165, 42, 42, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('burlywood')!.rgba, new RGBA(222, 184, 135, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('cadetblue')!.rgba, new RGBA(95, 158, 160, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('chartreuse')!.rgba, new RGBA(127, 255, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('chocolate')!.rgba, new RGBA(210, 105, 30, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('coral')!.rgba, new RGBA(255, 127, 80, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('cornflowerblue')!.rgba, new RGBA(100, 149, 237, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('cornsilk')!.rgba, new RGBA(255, 248, 220, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('crimson')!.rgba, new RGBA(220, 20, 60, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('cyan')!.rgba, new RGBA(0, 255, 255, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkblue')!.rgba, new RGBA(0, 0, 139, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkcyan')!.rgba, new RGBA(0, 139, 139, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkgoldenrod')!.rgba, new RGBA(184, 134, 11, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkgray')!.rgba, new RGBA(169, 169, 169, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkgreen')!.rgba, new RGBA(0, 100, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkgrey')!.rgba, new RGBA(169, 169, 169, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkkhaki')!.rgba, new RGBA(189, 183, 107, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkmagenta')!.rgba, new RGBA(139, 0, 139, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkolivegreen')!.rgba, new RGBA(85, 107, 47, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkorange')!.rgba, new RGBA(255, 140, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkorchid')!.rgba, new RGBA(153, 50, 204, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkred')!.rgba, new RGBA(139, 0, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darksalmon')!.rgba, new RGBA(233, 150, 122, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkseagreen')!.rgba, new RGBA(143, 188, 143, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkslateblue')!.rgba, new RGBA(72, 61, 139, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkslategray')!.rgba, new RGBA(47, 79, 79, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkslategrey')!.rgba, new RGBA(47, 79, 79, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkturquoise')!.rgba, new RGBA(0, 206, 209, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('darkviolet')!.rgba, new RGBA(148, 0, 211, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('deeppink')!.rgba, new RGBA(255, 20, 147, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('deepskyblue')!.rgba, new RGBA(0, 191, 255, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('dimgray')!.rgba, new RGBA(105, 105, 105, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('dimgrey')!.rgba, new RGBA(105, 105, 105, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('dodgerblue')!.rgba, new RGBA(30, 144, 255, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('firebrick')!.rgba, new RGBA(178, 34, 34, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('floralwhite')!.rgba, new RGBA(255, 250, 240, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('forestgreen')!.rgba, new RGBA(34, 139, 34, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('fuchsia')!.rgba, new RGBA(255, 0, 255, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('gainsboro')!.rgba, new RGBA(220, 220, 220, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('ghostwhite')!.rgba, new RGBA(248, 248, 255, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('gold')!.rgba, new RGBA(255, 215, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('goldenrod')!.rgba, new RGBA(218, 165, 32, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('gray')!.rgba, new RGBA(128, 128, 128, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('green')!.rgba, new RGBA(0, 128, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('greenyellow')!.rgba, new RGBA(173, 255, 47, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('grey')!.rgba, new RGBA(128, 128, 128, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('honeydew')!.rgba, new RGBA(240, 255, 240, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('hotpink')!.rgba, new RGBA(255, 105, 180, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('indianred')!.rgba, new RGBA(205, 92, 92, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('indigo')!.rgba, new RGBA(75, 0, 130, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('ivory')!.rgba, new RGBA(255, 255, 240, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('khaki')!.rgba, new RGBA(240, 230, 140, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lavender')!.rgba, new RGBA(230, 230, 250, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lavenderblush')!.rgba, new RGBA(255, 240, 245, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lawngreen')!.rgba, new RGBA(124, 252, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lemonchiffon')!.rgba, new RGBA(255, 250, 205, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightblue')!.rgba, new RGBA(173, 216, 230, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightcoral')!.rgba, new RGBA(240, 128, 128, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightcyan')!.rgba, new RGBA(224, 255, 255, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightgoldenrodyellow')!.rgba, new RGBA(250, 250, 210, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightgray')!.rgba, new RGBA(211, 211, 211, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightgreen')!.rgba, new RGBA(144, 238, 144, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightgrey')!.rgba, new RGBA(211, 211, 211, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightpink')!.rgba, new RGBA(255, 182, 193, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightsalmon')!.rgba, new RGBA(255, 160, 122, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightseagreen')!.rgba, new RGBA(32, 178, 170, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightskyblue')!.rgba, new RGBA(135, 206, 250, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightslategray')!.rgba, new RGBA(119, 136, 153, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightslategrey')!.rgba, new RGBA(119, 136, 153, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightsteelblue')!.rgba, new RGBA(176, 196, 222, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lightyellow')!.rgba, new RGBA(255, 255, 224, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('lime')!.rgba, new RGBA(0, 255, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('limegreen')!.rgba, new RGBA(50, 205, 50, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('linen')!.rgba, new RGBA(250, 240, 230, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('magenta')!.rgba, new RGBA(255, 0, 255, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('maroon')!.rgba, new RGBA(128, 0, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('mediumaquamarine')!.rgba, new RGBA(102, 205, 170, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('mediumblue')!.rgba, new RGBA(0, 0, 205, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('mediumorchid')!.rgba, new RGBA(186, 85, 211, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('mediumpurple')!.rgba, new RGBA(147, 112, 219, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('mediumseagreen')!.rgba, new RGBA(60, 179, 113, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('mediumslateblue')!.rgba, new RGBA(123, 104, 238, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('mediumspringgreen')!.rgba, new RGBA(0, 250, 154, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('mediumturquoise')!.rgba, new RGBA(72, 209, 204, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('mediumvioletred')!.rgba, new RGBA(199, 21, 133, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('midnightblue')!.rgba, new RGBA(25, 25, 112, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('mintcream')!.rgba, new RGBA(245, 255, 250, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('mistyrose')!.rgba, new RGBA(255, 228, 225, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('moccasin')!.rgba, new RGBA(255, 228, 181, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('navajowhite')!.rgba, new RGBA(255, 222, 173, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('navy')!.rgba, new RGBA(0, 0, 128, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('oldlace')!.rgba, new RGBA(253, 245, 230, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('olive')!.rgba, new RGBA(128, 128, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('olivedrab')!.rgba, new RGBA(107, 142, 35, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('orange')!.rgba, new RGBA(255, 165, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('orangered')!.rgba, new RGBA(255, 69, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('orchid')!.rgba, new RGBA(218, 112, 214, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('palegoldenrod')!.rgba, new RGBA(238, 232, 170, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('palegreen')!.rgba, new RGBA(152, 251, 152, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('paleturquoise')!.rgba, new RGBA(175, 238, 238, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('palevioletred')!.rgba, new RGBA(219, 112, 147, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('papayawhip')!.rgba, new RGBA(255, 239, 213, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('peachpuff')!.rgba, new RGBA(255, 218, 185, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('peru')!.rgba, new RGBA(205, 133, 63, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('pink')!.rgba, new RGBA(255, 192, 203, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('plum')!.rgba, new RGBA(221, 160, 221, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('powderblue')!.rgba, new RGBA(176, 224, 230, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('purple')!.rgba, new RGBA(128, 0, 128, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('rebeccapurple')!.rgba, new RGBA(102, 51, 153, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('red')!.rgba, new RGBA(255, 0, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('rosybrown')!.rgba, new RGBA(188, 143, 143, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('royalblue')!.rgba, new RGBA(65, 105, 225, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('saddlebrown')!.rgba, new RGBA(139, 69, 19, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('salmon')!.rgba, new RGBA(250, 128, 114, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('sandybrown')!.rgba, new RGBA(244, 164, 96, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('seagreen')!.rgba, new RGBA(46, 139, 87, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('seashell')!.rgba, new RGBA(255, 245, 238, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('sienna')!.rgba, new RGBA(160, 82, 45, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('silver')!.rgba, new RGBA(192, 192, 192, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('skyblue')!.rgba, new RGBA(135, 206, 235, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('slateblue')!.rgba, new RGBA(106, 90, 205, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('slategray')!.rgba, new RGBA(112, 128, 144, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('slategrey')!.rgba, new RGBA(112, 128, 144, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('snow')!.rgba, new RGBA(255, 250, 250, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('springgreen')!.rgba, new RGBA(0, 255, 127, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('steelblue')!.rgba, new RGBA(70, 130, 180, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('tan')!.rgba, new RGBA(210, 180, 140, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('teal')!.rgba, new RGBA(0, 128, 128, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('thistle')!.rgba, new RGBA(216, 191, 216, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('tomato')!.rgba, new RGBA(255, 99, 71, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('turquoise')!.rgba, new RGBA(64, 224, 208, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('violet')!.rgba, new RGBA(238, 130, 238, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('wheat')!.rgba, new RGBA(245, 222, 179, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('white')!.rgba, new RGBA(255, 255, 255, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('whitesmoke')!.rgba, new RGBA(245, 245, 245, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('yellow')!.rgba, new RGBA(255, 255, 0, 1));
+ assert.deepStrictEqual(Color.Format.CSS.parse('yellowgreen')!.rgba, new RGBA(154, 205, 50, 1));
+ });
+
test('hex-color', () => {
// somewhat valid
assert.deepStrictEqual(Color.Format.CSS.parse('#FFFFG0')!.rgba, new RGBA(255, 255, 0, 1));
From d2b1410df14c70f04fc0c4146cb02cdbbe128e0f Mon Sep 17 00:00:00 2001
From: Martin Aeschlimann
Date: Thu, 21 Nov 2024 16:10:04 +0100
Subject: [PATCH 038/118] chat: persist 'isHidden' (#234350)
---
src/vs/workbench/contrib/chat/common/chatModel.ts | 4 ++++
.../common/__snapshots__/ChatService_can_deserialize.0.snap | 1 +
.../ChatService_can_deserialize_with_response.0.snap | 1 +
.../common/__snapshots__/ChatService_can_serialize.1.snap | 2 ++
.../common/__snapshots__/ChatService_sendRequest_fails.0.snap | 1 +
5 files changed, 9 insertions(+)
diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts
index 41f3a8f1595b5..c70bdf6ab0454 100644
--- a/src/vs/workbench/contrib/chat/common/chatModel.ts
+++ b/src/vs/workbench/contrib/chat/common/chatModel.ts
@@ -660,6 +660,7 @@ export interface ISerializableChatRequestData {
/** Is really like "prompt data". This is the message in the format in which the agent gets it + variable values. */
variableData: IChatRequestVariableData;
response: ReadonlyArray | undefined;
+ isHidden: boolean;
responseId?: string;
agent?: ISerializableChatAgentData;
slashCommand?: IChatAgentCommand;
@@ -1004,6 +1005,7 @@ export class ChatModel extends Disposable implements IChatModel {
// Old messages don't have variableData, or have it in the wrong (non-array) shape
const variableData: IChatRequestVariableData = this.reviveVariableData(raw.variableData);
const request = new ChatRequestModel(this, parsedRequest, variableData, raw.timestamp ?? -1, undefined, undefined, undefined, undefined, undefined, undefined, raw.requestId);
+ request.isHidden = !!raw.isHidden;
if (raw.response || raw.result || (raw as any).responseErrorDetails) {
const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format
reviveSerializedAgent(raw.agent) : undefined;
@@ -1013,6 +1015,7 @@ export class ChatModel extends Disposable implements IChatModel {
// eslint-disable-next-line local/code-no-dangerous-type-assertions
{ errorDetails: raw.responseErrorDetails } as IChatAgentResult : raw.result;
request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, raw.voteDownReason, result, raw.followups, undefined, undefined, raw.responseId);
+ request.response.isHidden = !!raw.isHidden;
if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway?
request.response.applyReference(revive(raw.usedContext));
}
@@ -1282,6 +1285,7 @@ export class ChatModel extends Disposable implements IChatModel {
})
: undefined,
responseId: r.response?.id,
+ isHidden: r.isHidden,
result: r.response?.result,
followups: r.response?.followups,
isCanceled: r.response?.isCanceled,
diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap
index 7bf0d5055861d..d6eda2dc105d2 100644
--- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap
+++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap
@@ -57,6 +57,7 @@
variableData: { variables: [ ] },
response: [ ],
responseId: undefined,
+ isHidden: false,
result: { metadata: { metadataKey: "value" } },
followups: undefined,
isCanceled: false,
diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap
index 3f9c0004d5027..f42a981fc872c 100644
--- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap
+++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap
@@ -57,6 +57,7 @@
variableData: { variables: [ ] },
response: [ ],
responseId: undefined,
+ isHidden: false,
result: { errorDetails: { message: "No activated agent with id \"ChatProviderWithUsedContext\"" } },
followups: undefined,
isCanceled: false,
diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap
index 6a2f07aa1b411..8ad16f37294b7 100644
--- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap
+++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap
@@ -57,6 +57,7 @@
variableData: { variables: [ ] },
response: [ ],
responseId: undefined,
+ isHidden: false,
result: { metadata: { metadataKey: "value" } },
followups: [
{
@@ -130,6 +131,7 @@
variableData: { variables: [ ] },
response: [ ],
responseId: undefined,
+ isHidden: false,
result: { },
followups: [ ],
isCanceled: false,
diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap
index 2f1c69f5174b7..a4eeca5350e2d 100644
--- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap
+++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap
@@ -57,6 +57,7 @@
variableData: { variables: [ ] },
response: [ ],
responseId: undefined,
+ isHidden: false,
result: { errorDetails: { message: "No activated agent with id \"ChatProviderWithUsedContext\"" } },
followups: undefined,
isCanceled: false,
From a414275985b8ab3472199f1e2f7bbb437d69ba89 Mon Sep 17 00:00:00 2001
From: Benjamin Pasero
Date: Thu, 21 Nov 2024 16:50:23 +0100
Subject: [PATCH 039/118] chat - move the hide action to the dropdown (#234359)
---
.../chat/browser/chatSetup.contribution.ts | 36 ++++++++++++++-----
1 file changed, 28 insertions(+), 8 deletions(-)
diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
index fb6f22da1105a..7cf68461ea498 100644
--- a/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
@@ -39,6 +39,8 @@ import { IViewDescriptorService, ViewContainerLocation } from '../../../common/v
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
import { getActiveElement } from '../../../../base/browser/dom.js';
import { ILogService } from '../../../../platform/log/common/log.js';
+import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
+import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
const defaultChat = {
extensionId: product.defaultChatAgent?.extensionId ?? '',
@@ -140,7 +142,7 @@ class ChatSetupContribution extends Disposable implements IWorkbenchContribution
header,
`[${localize('signInAndSetup', "Sign in to use {0}", defaultChat.name)}](command:${ChatSetupSignInAndInstallChatAction.ID})`,
footer,
- `[${localize('learnMore', "Learn More")}](${defaultChat.documentationUrl}) | [${localize('hideSetup', "Hide")}](command:${ChatSetupHideAction.ID} "${localize('hideSetup', "Hide")}")`,
+ `[${localize('learnMore', "Learn More")}](${defaultChat.documentationUrl})`,
].join('\n\n'), { isTrusted: true }),
});
@@ -159,7 +161,7 @@ class ChatSetupContribution extends Disposable implements IWorkbenchContribution
header,
`[${localize('setup', "Install {0}", defaultChat.name)}](command:${ChatSetupInstallAction.ID})`,
footer,
- `[${localize('learnMore', "Learn More")}](${defaultChat.documentationUrl}) | [${localize('hideSetup', "Hide")}](command:${ChatSetupHideAction.ID} "${localize('hideSetup', "Hide")}")`,
+ `[${localize('learnMore', "Learn More")}](${defaultChat.documentationUrl})`,
].join('\n\n'), { isTrusted: true })
});
@@ -401,8 +403,7 @@ class ChatSetupTriggerAction extends Action2 {
constructor() {
super({
id: ChatSetupTriggerAction.ID,
- title: ChatSetupTriggerAction.TITLE,
- f1: false
+ title: ChatSetupTriggerAction.TITLE
});
}
@@ -419,13 +420,18 @@ class ChatSetupTriggerAction extends Action2 {
class ChatSetupHideAction extends Action2 {
static readonly ID = 'workbench.action.chat.hideSetup';
- static readonly TITLE = localize2('hideChatSetup', "Hide Chat Setup");
+ static readonly TITLE = localize2('hideChatSetup', "Hide {0}", defaultChat.name);
constructor() {
super({
id: ChatSetupHideAction.ID,
title: ChatSetupHideAction.TITLE,
- f1: false
+ menu: {
+ id: MenuId.ChatCommandCenter,
+ group: 'a_first',
+ order: 1,
+ when: ChatContextKeys.Setup.installed.negate()
+ }
});
}
@@ -433,6 +439,18 @@ class ChatSetupHideAction extends Action2 {
const viewsDescriptorService = accessor.get(IViewDescriptorService);
const layoutService = accessor.get(IWorkbenchLayoutService);
const instantiationService = accessor.get(IInstantiationService);
+ const configurationService = accessor.get(IConfigurationService);
+ const dialogService = accessor.get(IDialogService);
+
+ const { confirmed } = await dialogService.confirm({
+ message: localize('hideChatSetupConfirm', "Are you sure you want to hide {0}?", defaultChat.name),
+ detail: localize('hideChatSetupDetail', "You can restore chat controls from the 'chat.commandCenter.enabled' setting."),
+ primaryButton: localize('hideChatSetup', "Hide {0}", defaultChat.name)
+ });
+
+ if (!confirmed) {
+ return;
+ }
const location = viewsDescriptorService.getViewLocationById(ChatViewId);
@@ -444,6 +462,8 @@ class ChatSetupHideAction extends Action2 {
layoutService.setPartHidden(true, Parts.AUXILIARYBAR_PART); // hide if there are no views in the secondary sidebar
}
}
+
+ configurationService.updateValue('chat.commandCenter.enabled', false);
}
}
@@ -459,7 +479,7 @@ class ChatSetupInstallAction extends Action2 {
category: CHAT_CATEGORY,
menu: {
id: MenuId.ChatCommandCenter,
- group: 'a_open',
+ group: 'a_first',
order: 0,
when: ContextKeyExpr.and(
ChatContextKeys.Setup.signedIn,
@@ -525,7 +545,7 @@ class ChatSetupSignInAndInstallChatAction extends Action2 {
category: CHAT_CATEGORY,
menu: {
id: MenuId.ChatCommandCenter,
- group: 'a_open',
+ group: 'a_first',
order: 0,
when: ContextKeyExpr.and(
ChatContextKeys.Setup.signedIn.negate(),
From b82ed8d1a2ad99729469d3c2257e94eb414c73e9 Mon Sep 17 00:00:00 2001
From: Joyce Er
Date: Thu, 21 Nov 2024 08:20:06 -0800
Subject: [PATCH 040/118] fix: chat related files polish (#234364)
* fix: don't re-add suggested files that were ignored by user
* fix: don't include suggested files in the working set list count
* fix: don't change an attached file back to being suggested
* fix: italicize suggested file entries
---
.../browser/chatContentParts/chatReferencesContentPart.ts | 2 +-
.../chat/browser/chatEditing/chatEditingSession.ts | 2 +-
src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 8 +++++++-
src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +-
4 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts
index 42de7cc59a0bd..e05b69b29af3d 100644
--- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts
@@ -394,7 +394,7 @@ class CollapsibleListRenderer implements IListRenderer= remainingFileEntriesBudget) {
// The user tried to attach too many files, we have to drop anything after the limit
const entriesToPreserve: IChatCollapsibleListItem[] = [];
@@ -1136,9 +1138,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
// so that the Add Files button remains enabled and the user can easily
// override the suggestions with their own manual file selections
entries = [...entriesToPreserve, ...newEntriesThatFit, ...suggestedFilesThatFit];
+ suggestedFilesInWorkingSetCount = suggestedFilesThatFit.length;
+ } else {
+ suggestedFilesInWorkingSetCount = entries.filter(e => e.kind === 'reference' && e.state === WorkingSetEntryState.Suggested).length;
}
if (entries.length > 1) {
- overviewFileCount.textContent = ' ' + localize('chatEditingSession.manyFiles', '({0} files)', entries.length);
+ const fileCount = entries.length - suggestedFilesInWorkingSetCount;
+ overviewFileCount.textContent = ' ' + (fileCount === 1 ? localize('chatEditingSession.oneFile', '(1 file)') : localize('chatEditingSession.manyFiles', '({0} files)', fileCount));
}
if (excludedEntries.length > 0) {
diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts
index 2ce9404d4e231..58d2543794da0 100644
--- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts
@@ -1051,7 +1051,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
actualSize: number;
};
this.telemetryService.publicLog2('chatEditing/workingSetSize', { originalSize: this.inputPart.attemptedWorkingSetEntriesCount, actualSize: uniqueWorkingSetEntries.size });
- currentEditingSession?.remove(WorkingSetEntryRemovalReason.Programmatic, ...unconfirmedSuggestions);
+ currentEditingSession?.remove(WorkingSetEntryRemovalReason.User, ...unconfirmedSuggestions);
}
const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, {
From b6ce07fb3cd9edec82e31d3473be318074d27ab3 Mon Sep 17 00:00:00 2001
From: Rob Lourens
Date: Thu, 21 Nov 2024 08:26:16 -0800
Subject: [PATCH 041/118] Add telemetry for tool calls (#234365)
* Add telemetry for tool calls
* Fix test
---
.../chat/browser/languageModelToolsService.ts | 106 ++++++++++++------
.../chat/common/languageModelToolsService.ts | 2 +
.../tools/languageModelToolsContribution.ts | 1 +
.../browser/languageModelToolsService.test.ts | 18 +--
4 files changed, 85 insertions(+), 42 deletions(-)
diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts
index 9f3860a6f754d..2b3f6b2dc497d 100644
--- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts
+++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts
@@ -6,13 +6,14 @@
import { renderStringAsPlaintext } from '../../../../base/browser/markdownRenderer.js';
import { RunOnceScheduler } from '../../../../base/common/async.js';
import { CancellationToken } from '../../../../base/common/cancellation.js';
-import { CancellationError } from '../../../../base/common/errors.js';
+import { CancellationError, isCancellationError } from '../../../../base/common/errors.js';
import { Emitter } from '../../../../base/common/event.js';
import { Iterable } from '../../../../base/common/iterator.js';
import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { localize } from '../../../../nls.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
+import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
import { ChatModel } from '../common/chatModel.js';
import { ChatToolInvocation } from '../common/chatProgressTypes/chatToolInvocation.js';
@@ -41,6 +42,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IChatService private readonly _chatService: IChatService,
@IDialogService private readonly _dialogService: IDialogService,
+ @ITelemetryService private readonly _telemetryService: ITelemetryService,
) {
super();
@@ -138,44 +140,82 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
// Shortcut to write to the model directly here, but could call all the way back to use the real stream.
let toolInvocation: ChatToolInvocation | undefined;
- if (dto.context) {
- const model = this._chatService.getSession(dto.context?.sessionId) as ChatModel;
- const request = model.getRequests().at(-1)!;
-
- const prepared = tool.impl.prepareToolInvocation ?
- await tool.impl.prepareToolInvocation(dto.parameters, token)
- : undefined;
-
- const defaultMessage = localize('toolInvocationMessage', "Using {0}", `"${tool.data.displayName}"`);
- const invocationMessage = prepared?.invocationMessage ?? defaultMessage;
- toolInvocation = new ChatToolInvocation(invocationMessage, prepared?.confirmationMessages);
- token.onCancellationRequested(() => {
- toolInvocation!.confirmed.complete(false);
- });
- model.acceptResponseProgress(request, toolInvocation);
- if (prepared?.confirmationMessages) {
- const userConfirmed = await toolInvocation.confirmed.p;
- if (!userConfirmed) {
- throw new CancellationError();
+
+ try {
+ if (dto.context) {
+ const model = this._chatService.getSession(dto.context?.sessionId) as ChatModel;
+ const request = model.getRequests().at(-1)!;
+
+ const prepared = tool.impl.prepareToolInvocation ?
+ await tool.impl.prepareToolInvocation(dto.parameters, token)
+ : undefined;
+
+ const defaultMessage = localize('toolInvocationMessage', "Using {0}", `"${tool.data.displayName}"`);
+ const invocationMessage = prepared?.invocationMessage ?? defaultMessage;
+ toolInvocation = new ChatToolInvocation(invocationMessage, prepared?.confirmationMessages);
+ token.onCancellationRequested(() => {
+ toolInvocation!.confirmed.complete(false);
+ });
+ model.acceptResponseProgress(request, toolInvocation);
+ if (prepared?.confirmationMessages) {
+ const userConfirmed = await toolInvocation.confirmed.p;
+ if (!userConfirmed) {
+ throw new CancellationError();
+ }
}
- }
- } else {
- const prepared = tool.impl.prepareToolInvocation ?
- await tool.impl.prepareToolInvocation(dto.parameters, token)
- : undefined;
-
- if (prepared?.confirmationMessages) {
- const result = await this._dialogService.confirm({ message: prepared.confirmationMessages.title, detail: renderStringAsPlaintext(prepared.confirmationMessages.message) });
- if (!result.confirmed) {
- throw new CancellationError();
+ } else {
+ const prepared = tool.impl.prepareToolInvocation ?
+ await tool.impl.prepareToolInvocation(dto.parameters, token)
+ : undefined;
+
+ if (prepared?.confirmationMessages) {
+ const result = await this._dialogService.confirm({ message: prepared.confirmationMessages.title, detail: renderStringAsPlaintext(prepared.confirmationMessages.message) });
+ if (!result.confirmed) {
+ throw new CancellationError();
+ }
}
}
- }
- try {
- return await tool.impl.invoke(dto, countTokens, token);
+
+ const result = await tool.impl.invoke(dto, countTokens, token);
+ this._telemetryService.publicLog2(
+ 'languageModelToolInvoked',
+ {
+ result: 'success',
+ chatSessionId: dto.context?.sessionId,
+ toolId: tool.data.id,
+ toolExtensionId: tool.data.extensionId?.value,
+ });
+ return result;
+ } catch (err) {
+ const result = isCancellationError(err) ? 'userCancelled' : 'error';
+ this._telemetryService.publicLog2(
+ 'languageModelToolInvoked',
+ {
+ result,
+ chatSessionId: dto.context?.sessionId,
+ toolId: tool.data.id,
+ toolExtensionId: tool.data.extensionId?.value,
+ });
+ throw err;
} finally {
toolInvocation?.isCompleteDeferred.complete();
}
}
}
+
+type LanguageModelToolInvokedEvent = {
+ result: 'success' | 'error' | 'userCancelled';
+ chatSessionId: string | undefined;
+ toolId: string;
+ toolExtensionId: string | undefined;
+};
+
+type LanguageModelToolInvokedClassification = {
+ result: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether invoking the LanguageModelTool resulted in an error.' };
+ chatSessionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the chat session that the tool was used within, if applicable.' };
+ toolId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the tool used.' };
+ toolExtensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension that contributed the tool.' };
+ owner: 'roblourens';
+ comment: 'Provides insight into the usage of language model tools.';
+};
diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts
index 6699d50ee5963..f2251ca8166f7 100644
--- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts
+++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts
@@ -11,10 +11,12 @@ import { IDisposable } from '../../../../base/common/lifecycle.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { URI } from '../../../../base/common/uri.js';
import { ContextKeyExpression } from '../../../../platform/contextkey/common/contextkey.js';
+import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
export interface IToolData {
id: string;
+ extensionId?: ExtensionIdentifier;
toolReferenceName?: string;
icon?: { dark: URI; light?: URI } | ThemeIcon;
when?: ContextKeyExpression;
diff --git a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts
index d48a1b54011bd..de36f417e6a4f 100644
--- a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts
+++ b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts
@@ -173,6 +173,7 @@ export class LanguageModelToolsExtensionPointHandler implements IWorkbenchContri
const tool: IToolData = {
...rawTool,
+ extensionId: extension.description.identifier,
inputSchema: rawTool.inputSchema,
id: rawTool.name,
icon,
diff --git a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts
index 48e21693f0238..9679c7a890893 100644
--- a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts
+++ b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts
@@ -6,14 +6,12 @@
import * as assert from 'assert';
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
-import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';
-import { ContextKeyService } from '../../../../../platform/contextkey/browser/contextKeyService.js';
import { ContextKeyEqualsExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
-import { TestExtensionService } from '../../../../test/common/workbenchTestServices.js';
-import { IToolData, IToolImpl, IToolInvocation } from '../../common/languageModelToolsService.js';
-import { MockChatService } from '../common/mockChatService.js';
-import { TestDialogService } from '../../../../../platform/dialogs/test/common/testDialogService.js';
+import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js';
import { LanguageModelToolsService } from '../../browser/languageModelToolsService.js';
+import { IToolData, IToolImpl, IToolInvocation } from '../../common/languageModelToolsService.js';
+import { ContextKeyService } from '../../../../../platform/contextkey/browser/contextKeyService.js';
+import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';
suite('LanguageModelToolsService', () => {
const store = ensureNoDisposablesAreLeakedInTestSuite();
@@ -22,9 +20,11 @@ suite('LanguageModelToolsService', () => {
let service: LanguageModelToolsService;
setup(() => {
- const extensionService = new TestExtensionService();
- contextKeyService = store.add(new ContextKeyService(new TestConfigurationService()));
- service = store.add(new LanguageModelToolsService(extensionService, contextKeyService, new MockChatService(), new TestDialogService()));
+ const instaService = workbenchInstantiationService({
+ contextKeyService: () => store.add(new ContextKeyService(new TestConfigurationService))
+ }, store);
+ contextKeyService = instaService.get(IContextKeyService);
+ service = store.add(instaService.createInstance(LanguageModelToolsService));
});
test('registerToolData', () => {
From 7f05cd2559b723ce24cd56db4df8b6bbb79c825e Mon Sep 17 00:00:00 2001
From: Martin Aeschlimann
Date: Thu, 21 Nov 2024 17:38:13 +0100
Subject: [PATCH 042/118] chat edits: fix for redo after reload (#234366)
---
.../browser/chatEditing/chatEditingService.ts | 16 ++---
.../browser/chatEditing/chatEditingSession.ts | 71 ++++++++++---------
.../contrib/chat/browser/chatWidget.ts | 2 +-
.../contrib/chat/common/chatEditingService.ts | 2 +-
4 files changed, 42 insertions(+), 49 deletions(-)
diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts
index 74966fc973b3f..a90172eacd107 100644
--- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts
@@ -31,7 +31,6 @@ import { IEditorGroup, IEditorGroupsService } from '../../../../services/editor/
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js';
import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEditor.js';
-import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js';
import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from '../../../multiDiffEditor/browser/multiDiffSourceResolverService.js';
import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js';
import { ChatContextKeys } from '../../common/chatContextKeys.js';
@@ -186,7 +185,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
super.dispose();
}
- async startOrContinueEditingSession(chatSessionId: string, options?: { silent: boolean }): Promise {
+ async startOrContinueEditingSession(chatSessionId: string): Promise {
const session = this._currentSessionObs.get();
if (session) {
if (session.chatSessionId === chatSessionId) {
@@ -195,10 +194,10 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
await session.stop();
}
}
- return this._createEditingSession(chatSessionId, options);
+ return this._createEditingSession(chatSessionId);
}
- private async _createEditingSession(chatSessionId: string, options?: { silent: boolean }): Promise {
+ private async _createEditingSession(chatSessionId: string): Promise {
if (this._currentSessionObs.get()) {
throw new BugIndicatingError('Cannot have more than one active editing session');
}
@@ -208,14 +207,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
// listen for completed responses, run the code mapper and apply the edits to this edit session
this._currentSessionDisposables.add(this.installAutoApplyObserver(chatSessionId));
- const input = MultiDiffEditorInput.fromResourceMultiDiffEditorInput({
- multiDiffSource: ChatEditingMultiDiffSourceResolver.getMultiDiffSourceUri(),
- label: localize('multiDiffEditorInput.name', "Suggested Edits")
- }, this._instantiationService);
-
- const editorPane = options?.silent ? undefined : await this._editorGroupsService.activeGroup.openEditor(input, { pinned: true, activation: EditorActivation.ACTIVATE }) as MultiDiffEditor | undefined;
-
- const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, editorPane, this._editingSessionFileLimitPromise);
+ const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, this._editingSessionFileLimitPromise);
await session.restoreState();
this._currentSessionDisposables.add(session.onDidDispose(() => {
diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts
index df0ee33226c1e..220fc5a30bfd1 100644
--- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts
@@ -32,7 +32,6 @@ import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiff
import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js';
import { ChatEditingSessionChangeType, ChatEditingSessionState, ChatEditKind, IChatEditingSession, WorkingSetDisplayMetadata, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../../common/chatEditingService.js';
import { IChatResponseModel } from '../../common/chatModel.js';
-import { IChatWidgetService } from '../chat.js';
import { ChatEditingMultiDiffSourceResolver } from './chatEditingService.js';
import { ChatEditingModifiedFileEntry, IModifiedEntryTelemetryInfo, ISnapshotEntry } from './chatEditingModifiedFileEntry.js';
import { ChatEditingTextModelContentProvider } from './chatEditingTextModelContentProviders.js';
@@ -81,6 +80,8 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
private _removedTransientEntries = new ResourceSet();
+ private _editorPane: MultiDiffEditor | undefined;
+
get state(): IObservable {
return this._state;
}
@@ -122,12 +123,11 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
get isVisible(): boolean {
this._assertNotDisposed();
- return Boolean(this.editorPane && this.editorPane.isVisible());
+ return Boolean(this._editorPane && this._editorPane.isVisible());
}
constructor(
public readonly chatSessionId: string,
- private editorPane: MultiDiffEditor | undefined,
private editingSessionFileLimitPromise: Promise,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IModelService private readonly _modelService: IModelService,
@@ -136,7 +136,6 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
@IBulkEditService public readonly _bulkEditService: IBulkEditService,
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
@IEditorService private readonly _editorService: IEditorService,
- @IChatWidgetService chatWidgetService: IChatWidgetService,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
@IFileService private readonly _fileService: IFileService,
@IFileDialogService private readonly _dialogService: IFileDialogService,
@@ -147,11 +146,6 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
) {
super();
- const widget = chatWidgetService.getWidgetBySessionId(chatSessionId);
- if (!widget) {
- return; // Shouldn't happen
- }
-
// Add the currently active editors to the working set
this._trackCurrentEditorsInWorkingSet();
this._register(this._editorService.onDidVisibleEditorsChange(() => {
@@ -313,7 +307,8 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
// Restore all entries from the snapshot
for (const snapshotEntry of snapshot.entries.values()) {
const entry = await this._getOrCreateModifiedFileEntry(snapshotEntry.resource, snapshotEntry.telemetryInfo);
- entry.restoreFromSnapshot(snapshotEntry); entriesArr.push(entry);
+ entry.restoreFromSnapshot(snapshotEntry);
+ entriesArr.push(entry);
}
this._entriesObs.set(entriesArr, undefined);
@@ -383,21 +378,20 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
async show(): Promise {
this._assertNotDisposed();
-
- if (this.editorPane?.isVisible()) {
- return;
- } else if (this.editorPane?.input) {
- await this._editorGroupsService.activeGroup.openEditor(this.editorPane.input, { pinned: true, activation: EditorActivation.ACTIVATE });
- return;
+ if (this._editorPane) {
+ if (this._editorPane.isVisible()) {
+ return;
+ } else if (this._editorPane.input) {
+ await this._editorGroupsService.activeGroup.openEditor(this._editorPane.input, { pinned: true, activation: EditorActivation.ACTIVATE });
+ return;
+ }
}
-
const input = MultiDiffEditorInput.fromResourceMultiDiffEditorInput({
multiDiffSource: ChatEditingMultiDiffSourceResolver.getMultiDiffSourceUri(),
label: localize('multiDiffEditorInput.name', "Suggested Edits")
}, this._instantiationService);
- const editorPane = await this._editorGroupsService.activeGroup.openEditor(input, { pinned: true, activation: EditorActivation.ACTIVATE }) as MultiDiffEditor | undefined;
- this.editorPane = editorPane;
+ this._editorPane = await this._editorGroupsService.activeGroup.openEditor(input, { pinned: true, activation: EditorActivation.ACTIVATE }) as MultiDiffEditor | undefined;
}
private stopPromise: Promise | undefined;
@@ -495,29 +489,29 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
async undoInteraction(): Promise {
const linearHistory = this._linearHistory.get();
- const linearHistoryIndex = this._linearHistoryIndex.get();
- if (linearHistoryIndex <= 0) {
+ const newIndex = this._linearHistoryIndex.get() - 1;
+ if (newIndex < 0) {
return;
}
- const previousSnapshot = linearHistory[linearHistoryIndex - 1];
+ const previousSnapshot = linearHistory[newIndex];
await this.restoreSnapshot(previousSnapshot.requestId);
- this._linearHistoryIndex.set(linearHistoryIndex - 1, undefined);
+ this._linearHistoryIndex.set(newIndex, undefined);
this._updateRequestHiddenState();
}
async redoInteraction(): Promise {
const linearHistory = this._linearHistory.get();
- const linearHistoryIndex = this._linearHistoryIndex.get();
- if (linearHistoryIndex >= linearHistory.length) {
+ const newIndex = this._linearHistoryIndex.get() + 1;
+ if (newIndex > linearHistory.length) {
return;
}
- const nextSnapshot = (linearHistoryIndex + 1 < linearHistory.length ? linearHistory[linearHistoryIndex + 1] : this._pendingSnapshot);
+ const nextSnapshot = newIndex < linearHistory.length ? linearHistory[newIndex] : this._pendingSnapshot;
if (!nextSnapshot) {
return;
}
await this.restoreSnapshot(nextSnapshot.requestId);
- this._linearHistoryIndex.set(linearHistoryIndex + 1, undefined);
+ this._linearHistoryIndex.set(newIndex, undefined);
this._updateRequestHiddenState();
}
@@ -627,9 +621,9 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
}
private _collapse(resource: URI, transaction: ITransaction | undefined) {
- const multiDiffItem = this.editorPane?.findDocumentDiffItem(resource);
+ const multiDiffItem = this._editorPane?.findDocumentDiffItem(resource);
if (multiDiffItem) {
- this.editorPane?.viewModel?.items.get().find((documentDiffItem) =>
+ this._editorPane?.viewModel?.items.get().find((documentDiffItem) =>
isEqual(documentDiffItem.originalUri, multiDiffItem.originalUri) &&
isEqual(documentDiffItem.modifiedUri, multiDiffItem.modifiedUri))
?.collapsed.set(true, transaction);
@@ -685,10 +679,12 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
this._logService.debug(`chatEditingSession: Restoring editing session at ${stateFilePath.toString()}`);
const stateFileContent = await this._fileService.readFile(stateFilePath);
const data = JSON.parse(stateFileContent.value.toString()) as IChatEditingSessionDTO;
+ if (data.version !== STORAGE_VERSION) {
+ return false;
+ }
this._filesToSkipCreating.clear();
this._initialFileContents.clear();
- this._pendingSnapshot = undefined;
const snapshotsFromHistory = await Promise.all(data.linearHistory.map(deserializeChatEditingSessionSnapshot));
data.filesToSkipCreating.forEach((uriStr: string) => {
@@ -697,10 +693,10 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
for (const fileContentDTO of data.initialFileContents) {
this._initialFileContents.set(URI.parse(fileContentDTO[0]), await getFileContent(fileContentDTO[1]));
}
+ this._pendingSnapshot = data.pendingSnapshot ? await deserializeChatEditingSessionSnapshot(data.pendingSnapshot) : undefined;
+ this._restoreSnapshot(await deserializeChatEditingSessionSnapshot(data.recentSnapshot));
this._linearHistoryIndex.set(data.linearHistoryIndex, undefined);
this._linearHistory.set(snapshotsFromHistory, undefined);
- const pendingSnapshot = await deserializeChatEditingSessionSnapshot(data.pendingSnapshot);
- this._restoreSnapshot(pendingSnapshot);
this._state.set(ChatEditingSessionState.Idle, undefined);
this._updateRequestHiddenState();
return true;
@@ -767,13 +763,14 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
};
try {
- const pendingSnapshot = this._createSnapshot(undefined);
const data = {
+ version: STORAGE_VERSION,
sessionId: this.chatSessionId,
linearHistory: this._linearHistory.get().map(serializeChatEditingSessionSnapshot),
linearHistoryIndex: this._linearHistoryIndex.get(),
initialFileContents: serializeResourceMap(this._initialFileContents, value => addFileContent(value)),
- pendingSnapshot: serializeChatEditingSessionSnapshot(pendingSnapshot),
+ pendingSnapshot: this._pendingSnapshot ? serializeChatEditingSessionSnapshot(this._pendingSnapshot) : undefined,
+ recentSnapshot: serializeChatEditingSessionSnapshot(this._createSnapshot(undefined)),
filesToSkipCreating: Array.from(this._filesToSkipCreating.keys()).map(uri => uri.toString()),
} satisfies IChatEditingSessionDTO;
@@ -833,11 +830,15 @@ interface IModifiedEntryTelemetryInfoDTO {
type ResourceMapDTO = [string, T][];
+const STORAGE_VERSION = 1;
+
interface IChatEditingSessionDTO {
+ readonly version: number;
readonly sessionId: string;
- readonly pendingSnapshot: IChatEditingSessionSnapshotDTO;
+ readonly recentSnapshot: IChatEditingSessionSnapshotDTO;
readonly linearHistory: IChatEditingSessionSnapshotDTO[];
readonly linearHistoryIndex: number;
+ readonly pendingSnapshot: IChatEditingSessionSnapshotDTO | undefined;
readonly initialFileContents: ResourceMapDTO;
readonly filesToSkipCreating: string[];
}
diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts
index 58d2543794da0..3b0aaa58dc859 100644
--- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts
@@ -283,7 +283,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
const sessionId = this._viewModel?.sessionId;
if (sessionId) {
if (sessionId !== currentEditSession?.chatSessionId) {
- currentEditSession = await this.chatEditingService.startOrContinueEditingSession(sessionId, { silent: true });
+ currentEditSession = await this.chatEditingService.startOrContinueEditingSession(sessionId);
}
} else {
if (currentEditSession) {
diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts
index 5b3b80e085283..74f7d6cd270d3 100644
--- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts
+++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts
@@ -36,7 +36,7 @@ export interface IChatEditingService {
readonly editingSessionFileLimit: number;
- startOrContinueEditingSession(chatSessionId: string, options?: { silent: boolean }): Promise;
+ startOrContinueEditingSession(chatSessionId: string): Promise;
getEditingSession(resource: URI): IChatEditingSession | null;
createSnapshot(requestId: string): void;
getSnapshotUri(requestId: string, uri: URI): URI | undefined;
From b7f6bbe55b92660234d8704e89fbc2fbd2a58b11 Mon Sep 17 00:00:00 2001
From: Benjamin Pasero
Date: Thu, 21 Nov 2024 18:09:20 +0100
Subject: [PATCH 043/118] chat - tweaks to welcome (#234370)
---
src/vs/base/common/product.ts | 1 +
.../contrib/chat/browser/chatSetup.contribution.ts | 13 ++++++++-----
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts
index 459ffb75a1021..fcb54c94eb396 100644
--- a/src/vs/base/common/product.ts
+++ b/src/vs/base/common/product.ts
@@ -318,4 +318,5 @@ export interface IDefaultChatAgent {
readonly entitlementChatEnabled: string;
readonly entitlementSkuKey: string;
readonly entitlementSku30DTrialValue: string;
+ readonly entitlementSkuAlternateUrl: string;
}
diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
index 7cf68461ea498..df27d811fdd82 100644
--- a/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
@@ -6,7 +6,7 @@
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
-import { ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js';
+import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { AuthenticationSession, IAuthenticationService } from '../../../services/authentication/common/authentication.js';
import { IProductService } from '../../../../platform/product/common/productService.js';
import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js';
@@ -57,6 +57,7 @@ const defaultChat = {
entitlementSkuKey: product.defaultChatAgent?.entitlementSkuKey ?? '',
entitlementSku30DTrialValue: product.defaultChatAgent?.entitlementSku30DTrialValue ?? '',
entitlementChatEnabled: product.defaultChatAgent?.entitlementChatEnabled ?? '',
+ entitlementSkuAlternateUrl: product.defaultChatAgent?.entitlementSkuAlternateUrl ?? ''
};
type ChatSetupEntitlementEnablementClassification = {
@@ -122,10 +123,8 @@ class ChatSetupContribution extends Disposable implements IWorkbenchContribution
}
private registerChatWelcome(): void {
- const header = localize('setupPreamble1', "{0} is your AI pair programmer.", defaultChat.name);
- const footer = this.telemetryService.telemetryLevel !== TelemetryLevel.NONE ?
- localize({ key: 'setupPreambleWithOptOut', comment: ['{Locked="]({0})"}'] }, "{0} may use your code snippets for product improvements. Read our [privacy statement]({1}) and learn how to [opt out]({2}).", defaultChat.name, defaultChat.privacyStatementUrl, defaultChat.collectionDocumentationUrl) :
- localize({ key: 'setupPreambleWithoutOptOut', comment: ['{Locked="]({0})"}'] }, "By proceeding you agree to our [privacy statement]({0}).", defaultChat.privacyStatementUrl);
+ const header = localize('setupHeader', "{0} is your AI pair programmer.", defaultChat.name);
+ const footer = localize({ key: 'setupFooter', comment: ['{Locked="]({0})"}'] }, "By proceeding you agree to our [privacy statement]({0}).", defaultChat.privacyStatementUrl);
// Setup: Triggered (signed-out)
Registry.as(ChatViewsWelcomeExtensions.ChatViewsWelcomeRegistry).register({
@@ -332,6 +331,7 @@ class ChatSetupRequestHelper {
return await requestService.request({
type,
url,
+ data: type === 'POST' ? JSON.stringify({}) : undefined,
headers: {
'Authorization': `Bearer ${session.accessToken}`
}
@@ -500,6 +500,7 @@ class ChatSetupInstallAction extends Action2 {
const contextKeyService = accessor.get(IContextKeyService);
const viewsService = accessor.get(IViewsService);
const chatAgentService = accessor.get(IChatAgentService);
+ const instantiationService = accessor.get(IInstantiationService);
const signedIn = !!session;
const setupInstallingContextKey = ChatContextKeys.Setup.installing.bindTo(contextKeyService);
@@ -510,6 +511,8 @@ class ChatSetupInstallAction extends Action2 {
setupInstallingContextKey.set(true);
showChatView(viewsService);
+ await instantiationService.invokeFunction(accessor => ChatSetupRequestHelper.request(accessor, defaultChat.entitlementSkuAlternateUrl, 'POST', session, CancellationToken.None));
+
await extensionsWorkbenchService.install(defaultChat.extensionId, {
enable: true,
isMachineScoped: false,
From 7662e0314532535862e6fb90dff52cde6bea595a Mon Sep 17 00:00:00 2001
From: Martin Aeschlimann
Date: Thu, 21 Nov 2024 19:46:47 +0100
Subject: [PATCH 044/118] edits: more simplifications (#234377)
* edits: more simplifications
* remove unused imports
---
.../browser/chatEditing/chatEditingService.ts | 74 +++++--------------
.../contrib/chat/browser/chatWidget.ts | 5 --
2 files changed, 20 insertions(+), 59 deletions(-)
diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts
index a90172eacd107..a4255e29f0d33 100644
--- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts
@@ -19,18 +19,14 @@ import { TextEdit } from '../../../../../editor/common/languages.js';
import { ITextModelService } from '../../../../../editor/common/services/resolverService.js';
import { localize, localize2 } from '../../../../../nls.js';
import { IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
-import { EditorActivation } from '../../../../../platform/editor/common/editor.js';
import { IFileService } from '../../../../../platform/files/common/files.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { bindContextKey } from '../../../../../platform/observable/common/platformObservableUtils.js';
import { IProgressService, ProgressLocation } from '../../../../../platform/progress/common/progress.js';
-import { EditorInput } from '../../../../common/editor/editorInput.js';
import { IWorkbenchAssignmentService } from '../../../../services/assignment/common/assignmentService.js';
import { IDecorationData, IDecorationsProvider, IDecorationsService } from '../../../../services/decorations/common/decorations.js';
-import { IEditorGroup, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js';
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js';
-import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEditor.js';
import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from '../../../multiDiffEditor/browser/multiDiffSourceResolverService.js';
import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js';
import { ChatContextKeys } from '../../common/chatContextKeys.js';
@@ -79,7 +75,6 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
private _chatRelatedFilesProviders = new Map();
constructor(
- @IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IMultiDiffSourceResolverService multiDiffSourceResolverService: IMultiDiffSourceResolverService,
@ITextModelService textModelService: ITextModelService,
@@ -204,10 +199,11 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
this._currentSessionDisposables.clear();
+ const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, this._editingSessionFileLimitPromise);
+
// listen for completed responses, run the code mapper and apply the edits to this edit session
- this._currentSessionDisposables.add(this.installAutoApplyObserver(chatSessionId));
+ this._currentSessionDisposables.add(this.installAutoApplyObserver(session));
- const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, this._editingSessionFileLimitPromise);
await session.restoreState();
this._currentSessionDisposables.add(session.onDidDispose(() => {
@@ -233,11 +229,11 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
await this._currentSessionObs.get()?.restoreSnapshot(requestId);
}
- private installAutoApplyObserver(sessionId: string): IDisposable {
+ private installAutoApplyObserver(session: ChatEditingSession): IDisposable {
- const chatModel = this._chatService.getSession(sessionId);
+ const chatModel = this._chatService.getSession(session.chatSessionId);
if (!chatModel) {
- throw new Error(`Edit session was created for a non-existing chat session: ${sessionId}`);
+ throw new Error(`Edit session was created for a non-existing chat session: ${session.chatSessionId}`);
}
const observerDisposables = new DisposableStore();
@@ -250,7 +246,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
const onResponseComplete = (responseModel: IChatResponseModel) => {
if (responseModel.result?.errorDetails && !responseModel.result.errorDetails.responseIsIncomplete) {
// Roll back everything
- this.restoreSnapshot(responseModel.requestId);
+ session.restoreSnapshot(responseModel.requestId);
this._applyingChatEditsFailedContextKey.set(true);
}
@@ -293,7 +289,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
await editsPromise;
- editsPromise = this._continueEditingSession(async (builder, token) => {
+ editsPromise = this._continueEditingSession(session, async (builder, token) => {
for await (const item of editsSource!.asyncIterable) {
if (token.isCancellationRequested) {
break;
@@ -304,7 +300,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
builder.textEdits(item.uri, group, isLastGroup && (item.done ?? false), responseModel);
}
}
- }, { silent: true }).finally(() => {
+ }).finally(() => {
editsPromise = undefined;
});
}
@@ -314,6 +310,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
observerDisposables.add(chatModel.onDidChange(async e => {
if (e.kind === 'addRequest') {
+ session.createSnapshot(e.request.id);
this._applyingChatEditsFailedContextKey.set(false);
const responseModel = e.request.response;
if (responseModel) {
@@ -338,27 +335,11 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
return observerDisposables;
}
- private async _continueEditingSession(builder: (stream: IChatEditingSessionStream, token: CancellationToken) => Promise, options?: { silent?: boolean }): Promise {
- const session = this._currentSessionObs.get();
- if (!session) {
- throw new BugIndicatingError('Cannot continue missing session');
- }
-
+ private async _continueEditingSession(session: ChatEditingSession, builder: (stream: IChatEditingSessionStream, token: CancellationToken) => Promise): Promise {
if (session.state.get() === ChatEditingSessionState.StreamingEdits) {
throw new BugIndicatingError('Cannot continue session that is still streaming');
}
- let editorPane: MultiDiffEditor | undefined;
- if (!options?.silent && session.isVisible) {
- const groupedEditors = this._findGroupedEditors();
- if (groupedEditors.length !== 1) {
- throw new Error(`Unexpected number of editors: ${groupedEditors.length}`);
- }
- const [group, editor] = groupedEditors[0];
-
- editorPane = await group.openEditor(editor, { pinned: true, activation: EditorActivation.ACTIVATE }) as MultiDiffEditor | undefined;
- }
-
const stream: IChatEditingSessionStream = {
textEdits: (resource: URI, textEdits: TextEdit[], isDone: boolean, responseModel: IChatResponseModel) => {
session.acceptTextEdits(resource, textEdits, isDone, responseModel);
@@ -368,18 +349,15 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
const cancellationTokenSource = new CancellationTokenSource();
this._currentAutoApplyOperationObs.set(cancellationTokenSource, undefined);
try {
- if (editorPane) {
- await editorPane?.showWhile(builder(stream, cancellationTokenSource.token));
- } else {
- await this._progressService.withProgress({
- location: ProgressLocation.Window,
- title: localize2('chatEditing.startingSession', 'Generating edits...').value,
- }, async () => {
- await builder(stream, cancellationTokenSource.token);
- },
- () => cancellationTokenSource.cancel()
- );
- }
+ await this._progressService.withProgress({
+ location: ProgressLocation.Window,
+ title: localize2('chatEditing.startingSession', 'Generating edits...').value,
+ }, async () => {
+ await builder(stream, cancellationTokenSource.token);
+ },
+ () => cancellationTokenSource.cancel()
+ );
+
} finally {
cancellationTokenSource.dispose();
this._currentAutoApplyOperationObs.set(null, undefined);
@@ -387,18 +365,6 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
}
}
- private _findGroupedEditors() {
- const editors: [IEditorGroup, EditorInput][] = [];
- for (const group of this._editorGroupsService.groups) {
- for (const editor of group.editors) {
- if (editor.resource?.scheme === ChatEditingMultiDiffSourceResolver.scheme) {
- editors.push([group, editor]);
- }
- }
- }
- return editors;
- }
-
hasRelatedFilesProviders(): boolean {
return this._chatRelatedFilesProviders.size > 0;
}
diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts
index 3b0aaa58dc859..316f2a724d6bd 100644
--- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts
@@ -889,11 +889,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
if (e.kind === 'setAgent') {
this._onDidChangeAgent.fire({ agent: e.agent, slashCommand: e.command });
}
- if (e.kind === 'addRequest') {
- if (this._location.location === ChatAgentLocation.EditingSession) {
- this.chatEditingService.createSnapshot(e.request.id);
- }
- }
}));
if (this.tree && this.visible) {
From a5071119aa0f2de196cc0439af95b57d539b5f1e Mon Sep 17 00:00:00 2001
From: Peng Lyu
Date: Thu, 21 Nov 2024 10:55:27 -0800
Subject: [PATCH 045/118] Notebook snapshot (cells and buffer)
---
.../browser/services/notebookServiceImpl.ts | 31 ++++++++-
.../common/model/notebookTextModel.ts | 65 +++++++++++++++----
.../contrib/notebook/common/notebookCommon.ts | 7 ++
.../notebook/common/notebookEditorModel.ts | 47 +-------------
.../notebook/common/notebookService.ts | 2 +
5 files changed, 93 insertions(+), 59 deletions(-)
diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts
index c57cb325bb36c..ed1dd4f33b642 100644
--- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts
+++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts
@@ -28,7 +28,7 @@ import { INotebookEditorOptions } from '../notebookBrowser.js';
import { NotebookDiffEditorInput } from '../../common/notebookDiffEditorInput.js';
import { NotebookCellTextModel } from '../../common/model/notebookCellTextModel.js';
import { NotebookTextModel } from '../../common/model/notebookTextModel.js';
-import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellUri, NotebookSetting, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, NotebookEditorPriority, NotebookRendererMatch, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, NotebookExtensionDescription, INotebookStaticPreloadInfo } from '../../common/notebookCommon.js';
+import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellUri, NotebookSetting, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, NotebookEditorPriority, NotebookRendererMatch, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, NotebookExtensionDescription, INotebookStaticPreloadInfo, NotebookData } from '../../common/notebookCommon.js';
import { NotebookEditorInput } from '../../common/notebookEditorInput.js';
import { INotebookEditorModelResolverService } from '../../common/notebookEditorModelResolverService.js';
import { NotebookOutputRendererInfo, NotebookStaticPreloadInfo as NotebookStaticPreloadInfo } from '../../common/notebookOutputRenderer.js';
@@ -42,9 +42,12 @@ import { IUriIdentityService } from '../../../../../platform/uriIdentity/common/
import { INotebookDocument, INotebookDocumentService } from '../../../../services/notebook/common/notebookDocumentService.js';
import { MergeEditorInput } from '../../../mergeEditor/browser/mergeEditorInput.js';
import type { EditorInputWithOptions, IResourceDiffEditorInput, IResourceMergeEditorInput } from '../../../../common/editor.js';
-import { streamToBuffer, VSBuffer, VSBufferReadableStream } from '../../../../../base/common/buffer.js';
+import { bufferToStream, streamToBuffer, VSBuffer, VSBufferReadableStream } from '../../../../../base/common/buffer.js';
import type { IEditorGroup } from '../../../../services/editor/common/editorGroupsService.js';
import { NotebookMultiDiffEditorInput } from '../diff/notebookMultiDiffEditorInput.js';
+import { SnapshotContext } from '../../../../services/workingCopy/common/fileWorkingCopy.js';
+import { CancellationToken } from '../../../../../base/common/cancellation.js';
+import { CancellationError } from '../../../../../base/common/errors.js';
export class NotebookProviderInfoStore extends Disposable {
@@ -776,6 +779,30 @@ export class NotebookService extends Disposable implements INotebookService {
return notebookModel;
}
+ async createNotebookTextDocumentSnapshot(uri: URI, context: SnapshotContext, token: CancellationToken): Promise {
+ const model = this.getNotebookTextModel(uri);
+
+ if (!model) {
+ throw new Error(`notebook for ${uri} doesn't exist`);
+ }
+
+ const info = await this.withNotebookDataProvider(model.viewType);
+
+ if (!(info instanceof SimpleNotebookProviderInfo)) {
+ throw new Error('CANNOT open file notebook with this provider');
+ }
+
+ const serializer = info.serializer;
+ const outputSizeLimit = this._configurationService.getValue(NotebookSetting.outputBackupSizeLimit) * 1024;
+ const data: NotebookData = model.createSnapshot({ context: context, outputSizeLimit: outputSizeLimit });
+ const bytes = await serializer.notebookToData(data);
+
+ if (token.isCancellationRequested) {
+ throw new CancellationError();
+ }
+ return bufferToStream(bytes);
+ }
+
getNotebookTextModel(uri: URI): NotebookTextModel | undefined {
return this._models.get(uri)?.model;
}
diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
index c3dbee3af8949..bc039b1aca71c 100644
--- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
+++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
@@ -3,29 +3,31 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { Emitter, Event, PauseableEmitter } from '../../../../../base/common/event.js';
-import { Disposable, dispose, IDisposable } from '../../../../../base/common/lifecycle.js';
-import { URI } from '../../../../../base/common/uri.js';
-import { NotebookCellTextModel } from './notebookCellTextModel.js';
-import { INotebookTextModel, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, diff, NotebookCellsChangeType, ICellDto2, TransientOptions, NotebookTextModelChangedEvent, IOutputDto, ICellOutput, IOutputItemDto, ISelectionState, NullablePartialNotebookCellMetadata, NotebookCellInternalMetadata, NullablePartialNotebookCellInternalMetadata, NotebookTextModelWillAddRemoveEvent, NotebookCellTextModelSplice, ICell, NotebookCellCollapseState, NotebookCellDefaultCollapseConfig, CellKind, NotebookCellExecutionState } from '../notebookCommon.js';
-import { IUndoRedoService, UndoRedoElementType, IUndoRedoElement, IResourceUndoRedoElement, UndoRedoGroup, IWorkspaceUndoRedoElement } from '../../../../../platform/undoRedo/common/undoRedo.js';
-import { MoveCellEdit, SpliceCellsEdit, CellMetadataEdit } from './cellEdit.js';
import { ISequence, LcsDiff } from '../../../../../base/common/diff/diff.js';
+import { Emitter, Event, PauseableEmitter } from '../../../../../base/common/event.js';
import { hash } from '../../../../../base/common/hash.js';
-import { NotebookCellOutputTextModel } from './notebookCellOutputTextModel.js';
-import { IModelService } from '../../../../../editor/common/services/model.js';
+import { Disposable, dispose, IDisposable } from '../../../../../base/common/lifecycle.js';
import { Schemas } from '../../../../../base/common/network.js';
+import { filter } from '../../../../../base/common/objects.js';
import { isEqual } from '../../../../../base/common/resources.js';
-import { ILanguageService } from '../../../../../editor/common/languages/language.js';
-import { FindMatch, ITextModel } from '../../../../../editor/common/model.js';
-import { TextModel } from '../../../../../editor/common/model/textModel.js';
import { isDefined } from '../../../../../base/common/types.js';
-import { ILanguageDetectionService } from '../../../../services/languageDetection/common/languageDetectionWorkerService.js';
+import { URI } from '../../../../../base/common/uri.js';
import { Position } from '../../../../../editor/common/core/position.js';
import { Range } from '../../../../../editor/common/core/range.js';
+import { ILanguageService } from '../../../../../editor/common/languages/language.js';
+import { FindMatch, ITextModel } from '../../../../../editor/common/model.js';
+import { TextModel } from '../../../../../editor/common/model/textModel.js';
import { SearchParams } from '../../../../../editor/common/model/textModelSearch.js';
+import { IModelService } from '../../../../../editor/common/services/model.js';
import { IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js';
+import { IResourceUndoRedoElement, IUndoRedoElement, IUndoRedoService, IWorkspaceUndoRedoElement, UndoRedoElementType, UndoRedoGroup } from '../../../../../platform/undoRedo/common/undoRedo.js';
+import { ILanguageDetectionService } from '../../../../services/languageDetection/common/languageDetectionWorkerService.js';
+import { SnapshotContext } from '../../../../services/workingCopy/common/fileWorkingCopy.js';
+import { CellEditType, CellKind, CellUri, diff, ICell, ICellDto2, ICellEditOperation, ICellOutput, INotebookSnapshotOptions, INotebookTextModel, IOutputDto, IOutputItemDto, ISelectionState, NotebookCellCollapseState, NotebookCellDefaultCollapseConfig, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, NotebookCellsChangeType, NotebookCellTextModelSplice, NotebookData, NotebookDocumentMetadata, NotebookTextModelChangedEvent, NotebookTextModelWillAddRemoveEvent, NullablePartialNotebookCellInternalMetadata, NullablePartialNotebookCellMetadata, TransientOptions } from '../notebookCommon.js';
import { INotebookExecutionStateService } from '../notebookExecutionStateService.js';
+import { CellMetadataEdit, MoveCellEdit, SpliceCellsEdit } from './cellEdit.js';
+import { NotebookCellOutputTextModel } from './notebookCellOutputTextModel.js';
+import { NotebookCellTextModel } from './notebookCellTextModel.js';
class StackOperation implements IWorkspaceUndoRedoElement {
type: UndoRedoElementType.Workspace;
@@ -441,6 +443,43 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
);
}
+ createSnapshot(options: INotebookSnapshotOptions): NotebookData {
+ const data: NotebookData = {
+ metadata: filter(this.metadata, key => !this.transientOptions.transientDocumentMetadata[key]),
+ cells: [],
+ };
+
+ let outputSize = 0;
+ for (const cell of this.cells) {
+ const cellData: ICellDto2 = {
+ cellKind: cell.cellKind,
+ language: cell.language,
+ mime: cell.mime,
+ source: cell.getValue(),
+ outputs: [],
+ internalMetadata: cell.internalMetadata
+ };
+
+ if (options.context === SnapshotContext.Backup && options.outputSizeLimit > 0) {
+ cell.outputs.forEach(output => {
+ output.outputs.forEach(item => {
+ outputSize += item.data.byteLength;
+ });
+ });
+ if (outputSize > options.outputSizeLimit) {
+ throw new Error('Notebook too large to backup');
+ }
+ }
+
+ cellData.outputs = !this.transientOptions.transientOutputs ? cell.outputs : [];
+ cellData.metadata = filter(cell.metadata, key => !this.transientOptions.transientCellMetadata[key]);
+
+ data.cells.push(cellData);
+ }
+
+ return data;
+ }
+
static computeEdits(model: NotebookTextModel, cells: ICellDto2[], executingHandles: number[] = []): ICellEditOperation[] {
const edits: ICellEditOperation[] = [];
const isExecuting = (cell: NotebookCellTextModel) => executingHandles.includes(cell.handle);
diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
index 9abec0e547a7e..25e94b8561993 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
@@ -36,6 +36,7 @@ import { ICellRange } from './notebookRange.js';
import { RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js';
import { generateMetadataUri, generate as generateUri, parseMetadataUri, parse as parseUri } from '../../../services/notebook/common/notebookDocumentService.js';
import { IWorkingCopyBackupMeta, IWorkingCopySaveEvent } from '../../../services/workingCopy/common/workingCopy.js';
+import { SnapshotContext } from '../../../services/workingCopy/common/fileWorkingCopy.js';
export const NOTEBOOK_EDITOR_ID = 'workbench.editor.notebook';
export const NOTEBOOK_DIFF_EDITOR_ID = 'workbench.editor.notebookTextDiffEditor';
@@ -276,6 +277,11 @@ export interface ICell {
onDidChangeInternalMetadata: Event;
}
+export interface INotebookSnapshotOptions {
+ context: SnapshotContext;
+ outputSizeLimit: number;
+}
+
export interface INotebookTextModel extends INotebookTextModelLike {
readonly notebookType: string;
readonly viewType: string;
@@ -286,6 +292,7 @@ export interface INotebookTextModel extends INotebookTextModelLike {
readonly length: number;
readonly cells: readonly ICell[];
reset(cells: ICellDto2[], metadata: NotebookDocumentMetadata, transientOptions: TransientOptions): void;
+ createSnapshot(options: INotebookSnapshotOptions): NotebookData;
applyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: ISelectionState | undefined, endSelectionsComputer: () => ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, computeUndoRedo?: boolean): boolean;
onDidChangeContent: Event;
onWillDispose: Event;
diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
index c48d382960a0d..bab8c66f9425b 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
@@ -3,14 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { VSBufferReadableStream, bufferToStream, streamToBuffer } from '../../../../base/common/buffer.js';
+import { VSBufferReadableStream, streamToBuffer } from '../../../../base/common/buffer.js';
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { CancellationError } from '../../../../base/common/errors.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { IMarkdownString } from '../../../../base/common/htmlContent.js';
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
import { Schemas } from '../../../../base/common/network.js';
-import { filter } from '../../../../base/common/objects.js';
import { assertType } from '../../../../base/common/types.js';
import { URI } from '../../../../base/common/uri.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
@@ -19,7 +18,7 @@ import { ITelemetryService } from '../../../../platform/telemetry/common/telemet
import { IRevertOptions, ISaveOptions, IUntypedEditorInput } from '../../../common/editor.js';
import { EditorModel } from '../../../common/editor/editorModel.js';
import { NotebookTextModel } from './model/notebookTextModel.js';
-import { ICellDto2, INotebookEditorModel, INotebookLoadOptions, IResolvedNotebookEditorModel, NotebookCellsChangeType, NotebookData, NotebookSetting } from './notebookCommon.js';
+import { INotebookEditorModel, INotebookLoadOptions, IResolvedNotebookEditorModel, NotebookCellsChangeType, NotebookSetting } from './notebookCommon.js';
import { INotebookLoggingService } from './notebookLoggingService.js';
import { INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from './notebookService.js';
import { IFilesConfigurationService } from '../../../services/filesConfiguration/common/filesConfigurationService.js';
@@ -294,47 +293,7 @@ export class NotebookFileWorkingCopyModel extends Disposable implements IStoredF
}
async snapshot(context: SnapshotContext, token: CancellationToken): Promise {
- const serializer = await this.getNotebookSerializer();
-
- const data: NotebookData = {
- metadata: filter(this._notebookModel.metadata, key => !serializer.options.transientDocumentMetadata[key]),
- cells: [],
- };
-
- let outputSize = 0;
- for (const cell of this._notebookModel.cells) {
- const cellData: ICellDto2 = {
- cellKind: cell.cellKind,
- language: cell.language,
- mime: cell.mime,
- source: cell.getValue(),
- outputs: [],
- internalMetadata: cell.internalMetadata
- };
-
- const outputSizeLimit = this._configurationService.getValue(NotebookSetting.outputBackupSizeLimit) * 1024;
- if (context === SnapshotContext.Backup && outputSizeLimit > 0) {
- cell.outputs.forEach(output => {
- output.outputs.forEach(item => {
- outputSize += item.data.byteLength;
- });
- });
- if (outputSize > outputSizeLimit) {
- throw new Error('Notebook too large to backup');
- }
- }
-
- cellData.outputs = !serializer.options.transientOutputs ? cell.outputs : [];
- cellData.metadata = filter(cell.metadata, key => !serializer.options.transientCellMetadata[key]);
-
- data.cells.push(cellData);
- }
-
- const bytes = await serializer.notebookToData(data);
- if (token.isCancellationRequested) {
- throw new CancellationError();
- }
- return bufferToStream(bytes);
+ return this._notebookService.createNotebookTextDocumentSnapshot(this._notebookModel.uri, context, token);
}
async update(stream: VSBufferReadableStream, token: CancellationToken): Promise {
diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts
index aa4af44726e2a..553e38a69a9fe 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookService.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts
@@ -18,6 +18,7 @@ import { IFileStatWithMetadata, IWriteFileOptions } from '../../../../platform/f
import { ITextQuery } from '../../../services/search/common/search.js';
import { NotebookPriorityInfo } from '../../search/common/search.js';
import { INotebookFileMatchNoModel } from '../../search/common/searchNotebookHelpers.js';
+import { SnapshotContext } from '../../../services/workingCopy/common/fileWorkingCopy.js';
export const INotebookService = createDecorator('notebookService');
@@ -80,6 +81,7 @@ export interface INotebookService {
saveMimeDisplayOrder(target: ConfigurationTarget): void;
createNotebookTextModel(viewType: string, uri: URI, stream?: VSBufferReadableStream): Promise;
+ createNotebookTextDocumentSnapshot(uri: URI, context: SnapshotContext, token: CancellationToken): Promise;
getNotebookTextModel(uri: URI): NotebookTextModel | undefined;
getNotebookTextModels(): Iterable;
listNotebookDocuments(): readonly NotebookTextModel[];
From d7ab35a98e78953402198337f53c279b37f3bec1 Mon Sep 17 00:00:00 2001
From: Tyler James Leonhardt
Date: Thu, 21 Nov 2024 10:58:49 -0800
Subject: [PATCH 046/118] `handle: string` to `nativeHandle: UInt8Array` based
on feedback (#234378)
Feedback in https://github.com/microsoft/vscode/issues/229431
---
.../src/node/authProvider.ts | 4 ++--
src/vs/platform/window/common/window.ts | 3 ++-
.../platform/windows/electron-main/windowImpl.ts | 3 ++-
src/vs/workbench/api/common/extHost.api.impl.ts | 4 ++--
src/vs/workbench/api/common/extHostWindow.ts | 15 ++++++++++++++-
.../electron-sandbox/environmentService.ts | 3 ++-
.../electron-sandbox/localProcessExtensionHost.ts | 4 ++--
.../vscode.proposed.nativeWindowHandle.d.ts | 6 +++---
8 files changed, 29 insertions(+), 13 deletions(-)
diff --git a/extensions/microsoft-authentication/src/node/authProvider.ts b/extensions/microsoft-authentication/src/node/authProvider.ts
index e5d7440f6f3c7..f6c771e4f9ae4 100644
--- a/extensions/microsoft-authentication/src/node/authProvider.ts
+++ b/extensions/microsoft-authentication/src/node/authProvider.ts
@@ -190,7 +190,7 @@ export class MsalAuthProvider implements AuthenticationProvider {
let result: AuthenticationResult | undefined;
try {
- const windowHandle = env.handle ? Buffer.from(env.handle, 'base64') : undefined;
+ const windowHandle = env.nativeHandle ? Buffer.from(env.nativeHandle) : undefined;
result = await cachedPca.acquireTokenInteractive({
openBrowser: async (url: string) => { await env.openExternal(Uri.parse(url)); },
scopes: scopeData.scopesToSend,
@@ -232,7 +232,7 @@ export class MsalAuthProvider implements AuthenticationProvider {
// The user wants to try the loopback client or we got an error likely due to spinning up the server
const loopbackClient = new UriHandlerLoopbackClient(this._uriHandler, redirectUri, this._logger);
try {
- const windowHandle = env.handle ? Buffer.from(env.handle) : undefined;
+ const windowHandle = env.nativeHandle ? Buffer.from(env.nativeHandle) : undefined;
result = await cachedPca.acquireTokenInteractive({
openBrowser: (url: string) => loopbackClient.openBrowser(url),
scopes: scopeData.scopesToSend,
diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts
index 86fb83e422486..d547c37bf50e4 100644
--- a/src/vs/platform/window/common/window.ts
+++ b/src/vs/platform/window/common/window.ts
@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import { VSBuffer } from '../../../base/common/buffer.js';
import { IStringDictionary } from '../../../base/common/collections.js';
import { PerformanceMark } from '../../../base/common/performance.js';
import { isLinux, isMacintosh, isNative, isWeb } from '../../../base/common/platform.js';
@@ -355,7 +356,7 @@ export interface IOSConfiguration {
export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs, ISandboxConfiguration {
mainPid: number;
- handle?: string;
+ handle?: VSBuffer;
machineId: string;
sqmId: string;
diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts
index 6a3ecb6a7e1aa..3a9132dd3cd96 100644
--- a/src/vs/platform/windows/electron-main/windowImpl.ts
+++ b/src/vs/platform/windows/electron-main/windowImpl.ts
@@ -43,6 +43,7 @@ import { IStateService } from '../../state/node/state.js';
import { IUserDataProfilesMainService } from '../../userDataProfile/electron-main/userDataProfile.js';
import { ILoggerMainService } from '../../log/electron-main/loggerService.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
+import { VSBuffer } from '../../../base/common/buffer.js';
export interface IWindowCreationOptions {
readonly state: IWindowState;
@@ -1093,7 +1094,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow {
// Update window related properties
try {
- configuration.handle = this._win.getNativeWindowHandle().toString('base64');
+ configuration.handle = VSBuffer.wrap(this._win.getNativeWindowHandle());
} catch (error) {
this.logService.error(`Error getting native window handle: ${error}`);
}
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
index 8d2a972f62bd0..672848e05fe61 100644
--- a/src/vs/workbench/api/common/extHost.api.impl.ts
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts
@@ -437,9 +437,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension, 'resolvers');
return initData.commit;
},
- get handle(): string | undefined {
+ get nativeHandle(): Uint8Array | undefined {
checkProposedApiEnabled(extension, 'nativeWindowHandle');
- return initData.handle;
+ return extHostWindow.nativeHandle;
}
};
if (!initData.environment.extensionTestsLocationURI) {
diff --git a/src/vs/workbench/api/common/extHostWindow.ts b/src/vs/workbench/api/common/extHostWindow.ts
index 6f74b721b8a88..e75364dc089cb 100644
--- a/src/vs/workbench/api/common/extHostWindow.ts
+++ b/src/vs/workbench/api/common/extHostWindow.ts
@@ -11,6 +11,8 @@ import { createDecorator } from '../../../platform/instantiation/common/instanti
import { IExtHostRpcService } from './extHostRpcService.js';
import { WindowState } from 'vscode';
import { ExtHostWindowShape, IOpenUriOptions, MainContext, MainThreadWindowShape } from './extHost.protocol.js';
+import { IExtHostInitDataService } from './extHostInitDataService.js';
+import { decodeBase64 } from '../../../base/common/buffer.js';
export class ExtHostWindow implements ExtHostWindowShape {
@@ -24,6 +26,7 @@ export class ExtHostWindow implements ExtHostWindowShape {
private readonly _onDidChangeWindowState = new Emitter();
readonly onDidChangeWindowState: Event = this._onDidChangeWindowState.event;
+ private _nativeHandle: Uint8Array | undefined;
private _state = ExtHostWindow.InitialState;
getState(): WindowState {
@@ -40,7 +43,13 @@ export class ExtHostWindow implements ExtHostWindowShape {
};
}
- constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService) {
+ constructor(
+ @IExtHostInitDataService initData: IExtHostInitDataService,
+ @IExtHostRpcService extHostRpc: IExtHostRpcService
+ ) {
+ if (initData.handle) {
+ this._nativeHandle = decodeBase64(initData.handle).buffer;
+ }
this._proxy = extHostRpc.getProxy(MainContext.MainThreadWindow);
this._proxy.$getInitialState().then(({ isFocused, isActive }) => {
this.onDidChangeWindowProperty('focused', isFocused);
@@ -48,6 +57,10 @@ export class ExtHostWindow implements ExtHostWindowShape {
});
}
+ get nativeHandle(): Uint8Array | undefined {
+ return this._nativeHandle;
+ }
+
$onDidChangeWindowFocus(value: boolean) {
this.onDidChangeWindowProperty('focused', value);
}
diff --git a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts
index aebabc002032a..1f25e5b8aee74 100644
--- a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts
+++ b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts
@@ -14,6 +14,7 @@ import { URI } from '../../../../base/common/uri.js';
import { Schemas } from '../../../../base/common/network.js';
import { IProductService } from '../../../../platform/product/common/productService.js';
import { joinPath } from '../../../../base/common/resources.js';
+import { VSBuffer } from '../../../../base/common/buffer.js';
export const INativeWorkbenchEnvironmentService = refineServiceDecorator(IEnvironmentService);
@@ -26,7 +27,7 @@ export interface INativeWorkbenchEnvironmentService extends IBrowserWorkbenchEnv
// --- Window
readonly window: {
id: number;
- handle?: string;
+ handle?: VSBuffer;
colorScheme: IColorScheme;
maximized?: boolean;
accessibilitySupport?: boolean;
diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts
index c815dbddf6ba8..ff0ebd397e546 100644
--- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts
+++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { timeout } from '../../../../base/common/async.js';
-import { VSBuffer } from '../../../../base/common/buffer.js';
+import { encodeBase64, VSBuffer } from '../../../../base/common/buffer.js';
import { CancellationError } from '../../../../base/common/errors.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
@@ -512,7 +512,7 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost {
logsLocation: this._environmentService.extHostLogsPath,
autoStart: (this.startup === ExtensionHostStartup.EagerAutoStart),
uiKind: UIKind.Desktop,
- handle: this._environmentService.window.handle
+ handle: this._environmentService.window.handle ? encodeBase64(this._environmentService.window.handle) : undefined
};
}
diff --git a/src/vscode-dts/vscode.proposed.nativeWindowHandle.d.ts b/src/vscode-dts/vscode.proposed.nativeWindowHandle.d.ts
index b5699ade4dabf..592f3266e14a5 100644
--- a/src/vscode-dts/vscode.proposed.nativeWindowHandle.d.ts
+++ b/src/vscode-dts/vscode.proposed.nativeWindowHandle.d.ts
@@ -9,9 +9,9 @@ declare module 'vscode' {
export namespace env {
/**
- * Retrieves a base64 representation of a native window
- * handle of the current window.
+ * Retrieves the native window handle of the current active window.
+ * The current active window may not be associated with this extension host.
*/
- export const handle: string | undefined;
+ export const nativeHandle: Uint8Array | undefined;
}
}
From 137a86be40c0121c48d30104347ddf3c2c7bc150 Mon Sep 17 00:00:00 2001
From: Peng Lyu
Date: Thu, 21 Nov 2024 11:02:39 -0800
Subject: [PATCH 047/118] RestoreSnapshot
---
.../browser/services/notebookServiceImpl.ts | 22 +++++++++++++++++++
.../common/model/notebookTextModel.ts | 4 ++++
.../contrib/notebook/common/notebookCommon.ts | 1 +
.../notebook/common/notebookService.ts | 1 +
4 files changed, 28 insertions(+)
diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts
index ed1dd4f33b642..ed2a166a20132 100644
--- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts
+++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts
@@ -803,6 +803,28 @@ export class NotebookService extends Disposable implements INotebookService {
return bufferToStream(bytes);
}
+ async restoreNotebookTextModelFromSnapshot(uri: URI, viewType: string, snapshot: VSBufferReadableStream): Promise {
+ const model = this.getNotebookTextModel(uri);
+
+ if (!model) {
+ throw new Error(`notebook for ${uri} doesn't exist`);
+ }
+
+ const info = await this.withNotebookDataProvider(model.viewType);
+
+ if (!(info instanceof SimpleNotebookProviderInfo)) {
+ throw new Error('CANNOT open file notebook with this provider');
+ }
+
+ const serializer = info.serializer;
+
+ const bytes = await streamToBuffer(snapshot);
+ const data = await info.serializer.dataToNotebook(bytes);
+ model.restoreSnapshot(data, serializer.options);
+
+ return model;
+ }
+
getNotebookTextModel(uri: URI): NotebookTextModel | undefined {
return this._models.get(uri)?.model;
}
diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
index bc039b1aca71c..eda48391ecf59 100644
--- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
+++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
@@ -480,6 +480,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
return data;
}
+ restoreSnapshot(snapshot: NotebookData, transientOptions?: TransientOptions): void {
+ this.reset(snapshot.cells, snapshot.metadata, transientOptions ?? this.transientOptions);
+ }
+
static computeEdits(model: NotebookTextModel, cells: ICellDto2[], executingHandles: number[] = []): ICellEditOperation[] {
const edits: ICellEditOperation[] = [];
const isExecuting = (cell: NotebookCellTextModel) => executingHandles.includes(cell.handle);
diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
index 25e94b8561993..26ea447e9b6cd 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
@@ -293,6 +293,7 @@ export interface INotebookTextModel extends INotebookTextModelLike {
readonly cells: readonly ICell[];
reset(cells: ICellDto2[], metadata: NotebookDocumentMetadata, transientOptions: TransientOptions): void;
createSnapshot(options: INotebookSnapshotOptions): NotebookData;
+ restoreSnapshot(snapshot: NotebookData, transientOptions?: TransientOptions): void;
applyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: ISelectionState | undefined, endSelectionsComputer: () => ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, computeUndoRedo?: boolean): boolean;
onDidChangeContent: Event;
onWillDispose: Event;
diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts
index 553e38a69a9fe..449a985c6882c 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookService.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts
@@ -82,6 +82,7 @@ export interface INotebookService {
createNotebookTextModel(viewType: string, uri: URI, stream?: VSBufferReadableStream): Promise;
createNotebookTextDocumentSnapshot(uri: URI, context: SnapshotContext, token: CancellationToken): Promise;
+ restoreNotebookTextModelFromSnapshot(uri: URI, viewType: string, snapshot: VSBufferReadableStream): Promise;
getNotebookTextModel(uri: URI): NotebookTextModel | undefined;
getNotebookTextModels(): Iterable;
listNotebookDocuments(): readonly NotebookTextModel[];
From 450a3b337f8d592b8b619e23adfd80545d8e6267 Mon Sep 17 00:00:00 2001
From: Connor Peet
Date: Thu, 21 Nov 2024 11:08:49 -0800
Subject: [PATCH 048/118] remote: broaden URI identification on openExternal
(#234380)
I want to protocol-activate copilot in a remote workspace using
openExternal, but I observed the URI was being treated as a path and
malformed. This change broadens the URI identification logic to accept
anything that looks like a protocol. I think this is okay since we
were already excluding file URIs from the rebase, so this really just
impacts any other estoeric URIs in addition to the protocol `urlProtocol`.
fyi @alexdima
---
src/vs/server/node/server.cli.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts
index 4c821c003d5cf..eb68e8aa8b53f 100644
--- a/src/vs/server/node/server.cli.ts
+++ b/src/vs/server/node/server.cli.ts
@@ -375,7 +375,7 @@ function openInBrowser(args: string[], verbose: boolean) {
const uris: string[] = [];
for (const location of args) {
try {
- if (/^(http|https|file):\/\//.test(location)) {
+ if (/^[a-z-]+:\/\/.+/.test(location)) {
uris.push(url.parse(location).href);
} else {
uris.push(pathToURI(location).href);
From 69adedd46bef3d8c820f6cb556d22d314a0b0c03 Mon Sep 17 00:00:00 2001
From: Joyce Er
Date: Thu, 21 Nov 2024 11:13:25 -0800
Subject: [PATCH 049/118] fix: serialize/deserialize working set URIs for
restored chat requests (#234375)
* fix: serialize/deserialize working set URIs for restored chat requests
* tests: update snapshots
---
src/vs/workbench/contrib/chat/common/chatModel.ts | 4 +++-
.../common/__snapshots__/ChatService_can_deserialize.0.snap | 1 +
.../ChatService_can_deserialize_with_response.0.snap | 1 +
.../common/__snapshots__/ChatService_can_serialize.1.snap | 2 ++
.../common/__snapshots__/ChatService_sendRequest_fails.0.snap | 1 +
5 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts
index c70bdf6ab0454..18e8eead70aa7 100644
--- a/src/vs/workbench/contrib/chat/common/chatModel.ts
+++ b/src/vs/workbench/contrib/chat/common/chatModel.ts
@@ -663,6 +663,7 @@ export interface ISerializableChatRequestData {
isHidden: boolean;
responseId?: string;
agent?: ISerializableChatAgentData;
+ workingSet?: UriComponents[];
slashCommand?: IChatAgentCommand;
// responseErrorDetails: IChatResponseErrorDetails | undefined;
result?: IChatAgentResult; // Optional for backcompat
@@ -1004,7 +1005,7 @@ export class ChatModel extends Disposable implements IChatModel {
// Old messages don't have variableData, or have it in the wrong (non-array) shape
const variableData: IChatRequestVariableData = this.reviveVariableData(raw.variableData);
- const request = new ChatRequestModel(this, parsedRequest, variableData, raw.timestamp ?? -1, undefined, undefined, undefined, undefined, undefined, undefined, raw.requestId);
+ const request = new ChatRequestModel(this, parsedRequest, variableData, raw.timestamp ?? -1, undefined, undefined, undefined, undefined, raw.workingSet?.map((uri) => URI.revive(uri)), undefined, raw.requestId);
request.isHidden = !!raw.isHidden;
if (raw.response || raw.result || (raw as any).responseErrorDetails) {
const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format
@@ -1292,6 +1293,7 @@ export class ChatModel extends Disposable implements IChatModel {
vote: r.response?.vote,
voteDownReason: r.response?.voteDownReason,
agent: agentJson,
+ workingSet: r.workingSet,
slashCommand: r.response?.slashCommand,
usedContext: r.response?.usedContext,
contentReferences: r.response?.contentReferences,
diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap
index d6eda2dc105d2..c6069fc01267d 100644
--- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap
+++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap
@@ -78,6 +78,7 @@
slashCommands: [ ],
disambiguation: [ ]
},
+ workingSet: undefined,
slashCommand: undefined,
usedContext: {
documents: [
diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap
index f42a981fc872c..9bd998cda8894 100644
--- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap
+++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap
@@ -78,6 +78,7 @@
slashCommands: [ ],
disambiguation: [ ]
},
+ workingSet: undefined,
slashCommand: undefined,
usedContext: undefined,
contentReferences: [ ],
diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap
index 8ad16f37294b7..39f80194a0b82 100644
--- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap
+++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap
@@ -85,6 +85,7 @@
slashCommands: [ ],
disambiguation: [ ]
},
+ workingSet: undefined,
slashCommand: undefined,
usedContext: {
documents: [
@@ -153,6 +154,7 @@
disambiguation: [ ],
isDefault: true
},
+ workingSet: undefined,
slashCommand: undefined,
usedContext: undefined,
contentReferences: [ ],
diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap
index a4eeca5350e2d..5b280dc8bc6fe 100644
--- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap
+++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap
@@ -78,6 +78,7 @@
slashCommands: [ ],
disambiguation: [ ]
},
+ workingSet: undefined,
slashCommand: undefined,
usedContext: undefined,
contentReferences: [ ],
From 20ecaf99d3bb45555f65fed830a213061ec7a285 Mon Sep 17 00:00:00 2001
From: Peng Lyu
Date: Thu, 21 Nov 2024 11:38:28 -0800
Subject: [PATCH 050/118] fix tests
---
.../browser/services/notebookServiceImpl.ts | 2 +-
.../notebook/common/model/notebookTextModel.ts | 7 ++++---
.../contrib/notebook/common/notebookCommon.ts | 1 +
.../test/browser/notebookEditorModel.test.ts | 16 +++++++++++++---
4 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts
index ed2a166a20132..80d1a7ad448fe 100644
--- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts
+++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts
@@ -794,7 +794,7 @@ export class NotebookService extends Disposable implements INotebookService {
const serializer = info.serializer;
const outputSizeLimit = this._configurationService.getValue(NotebookSetting.outputBackupSizeLimit) * 1024;
- const data: NotebookData = model.createSnapshot({ context: context, outputSizeLimit: outputSizeLimit });
+ const data: NotebookData = model.createSnapshot({ context: context, outputSizeLimit: outputSizeLimit, transientOptions: serializer.options });
const bytes = await serializer.notebookToData(data);
if (token.isCancellationRequested) {
diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
index eda48391ecf59..a7f1c786742ab 100644
--- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
+++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
@@ -444,8 +444,9 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
}
createSnapshot(options: INotebookSnapshotOptions): NotebookData {
+ const transientOptions = options.transientOptions ?? this.transientOptions;
const data: NotebookData = {
- metadata: filter(this.metadata, key => !this.transientOptions.transientDocumentMetadata[key]),
+ metadata: filter(this.metadata, key => !transientOptions.transientDocumentMetadata[key]),
cells: [],
};
@@ -471,8 +472,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
}
}
- cellData.outputs = !this.transientOptions.transientOutputs ? cell.outputs : [];
- cellData.metadata = filter(cell.metadata, key => !this.transientOptions.transientCellMetadata[key]);
+ cellData.outputs = !transientOptions.transientOutputs ? cell.outputs : [];
+ cellData.metadata = filter(cell.metadata, key => !transientOptions.transientCellMetadata[key]);
data.cells.push(cellData);
}
diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
index 26ea447e9b6cd..300b9209edc4e 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
@@ -280,6 +280,7 @@ export interface ICell {
export interface INotebookSnapshotOptions {
context: SnapshotContext;
outputSizeLimit: number;
+ transientOptions?: TransientOptions;
}
export interface INotebookTextModel extends INotebookTextModelLike {
diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts
index 48f4e9a231a80..5ab182642248f 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import assert from 'assert';
-import { VSBuffer } from '../../../../../base/common/buffer.js';
+import { bufferToStream, VSBuffer, VSBufferReadableStream } from '../../../../../base/common/buffer.js';
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
import { Mimes } from '../../../../../base/common/mime.js';
@@ -246,7 +246,8 @@ suite('NotebookFileWorkingCopyModel', function () {
assert.strictEqual(notebook.cells[0].metadata!.bar, undefined);
return VSBuffer.fromString('');
}
- }
+ },
+ configurationService
),
configurationService,
telemetryService,
@@ -309,7 +310,7 @@ suite('NotebookFileWorkingCopyModel', function () {
});
});
-function mockNotebookService(notebook: NotebookTextModel, notebookSerializer: Promise | INotebookSerializer) {
+function mockNotebookService(notebook: NotebookTextModel, notebookSerializer: Promise | INotebookSerializer, configurationService: TestConfigurationService = new TestConfigurationService()): INotebookService {
return new class extends mock() {
private serializer: INotebookSerializer | undefined = undefined;
override async withNotebookDataProvider(viewType: string): Promise {
@@ -336,5 +337,14 @@ function mockNotebookService(notebook: NotebookTextModel, notebookSerializer: Pr
}
);
}
+ override async createNotebookTextDocumentSnapshot(uri: URI, context: SnapshotContext, token: CancellationToken): Promise {
+ const info = await this.withNotebookDataProvider(notebook.viewType);
+ const serializer = info.serializer;
+ const outputSizeLimit = configurationService.getValue(NotebookSetting.outputBackupSizeLimit) ?? 1024;
+ const data: NotebookData = notebook.createSnapshot({ context: context, outputSizeLimit: outputSizeLimit, transientOptions: serializer.options });
+ const bytes = await serializer.notebookToData(data);
+
+ return bufferToStream(bytes);
+ }
};
}
From b5c3a7f91362dba398912e4ac5dc1750864e0c89 Mon Sep 17 00:00:00 2001
From: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
Date: Thu, 21 Nov 2024 11:40:45 -0800
Subject: [PATCH 051/118] Fix transparent color test
---
src/vs/base/test/common/color.test.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/vs/base/test/common/color.test.ts b/src/vs/base/test/common/color.test.ts
index ca5db568ca67f..5410575cc3304 100644
--- a/src/vs/base/test/common/color.test.ts
+++ b/src/vs/base/test/common/color.test.ts
@@ -212,7 +212,7 @@ suite('Color', () => {
});
test('transparent', () => {
- assert.deepStrictEqual(Color.Format.CSS.parse('transparent'), new Color(new RGBA(0, 0, 0, 0)));
+ assert.deepStrictEqual(Color.Format.CSS.parse('transparent')!.rgba, new RGBA(0, 0, 0, 0));
});
test('named keyword', () => {
From 3e0bc9e864df15e06dfa5b3e7f079b9d11816931 Mon Sep 17 00:00:00 2001
From: Joyce Er
Date: Thu, 21 Nov 2024 12:38:12 -0800
Subject: [PATCH 052/118] feat: allow removing files from working set (#234385)
---
.../browser/chatEditing/chatEditingActions.ts | 44 ++++++++++++++++++-
.../browser/chatEditing/chatEditingSession.ts | 20 ++++++---
.../contrib/chat/browser/chatInputPart.ts | 4 +-
3 files changed, 58 insertions(+), 10 deletions(-)
diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts
index 45a79d8eaafc3..c52b0ae8f7cad 100644
--- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts
@@ -86,8 +86,8 @@ registerAction2(class RemoveFileFromWorkingSet extends WorkingSetAction {
icon: Codicon.close,
menu: [{
id: MenuId.ChatEditingWidgetModifiedFilesToolbar,
- when: ContextKeyExpr.or(ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Attached), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Suggested), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Transient)),
- order: 0,
+ // when: ContextKeyExpr.or(ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Attached), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Suggested), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Transient)),
+ order: 5,
group: 'navigation'
}],
});
@@ -287,6 +287,46 @@ export class ChatEditingDiscardAllAction extends Action2 {
}
registerAction2(ChatEditingDiscardAllAction);
+export class ChatEditingRemoveAllFilesAction extends Action2 {
+ static readonly ID = 'chatEditing.removeAllFiles';
+
+ constructor() {
+ super({
+ id: ChatEditingRemoveAllFilesAction.ID,
+ title: localize('removeAll', 'Remove All'),
+ icon: Codicon.clearAll,
+ tooltip: localize('removeAllFiles', 'Remove All Files'),
+ precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate()),
+ menu: [
+ {
+ id: MenuId.ChatEditingWidgetToolbar,
+ group: 'navigation',
+ order: 5,
+ when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession))
+ }
+ ]
+ });
+ }
+
+ async run(accessor: ServicesAccessor, ...args: any[]): Promise {
+ const chatEditingService = accessor.get(IChatEditingService);
+ const currentEditingSession = chatEditingService.currentEditingSession;
+ if (!currentEditingSession) {
+ return;
+ }
+
+ const chatWidget = accessor.get(IChatWidgetService).getWidgetBySessionId(currentEditingSession.chatSessionId);
+ const uris = [...currentEditingSession.workingSet.keys()];
+ currentEditingSession.remove(WorkingSetEntryRemovalReason.User, ...uris);
+ for (const uri of chatWidget?.attachmentModel.attachments ?? []) {
+ if (uri.isFile && URI.isUri(uri.value)) {
+ chatWidget?.attachmentModel.delete(uri.value.toString());
+ }
+ }
+ }
+}
+registerAction2(ChatEditingRemoveAllFilesAction);
+
export class ChatEditingShowChangesAction extends Action2 {
static readonly ID = 'chatEditing.viewChanges';
static readonly LABEL = localize('chatEditing.viewChanges', 'View All Edits');
diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts
index 220fc5a30bfd1..d82246020c116 100644
--- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts
@@ -319,13 +319,21 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
let didRemoveUris = false;
for (const uri of uris) {
- const state = this._workingSet.get(uri);
- if (state === undefined) {
- continue;
+
+ const entry = this._entriesObs.get().find(e => isEqual(e.modifiedURI, uri));
+ if (entry) {
+ entry.dispose();
+ const newEntries = this._entriesObs.get().filter(e => !isEqual(e.modifiedURI, uri));
+ this._entriesObs.set(newEntries, undefined);
+ didRemoveUris = true;
}
- didRemoveUris = this._workingSet.delete(uri) || didRemoveUris;
- if (reason === WorkingSetEntryRemovalReason.User && (state.state === WorkingSetEntryState.Transient || state.state === WorkingSetEntryState.Suggested)) {
- this._removedTransientEntries.add(uri);
+
+ const state = this._workingSet.get(uri);
+ if (state !== undefined) {
+ didRemoveUris = this._workingSet.delete(uri) || didRemoveUris;
+ if (reason === WorkingSetEntryRemovalReason.User && (state.state === WorkingSetEntryState.Transient || state.state === WorkingSetEntryState.Suggested)) {
+ this._removedTransientEntries.add(uri);
+ }
}
}
diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts
index 4158cc41cfe58..135718089b2bf 100644
--- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts
@@ -91,7 +91,7 @@ import { ChatAttachmentModel, EditsAttachmentModel } from './chatAttachmentModel
import { IDisposableReference } from './chatContentParts/chatCollections.js';
import { CollapsibleListPool, IChatCollapsibleListItem } from './chatContentParts/chatReferencesContentPart.js';
import { ChatDragAndDrop, EditsDragAndDrop } from './chatDragAndDrop.js';
-import { ChatEditingShowChangesAction } from './chatEditing/chatEditingActions.js';
+import { ChatEditingRemoveAllFilesAction, ChatEditingShowChangesAction } from './chatEditing/chatEditingActions.js';
import { ChatEditingSaveAllAction } from './chatEditorSaving.js';
import { ChatFollowups } from './chatFollowups.js';
import { IChatViewState } from './chatWidget.js';
@@ -1180,7 +1180,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
arg: { sessionId: chatEditingSession.chatSessionId },
},
buttonConfigProvider: (action) => {
- if (action.id === ChatEditingShowChangesAction.ID || action.id === ChatEditingSaveAllAction.ID) {
+ if (action.id === ChatEditingShowChangesAction.ID || action.id === ChatEditingSaveAllAction.ID || action.id === ChatEditingRemoveAllFilesAction.ID) {
return { showIcon: true, showLabel: false, isSecondary: true };
}
return undefined;
From ec1e84b5514c8c89ab7f6ea5416ad4fb91e2a650 Mon Sep 17 00:00:00 2001
From: Benjamin Pasero
Date: Thu, 21 Nov 2024 21:49:31 +0100
Subject: [PATCH 053/118] chat - make hide action globally accessible (#234383)
---
.../workbench/contrib/chat/browser/chatSetup.contribution.ts | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
index df27d811fdd82..26f4abedbfa52 100644
--- a/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
+++ b/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts
@@ -426,6 +426,11 @@ class ChatSetupHideAction extends Action2 {
super({
id: ChatSetupHideAction.ID,
title: ChatSetupHideAction.TITLE,
+ f1: true,
+ precondition: ContextKeyExpr.and(
+ ChatContextKeys.Setup.triggered,
+ ChatContextKeys.Setup.installed.negate()
+ ),
menu: {
id: MenuId.ChatCommandCenter,
group: 'a_first',
From f2fba1b45d47d98ee688de9c84df865c300ab593 Mon Sep 17 00:00:00 2001
From: Megan Rogge
Date: Thu, 21 Nov 2024 16:28:42 -0500
Subject: [PATCH 054/118] improve folder/file terminal completions (#234363)
---
extensions/terminal-suggest/README.md | 2 +-
.../terminal-suggest/src/completions/cd.ts | 29 +++
.../src/terminalSuggestMain.ts | 190 +++++++++++-------
.../browser/terminalCompletionService.ts | 70 ++++---
.../common/terminalSuggestConfiguration.ts | 2 +-
5 files changed, 180 insertions(+), 113 deletions(-)
create mode 100644 extensions/terminal-suggest/src/completions/cd.ts
diff --git a/extensions/terminal-suggest/README.md b/extensions/terminal-suggest/README.md
index d93e4620567b3..adaffc410ac40 100644
--- a/extensions/terminal-suggest/README.md
+++ b/extensions/terminal-suggest/README.md
@@ -4,4 +4,4 @@
## Features
-Provides terminal suggestions for zsh, bash, and fish.
+Provides terminal suggestions for zsh, bash, fish, and pwsh.
diff --git a/extensions/terminal-suggest/src/completions/cd.ts b/extensions/terminal-suggest/src/completions/cd.ts
new file mode 100644
index 0000000000000..89fc633911b27
--- /dev/null
+++ b/extensions/terminal-suggest/src/completions/cd.ts
@@ -0,0 +1,29 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+const cdSpec: Fig.Spec = {
+ name: 'cd',
+ description: 'Change the shell working directory',
+ args: {
+ name: 'folder',
+ template: 'folders',
+ isVariadic: true,
+
+ suggestions: [
+ {
+ name: '-',
+ description: 'Switch to the last used folder',
+ hidden: true,
+ },
+ {
+ name: '~',
+ description: 'Switch to the home directory',
+ hidden: true,
+ },
+ ],
+ }
+};
+
+export default cdSpec;
diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts
index a8d537334e703..1ff88ed9eb6bf 100644
--- a/extensions/terminal-suggest/src/terminalSuggestMain.ts
+++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts
@@ -9,6 +9,7 @@ import * as path from 'path';
import { ExecOptionsWithStringEncoding, execSync } from 'child_process';
import codeInsidersCompletionSpec from './completions/code-insiders';
import codeCompletionSpec from './completions/code';
+import cdSpec from './completions/cd';
let cachedAvailableCommands: Set | undefined;
let cachedBuiltinCommands: Map | undefined;
@@ -20,8 +21,7 @@ function getBuiltinCommands(shell: string): string[] | undefined {
if (cachedCommands) {
return cachedCommands;
}
- // fixes a bug with file/folder completions brought about by the '.' command
- const filter = (cmd: string) => cmd && cmd !== '.';
+ const filter = (cmd: string) => cmd;
const options: ExecOptionsWithStringEncoding = { encoding: 'utf-8', shell };
switch (shellType) {
case 'bash': {
@@ -52,8 +52,11 @@ function getBuiltinCommands(shell: string): string[] | undefined {
}
break;
}
+ case 'pwsh': {
+ // native pwsh completions are builtin to vscode
+ return [];
+ }
}
- // native pwsh completions are builtin to vscode
return;
} catch (error) {
@@ -62,7 +65,6 @@ function getBuiltinCommands(shell: string): string[] | undefined {
}
}
-
export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.window.registerTerminalCompletionProvider({
id: 'terminal-suggest',
@@ -87,12 +89,12 @@ export async function activate(context: vscode.ExtensionContext) {
const items: vscode.TerminalCompletionItem[] = [];
const prefix = getPrefix(terminalContext.commandLine, terminalContext.cursorPosition);
- const specs = [codeCompletionSpec, codeInsidersCompletionSpec];
+ const specs = [codeCompletionSpec, codeInsidersCompletionSpec, cdSpec];
const specCompletions = await getCompletionItemsFromSpecs(specs, terminalContext, new Set(commands), prefix, token);
+ items.push(...specCompletions.items);
let filesRequested = specCompletions.filesRequested;
let foldersRequested = specCompletions.foldersRequested;
- items.push(...specCompletions.items);
if (!specCompletions.specificSuggestionsProvided) {
for (const command of commands) {
@@ -106,26 +108,26 @@ export async function activate(context: vscode.ExtensionContext) {
return undefined;
}
- const uniqueResults = new Map();
- for (const item of items) {
- if (!uniqueResults.has(item.label)) {
- uniqueResults.set(item.label, item);
- }
- }
- const resultItems = uniqueResults.size ? Array.from(uniqueResults.values()) : undefined;
+ const shouldShowResourceCompletions =
+ (
+ // If the command line is empty
+ terminalContext.commandLine.trim().length === 0
+ // or no completions are found
+ || !items?.length
+ // or the completion found is '.'
+ || items.length === 1 && items[0].label === '.'
+ )
+ // and neither files nor folders are going to be requested (for a specific spec's argument)
+ && (!filesRequested && !foldersRequested);
- // If no completions are found, the prefix is a path, and neither files nor folders
- // are going to be requested (for a specific spec's argument), show file/folder completions
- const shouldShowResourceCompletions = !resultItems?.length && prefix.match(/^[./\\ ]/) && !filesRequested && !foldersRequested;
if (shouldShowResourceCompletions) {
filesRequested = true;
foldersRequested = true;
}
-
if (filesRequested || foldersRequested) {
- return new vscode.TerminalCompletionList(resultItems, { filesRequested, foldersRequested, cwd: terminal.shellIntegration?.cwd, pathSeparator: shellPath.includes('/') ? '/' : '\\' });
+ return new vscode.TerminalCompletionList(items, { filesRequested, foldersRequested, cwd: terminal.shellIntegration?.cwd, pathSeparator: osIsWindows() ? '\\' : '/' });
}
- return resultItems;
+ return items;
}
}));
}
@@ -157,7 +159,7 @@ async function getCommandsInPath(): Promise | undefined> {
if (cachedAvailableCommands) {
return cachedAvailableCommands;
}
- const paths = os.platform() === 'win32' ? process.env.PATH?.split(';') : process.env.PATH?.split(':');
+ const paths = osIsWindows() ? process.env.PATH?.split(';') : process.env.PATH?.split(':');
if (!paths) {
return;
}
@@ -213,7 +215,7 @@ export function asArray(x: T | T[]): T[] {
}
function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext: { commandLine: string; cursorPosition: number }, availableCommands: Set, prefix: string, token: vscode.CancellationToken): { items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; specificSuggestionsProvided: boolean } {
- let items: vscode.TerminalCompletionItem[] = [];
+ const items: vscode.TerminalCompletionItem[] = [];
let filesRequested = false;
let foldersRequested = false;
for (const spec of specs) {
@@ -222,73 +224,105 @@ function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext: { comma
continue;
}
for (const specLabel of specLabels) {
- if (!availableCommands.has(specLabel) || token.isCancellationRequested) {
+ if (!availableCommands.has(specLabel) || token.isCancellationRequested || !terminalContext.commandLine.startsWith(specLabel)) {
continue;
}
- if (terminalContext.commandLine.startsWith(specLabel)) {
- if ('options' in spec && spec.options) {
- for (const option of spec.options) {
- const optionLabels = getLabel(option);
- if (!optionLabels) {
+ const precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1);
+ if ('options' in spec && spec.options) {
+ for (const option of spec.options) {
+ const optionLabels = getLabel(option);
+ if (!optionLabels) {
+ continue;
+ }
+ for (const optionLabel of optionLabels) {
+ if (optionLabel.startsWith(prefix) || (prefix.length > specLabel.length && prefix.trim() === specLabel)) {
+ items.push(createCompletionItem(terminalContext.cursorPosition, prefix, optionLabel, option.description, false, vscode.TerminalCompletionItemKind.Flag));
+ }
+ const expectedText = `${specLabel} ${optionLabel} `;
+ if (!precedingText.includes(expectedText)) {
continue;
}
- for (const optionLabel of optionLabels) {
- if (optionLabel.startsWith(prefix) || (prefix.length > specLabel.length && prefix.trim() === specLabel)) {
- items.push(createCompletionItem(terminalContext.cursorPosition, prefix, optionLabel, option.description, false, vscode.TerminalCompletionItemKind.Flag));
- }
- if (!option.args) {
- continue;
- }
- const args = asArray(option.args);
- for (const arg of args) {
- if (!arg) {
- continue;
- }
- const precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1);
- const expectedText = `${specLabel} ${optionLabel} `;
- if (!precedingText.includes(expectedText)) {
- continue;
- }
- if (arg.template) {
- if (arg.template === 'filepaths') {
- if (precedingText.includes(expectedText)) {
- filesRequested = true;
- }
- } else if (arg.template === 'folders') {
- if (precedingText.includes(expectedText)) {
- foldersRequested = true;
- }
- }
- }
- if (arg.suggestions?.length) {
- // there are specific suggestions to show
- items = [];
- const indexOfPrecedingText = terminalContext.commandLine.lastIndexOf(expectedText);
- const currentPrefix = precedingText.slice(indexOfPrecedingText + expectedText.length);
- for (const suggestion of arg.suggestions) {
- const suggestionLabels = getLabel(suggestion);
- if (!suggestionLabels) {
- continue;
- }
- for (const suggestionLabel of suggestionLabels) {
- if (suggestionLabel && suggestionLabel.startsWith(currentPrefix.trim())) {
- const hasSpaceBeforeCursor = terminalContext.commandLine[terminalContext.cursorPosition - 1] === ' ';
- // prefix will be '' if there is a space before the cursor
- items.push(createCompletionItem(terminalContext.cursorPosition, precedingText, suggestionLabel, arg.name, hasSpaceBeforeCursor, vscode.TerminalCompletionItemKind.Argument));
- }
- }
- }
- if (items.length) {
- return { items, filesRequested, foldersRequested, specificSuggestionsProvided: true };
- }
- }
- }
+ const indexOfPrecedingText = terminalContext.commandLine.lastIndexOf(expectedText);
+ const currentPrefix = precedingText.slice(indexOfPrecedingText + expectedText.length);
+ const argsCompletions = getCompletionItemsFromArgs(option.args, currentPrefix, terminalContext, precedingText);
+ if (!argsCompletions) {
+ continue;
+ }
+ if (argsCompletions.specificSuggestionsProvided) {
+ // prevents the list from containing a bunch of other stuff
+ return argsCompletions;
}
+ items.push(...argsCompletions.items);
+ filesRequested = filesRequested || argsCompletions.filesRequested;
+ foldersRequested = foldersRequested || argsCompletions.foldersRequested;
}
}
}
+ if ('args' in spec && asArray(spec.args)) {
+ const expectedText = `${specLabel} `;
+ if (!precedingText.includes(expectedText)) {
+ continue;
+ }
+ const indexOfPrecedingText = terminalContext.commandLine.lastIndexOf(expectedText);
+ const currentPrefix = precedingText.slice(indexOfPrecedingText + expectedText.length);
+ const argsCompletions = getCompletionItemsFromArgs(spec.args, currentPrefix, terminalContext, precedingText);
+ if (!argsCompletions) {
+ continue;
+ }
+ items.push(...argsCompletions.items);
+ filesRequested = filesRequested || argsCompletions.filesRequested;
+ foldersRequested = foldersRequested || argsCompletions.foldersRequested;
+ }
}
}
return { items, filesRequested, foldersRequested, specificSuggestionsProvided: false };
}
+function getCompletionItemsFromArgs(args: Fig.SingleOrArray | undefined, currentPrefix: string, terminalContext: { commandLine: string; cursorPosition: number }, precedingText: string): { items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; specificSuggestionsProvided: boolean } | undefined {
+ if (!args) {
+ return;
+ }
+
+ let items: vscode.TerminalCompletionItem[] = [];
+ let filesRequested = false;
+ let foldersRequested = false;
+ for (const arg of asArray(args)) {
+ if (!arg) {
+ continue;
+ }
+ if (arg.template) {
+ if (arg.template === 'filepaths') {
+ filesRequested = true;
+ } else if (arg.template === 'folders') {
+ foldersRequested = true;
+ }
+ }
+ if (arg.suggestions?.length) {
+ // there are specific suggestions to show
+ items = [];
+ for (const suggestion of arg.suggestions) {
+ const suggestionLabels = getLabel(suggestion);
+ if (!suggestionLabels) {
+ continue;
+ }
+
+ for (const suggestionLabel of suggestionLabels) {
+ if (suggestionLabel && suggestionLabel.startsWith(currentPrefix.trim())) {
+ const hasSpaceBeforeCursor = terminalContext.commandLine[terminalContext.cursorPosition - 1] === ' ';
+ // prefix will be '' if there is a space before the cursor
+ const description = typeof suggestion !== 'string' ? suggestion.description : '';
+ items.push(createCompletionItem(terminalContext.cursorPosition, precedingText, suggestionLabel, description, hasSpaceBeforeCursor, vscode.TerminalCompletionItemKind.Argument));
+ }
+ }
+ }
+ if (items.length) {
+ return { items, filesRequested, foldersRequested, specificSuggestionsProvided: true };
+ }
+ }
+ }
+ return { items, filesRequested, foldersRequested, specificSuggestionsProvided: false };
+}
+
+function osIsWindows(): boolean {
+ return os.platform() === 'win32';
+}
diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts
index 4e8379c847f93..bee024edb19eb 100644
--- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts
+++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { Disposable, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';
+import { Schemas } from '../../../../../base/common/network.js';
import { URI } from '../../../../../base/common/uri.js';
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
import { IFileService } from '../../../../../platform/files/common/files.js';
@@ -197,44 +198,47 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
}
const resourceCompletions: ITerminalCompletion[] = [];
- const fileStat = await this._fileService.resolve(cwd, { resolveSingleChildDescendants: true });
- if (!fileStat || !fileStat?.children) {
- return;
- }
+ const parentDirPath = cwd.fsPath.split(resourceRequestConfig.pathSeparator).slice(0, -1).join(resourceRequestConfig.pathSeparator);
+ const parentCwd = URI.from({ scheme: cwd.scheme, path: parentDirPath });
+ const dirToPrefixMap = new Map();
- for (const stat of fileStat.children) {
- let kind: TerminalCompletionItemKind | undefined;
- if (foldersRequested && stat.isDirectory) {
- kind = TerminalCompletionItemKind.Folder;
- }
- if (filesRequested && !stat.isDirectory && (stat.isFile || stat.resource.scheme === 'file')) {
- kind = TerminalCompletionItemKind.File;
- }
- if (kind === undefined) {
- continue;
- }
- const lastWord = promptValue.substring(0, cursorPosition).split(' ').pop();
- const lastIndexOfDot = lastWord?.lastIndexOf('.') ?? -1;
- const lastIndexOfSlash = lastWord?.lastIndexOf(resourceRequestConfig.pathSeparator) ?? -1;
- let label;
- if (lastIndexOfSlash > -1) {
- label = stat.resource.fsPath.replace(cwd.fsPath, '').substring(1);
- } else if (lastIndexOfDot === -1) {
- label = '.' + stat.resource.fsPath.replace(cwd.fsPath, '');
- } else {
- label = stat.resource.fsPath.replace(cwd.fsPath, '');
+ dirToPrefixMap.set(cwd, '.');
+ dirToPrefixMap.set(parentCwd, '..');
+
+ const lastWord = promptValue.substring(0, cursorPosition).split(' ').pop() ?? '';
+
+ for (const [dir, prefix] of dirToPrefixMap) {
+ const fileStat = await this._fileService.resolve(dir, { resolveSingleChildDescendants: true });
+
+ if (!fileStat || !fileStat?.children) {
+ return;
}
- resourceCompletions.push({
- label,
- kind,
- isDirectory: kind === TerminalCompletionItemKind.Folder,
- isFile: kind === TerminalCompletionItemKind.File,
- replacementIndex: cursorPosition,
- replacementLength: label.length
- });
+ for (const stat of fileStat.children) {
+ let kind: TerminalCompletionItemKind | undefined;
+ if (foldersRequested && stat.isDirectory) {
+ kind = TerminalCompletionItemKind.Folder;
+ }
+ if (filesRequested && !stat.isDirectory && (stat.isFile || stat.resource.scheme === Schemas.file)) {
+ kind = TerminalCompletionItemKind.File;
+ }
+ if (kind === undefined) {
+ continue;
+ }
+
+ const label = prefix + stat.resource.fsPath.replace(cwd.fsPath, '');
+ resourceCompletions.push({
+ label,
+ kind,
+ isDirectory: kind === TerminalCompletionItemKind.Folder,
+ isFile: kind === TerminalCompletionItemKind.File,
+ replacementIndex: cursorPosition - lastWord.length,
+ replacementLength: label.length
+ });
+ }
}
+
return resourceCompletions.length ? resourceCompletions : undefined;
}
}
diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts
index 0779fb33879dd..860bb28015acc 100644
--- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts
+++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts
@@ -34,7 +34,7 @@ export interface ITerminalSuggestConfiguration {
export const terminalSuggestConfiguration: IStringDictionary = {
[TerminalSuggestSettingId.Enabled]: {
restricted: true,
- markdownDescription: localize('suggest.enabled', "Enables experimental terminal Intellisense suggestions for supported shells ({0}) when {1} is set to {2}.\n\nIf shell integration is installed manually, {3} needs to be set to {4} before calling the shell integration script. \n\nFor zsh and bash completions, {5} will also need to be set.", 'PowerShell v7+, zsh, bash', `\`#${TerminalSettingId.ShellIntegrationEnabled}#\``, '`true`', '`VSCODE_SUGGEST`', '`1`', `\`#${TerminalSuggestSettingId.EnableExtensionCompletions}#\``),
+ markdownDescription: localize('suggest.enabled', "Enables experimental terminal Intellisense suggestions for supported shells ({0}) when {1} is set to {2}.\n\nIf shell integration is installed manually, {3} needs to be set to {4} before calling the shell integration script. \n\nFor extension provided completions, {5} will also need to be set.", 'PowerShell v7+, zsh, bash, fish', `\`#${TerminalSettingId.ShellIntegrationEnabled}#\``, '`true`', '`VSCODE_SUGGEST`', '`1`', `\`#${TerminalSuggestSettingId.EnableExtensionCompletions}#\``),
type: 'boolean',
default: false,
tags: ['experimental'],
From 927f53de441bf9146cc50d8863e67f45502b41e1 Mon Sep 17 00:00:00 2001
From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com>
Date: Thu, 21 Nov 2024 23:18:49 +0100
Subject: [PATCH 055/118] Git - tweak git blame computation (#234386)
* Helper methods
* Finished implementing the prototype
* Command handled model creation/disposal
* Cache staged resources diff information
---
extensions/git/src/blame.ts | 149 +++++++++---------
extensions/git/src/git.ts | 11 +-
extensions/git/src/repository.ts | 4 +-
.../browser/parts/editor/editorCommands.ts | 31 ++++
4 files changed, 120 insertions(+), 75 deletions(-)
diff --git a/extensions/git/src/blame.ts b/extensions/git/src/blame.ts
index 10c8e0d98f21e..fc0f74fcf169a 100644
--- a/extensions/git/src/blame.ts
+++ b/extensions/git/src/blame.ts
@@ -3,15 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command, MarkdownString } from 'vscode';
+import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command, MarkdownString, commands, LineChange } from 'vscode';
import { Model } from './model';
import { dispose, fromNow, IDisposable, pathEquals } from './util';
import { Repository } from './repository';
import { throttle } from './decorators';
import { BlameInformation } from './git';
-const notCommittedYetId = '0000000000000000000000000000000000000000';
-
function isLineChanged(lineNumber: number, changes: readonly TextEditorChange[]): boolean {
for (const change of changes) {
// If the change is a delete, skip it
@@ -64,28 +62,6 @@ function mapLineNumber(lineNumber: number, changes: readonly TextEditorChange[])
return lineNumber;
}
-function processTextEditorChangesWithBlameInformation(blameInformation: BlameInformation[], changes: readonly TextEditorChange[]): TextEditorChange[] {
- const [notYetCommittedBlameInformation] = blameInformation.filter(b => b.id === notCommittedYetId);
- if (!notYetCommittedBlameInformation) {
- return [...changes];
- }
-
- const changesWithBlameInformation: TextEditorChange[] = [];
- for (const change of changes) {
- const originalStartLineNumber = mapLineNumber(change.originalStartLineNumber, changes);
- const originalEndLineNumber = mapLineNumber(change.originalEndLineNumber, changes);
-
- if (notYetCommittedBlameInformation.ranges.some(range =>
- range.startLineNumber === originalStartLineNumber && range.endLineNumber === originalEndLineNumber)) {
- continue;
- }
-
- changesWithBlameInformation.push(change);
- }
-
- return changesWithBlameInformation;
-}
-
function getBlameInformationHover(documentUri: Uri, blameInformation: BlameInformation | string): MarkdownString {
if (typeof blameInformation === 'string') {
return new MarkdownString(blameInformation, true);
@@ -123,12 +99,7 @@ function getBlameInformationHover(documentUri: Uri, blameInformation: BlameInfor
interface RepositoryBlameInformation {
readonly commit: string; /* commit used for blame information */
- readonly blameInformation: Map;
-}
-
-interface ResourceBlameInformation {
- readonly staged: boolean; /* whether the file is staged */
- readonly blameInformation: BlameInformation[];
+ readonly blameInformation: Map;
}
interface LineBlameInformation {
@@ -141,7 +112,9 @@ export class GitBlameController {
public readonly onDidChangeBlameInformation = this._onDidChangeBlameInformation.event;
readonly textEditorBlameInformation = new Map();
+
private readonly _repositoryBlameInformation = new Map();
+ private readonly _stagedResourceDiffInformation = new Map>();
private _repositoryDisposables = new Map();
private _disposables: IDisposable[] = [];
@@ -163,6 +136,8 @@ export class GitBlameController {
const repositoryDisposables: IDisposable[] = [];
repository.onDidRunGitStatus(() => this._onDidRunGitStatus(repository), this, repositoryDisposables);
+ repository.onDidChangeRepository(e => this._onDidChangeRepository(repository, e), this, this._disposables);
+
this._repositoryDisposables.set(repository, repositoryDisposables);
}
@@ -177,38 +152,29 @@ export class GitBlameController {
}
private _onDidRunGitStatus(repository: Repository): void {
- let repositoryBlameInformation = this._repositoryBlameInformation.get(repository);
+ const repositoryBlameInformation = this._repositoryBlameInformation.get(repository);
if (!repositoryBlameInformation) {
return;
}
- let updateDecorations = false;
-
- // 1. HEAD commit changed (remove all blame information for the repository)
+ // HEAD commit changed (remove blame information for the repository)
if (repositoryBlameInformation.commit !== repository.HEAD?.commit) {
this._repositoryBlameInformation.delete(repository);
- repositoryBlameInformation = undefined;
- updateDecorations = true;
- }
-
- // 2. Resource has been staged/unstaged (remove blame information for the resource)
- for (const [uri, resourceBlameInformation] of repositoryBlameInformation?.blameInformation.entries() ?? []) {
- const isStaged = repository.indexGroup.resourceStates
- .some(r => pathEquals(uri.fsPath, r.resourceUri.fsPath));
- if (resourceBlameInformation.staged !== isStaged) {
- repositoryBlameInformation?.blameInformation.delete(uri);
- updateDecorations = true;
- }
- }
-
- if (updateDecorations) {
for (const textEditor of window.visibleTextEditors) {
this._updateTextEditorBlameInformation(textEditor);
}
}
}
+ private _onDidChangeRepository(repository: Repository, uri: Uri): void {
+ if (!/\.git\/index$/.test(uri.fsPath)) {
+ return;
+ }
+
+ this._stagedResourceDiffInformation.delete(repository);
+ }
+
private async _getBlameInformation(resource: Uri): Promise {
const repository = this._model.getRepository(resource);
if (!repository || !repository.HEAD?.commit) {
@@ -217,25 +183,66 @@ export class GitBlameController {
const repositoryBlameInformation = this._repositoryBlameInformation.get(repository) ?? {
commit: repository.HEAD.commit,
- blameInformation: new Map()
+ blameInformation: new Map()
} satisfies RepositoryBlameInformation;
let resourceBlameInformation = repositoryBlameInformation.blameInformation.get(resource);
if (repositoryBlameInformation.commit === repository.HEAD.commit && resourceBlameInformation) {
- return resourceBlameInformation.blameInformation;
+ return resourceBlameInformation;
}
- const staged = repository.indexGroup.resourceStates
- .some(r => pathEquals(resource.fsPath, r.resourceUri.fsPath));
- const blameInformation = await repository.blame2(resource.fsPath) ?? [];
- resourceBlameInformation = { staged, blameInformation } satisfies ResourceBlameInformation;
+ // Get blame information for the resource
+ resourceBlameInformation = await repository.blame2(resource.fsPath, repository.HEAD.commit) ?? [];
this._repositoryBlameInformation.set(repository, {
...repositoryBlameInformation,
blameInformation: repositoryBlameInformation.blameInformation.set(resource, resourceBlameInformation)
});
- return resourceBlameInformation.blameInformation;
+ return resourceBlameInformation;
+ }
+
+ private async _getStagedResourceDiffInformation(uri: Uri): Promise {
+ const repository = this._model.getRepository(uri);
+ if (!repository) {
+ return undefined;
+ }
+
+ const [resource] = repository.indexGroup
+ .resourceStates.filter(r => pathEquals(uri.fsPath, r.resourceUri.fsPath));
+
+ if (!resource || !resource.leftUri || !resource.rightUri) {
+ return undefined;
+ }
+
+ const diffInformationMap = this._stagedResourceDiffInformation.get(repository) ?? new Map();
+ let changes = diffInformationMap.get(resource.resourceUri);
+ if (changes) {
+ return changes;
+ }
+
+ // Get the diff information for the staged resource
+ const diffInformation: LineChange[] = await commands.executeCommand('_workbench.internal.computeDirtyDiff', resource.leftUri, resource.rightUri);
+ if (!diffInformation) {
+ return undefined;
+ }
+
+ changes = diffInformation.map(change => {
+ const kind = change.originalEndLineNumber === 0 ? TextEditorChangeKind.Addition :
+ change.modifiedEndLineNumber === 0 ? TextEditorChangeKind.Deletion : TextEditorChangeKind.Modification;
+
+ return {
+ originalStartLineNumber: change.originalStartLineNumber,
+ originalEndLineNumber: change.originalEndLineNumber,
+ modifiedStartLineNumber: change.modifiedStartLineNumber,
+ modifiedEndLineNumber: change.modifiedEndLineNumber,
+ kind
+ } satisfies TextEditorChange;
+ });
+
+ this._stagedResourceDiffInformation.set(repository, diffInformationMap.set(resource.resourceUri, changes));
+
+ return changes;
}
@throttle
@@ -250,13 +257,9 @@ export class GitBlameController {
return;
}
- // Remove the diff information that is contained in the git blame information.
- // This is done since git blame information is the source of truth and we don't
- // need the diff information for those ranges. The complete diff information is
- // still used to determine whether a line is changed or not.
- const diffInformationWithBlame = processTextEditorChangesWithBlameInformation(
- resourceBlameInformation,
- diffInformation.changes);
+ // The diff information does not contain changes that have been staged. We need
+ // to get the staged changes and if present, merge them with the diff information.
+ const diffInformationStagedResources: TextEditorChange[] = await this._getStagedResourceDiffInformation(textEditor.document.uri) ?? [];
const lineBlameInformation: LineBlameInformation[] = [];
for (const lineNumber of textEditor.selections.map(s => s.active.line)) {
@@ -266,8 +269,16 @@ export class GitBlameController {
continue;
}
- // Map the line number to the git blame ranges
- const lineNumberWithDiff = mapLineNumber(lineNumber + 1, diffInformationWithBlame);
+ // Check if the line is contained in the staged resources diff information
+ if (isLineChanged(lineNumber + 1, diffInformationStagedResources)) {
+ lineBlameInformation.push({ lineNumber, blameInformation: l10n.t('Not Committed Yet (Staged)') });
+ continue;
+ }
+
+ const diffInformationAll = [...diffInformation.changes, ...diffInformationStagedResources];
+
+ // Map the line number to the git blame ranges using the diff information
+ const lineNumberWithDiff = mapLineNumber(lineNumber + 1, diffInformationAll);
const blameInformation = resourceBlameInformation.find(blameInformation => {
return blameInformation.ranges.find(range => {
return lineNumberWithDiff >= range.startLineNumber && lineNumberWithDiff <= range.endLineNumber;
@@ -275,11 +286,7 @@ export class GitBlameController {
});
if (blameInformation) {
- if (blameInformation.id !== notCommittedYetId) {
- lineBlameInformation.push({ lineNumber, blameInformation });
- } else {
- lineBlameInformation.push({ lineNumber, blameInformation: l10n.t('Not Committed Yet (Staged)') });
- }
+ lineBlameInformation.push({ lineNumber, blameInformation });
}
}
diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts
index e81093703c513..155a52128b0c8 100644
--- a/extensions/git/src/git.ts
+++ b/extensions/git/src/git.ts
@@ -2207,9 +2207,16 @@ export class Repository {
}
}
- async blame2(path: string): Promise {
+ async blame2(path: string, ref?: string): Promise {
try {
- const args = ['blame', '--root', '--incremental', '--', sanitizePath(path)];
+ const args = ['blame', '--root', '--incremental'];
+
+ if (ref) {
+ args.push(ref);
+ }
+
+ args.push('--', sanitizePath(path));
+
const result = await this.exec(args);
return parseGitBlame(result.stdout.trim());
diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts
index 167130266ccdc..89f3221b6fb4b 100644
--- a/extensions/git/src/repository.ts
+++ b/extensions/git/src/repository.ts
@@ -1786,8 +1786,8 @@ export class Repository implements Disposable {
return await this.run(Operation.Blame(true), () => this.repository.blame(path));
}
- async blame2(path: string): Promise {
- return await this.run(Operation.Blame(false), () => this.repository.blame2(path));
+ async blame2(path: string, ref?: string): Promise {
+ return await this.run(Operation.Blame(false), () => this.repository.blame2(path, ref));
}
@throttle
diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts
index 1cecdaab32f09..0ab2d529df23d 100644
--- a/src/vs/workbench/browser/parts/editor/editorCommands.ts
+++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts
@@ -40,6 +40,8 @@ import { IPathService } from '../../../services/path/common/pathService.js';
import { IUntitledTextEditorService } from '../../../services/untitled/common/untitledTextEditorService.js';
import { DIFF_FOCUS_OTHER_SIDE, DIFF_FOCUS_PRIMARY_SIDE, DIFF_FOCUS_SECONDARY_SIDE, DIFF_OPEN_SIDE, registerDiffEditorCommands } from './diffEditorCommands.js';
import { IResolvedEditorCommandsContext, resolveCommandsContext } from './editorCommandsContext.js';
+import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js';
+import { ITextModelService } from '../../../../editor/common/services/resolverService.js';
export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors';
export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup';
@@ -536,6 +538,35 @@ function registerOpenEditorAPICommands(): void {
label: options.title,
});
});
+
+ CommandsRegistry.registerCommand('_workbench.internal.computeDirtyDiff', async (accessor: ServicesAccessor, original: UriComponents, modified: UriComponents) => {
+ const configurationService = accessor.get(IConfigurationService);
+ const editorWorkerService = accessor.get(IEditorWorkerService);
+ const textModelService = accessor.get(ITextModelService);
+
+ const originalResource = URI.revive(original);
+ const modifiedResource = URI.revive(modified);
+
+ const originalModel = await textModelService.createModelReference(originalResource);
+ const modifiedModel = await textModelService.createModelReference(modifiedResource);
+
+ const canComputeDirtyDiff = editorWorkerService.canComputeDirtyDiff(originalResource, modifiedResource);
+ if (!canComputeDirtyDiff) {
+ return undefined;
+ }
+
+ const ignoreTrimWhitespaceSetting = configurationService.getValue<'true' | 'false' | 'inherit'>('scm.diffDecorationsIgnoreTrimWhitespace');
+ const ignoreTrimWhitespace = ignoreTrimWhitespaceSetting === 'inherit'
+ ? configurationService.getValue('diffEditor.ignoreTrimWhitespace')
+ : ignoreTrimWhitespaceSetting !== 'false';
+
+ const changes = await editorWorkerService.computeDirtyDiff(originalResource, modifiedResource, ignoreTrimWhitespace);
+
+ originalModel.dispose();
+ modifiedModel.dispose();
+
+ return changes;
+ });
}
interface OpenMultiFileDiffEditorOptions {
From d94c69f4104ec744c798a1f7092738b8a7b59b6b Mon Sep 17 00:00:00 2001
From: Michael Lively