diff --git a/build/lib/tsb/builder.js b/build/lib/tsb/builder.js index 0da1f5c09e6..71c7a78e9a1 100644 --- a/build/lib/tsb/builder.js +++ b/build/lib/tsb/builder.js @@ -63,7 +63,7 @@ function createTypeScriptBuilder(config, projectFile, cmd) { const _log = config.logFn; const host = new LanguageServiceHost(cmd, projectFile, _log); const outHost = new LanguageServiceHost({ ...cmd, options: { ...cmd.options, sourceRoot: cmd.options.outDir } }, cmd.options.outDir ?? '', _log); - let lastCycleCheckVersion; + const toBeCheckedForCycles = []; const service = typescript_1.default.createLanguageService(host, typescript_1.default.createDocumentRegistry()); const lastBuildVersion = Object.create(null); const lastDtsHash = Object.create(null); @@ -294,6 +294,7 @@ function createTypeScriptBuilder(config, projectFile, cmd) { const jsValue = value.files.find(candidate => candidate.basename.endsWith('.js')); if (jsValue) { outHost.addScriptSnapshot(jsValue.path, new ScriptSnapshot(String(jsValue.contents), new Date())); + toBeCheckedForCycles.push(normalize(jsValue.path)); } }).catch(e => { // can't just skip this or make a result up.. @@ -387,24 +388,21 @@ function createTypeScriptBuilder(config, projectFile, cmd) { workOnNext(); }).then(() => { // check for cyclic dependencies - const thisCycleCheckVersion = outHost.getProjectVersion(); - if (thisCycleCheckVersion === lastCycleCheckVersion) { - return; - } - const oneCycle = outHost.hasCyclicDependency(); - lastCycleCheckVersion = thisCycleCheckVersion; - delete oldErrors[projectFile]; - if (oneCycle) { - const cycleError = { - category: typescript_1.default.DiagnosticCategory.Error, - code: 1, - file: undefined, - start: undefined, - length: undefined, - messageText: `CYCLIC dependency between ${oneCycle}` - }; - onError(cycleError); - newErrors[projectFile] = [cycleError]; + const cycles = outHost.getCyclicDependencies(toBeCheckedForCycles); + toBeCheckedForCycles.length = 0; + for (const [filename, error] of cycles) { + const cyclicDepErrors = []; + if (error) { + cyclicDepErrors.push({ + category: typescript_1.default.DiagnosticCategory.Error, + code: 1, + file: undefined, + start: undefined, + length: undefined, + messageText: `CYCLIC dependency: ${error}` + }); + } + newErrors[filename] = cyclicDepErrors; } }).then(() => { // store the build versions to not rebuilt the next time @@ -588,15 +586,17 @@ class LanguageServiceHost { node.incoming.forEach(entry => target.push(entry.data)); } } - hasCyclicDependency() { + getCyclicDependencies(filenames) { // Ensure dependencies are up to date while (this._dependenciesRecomputeList.length) { this._processFile(this._dependenciesRecomputeList.pop()); } - const cycle = this._dependencies.findCycle(); - return cycle - ? cycle.join(' -> ') - : undefined; + const cycles = this._dependencies.findCycles(filenames.sort((a, b) => a.localeCompare(b))); + const result = new Map(); + for (const [key, value] of cycles) { + result.set(key, value?.join(' -> ')); + } + return result; } _processFile(filename) { if (filename.match(/.*\.d\.ts$/)) { diff --git a/build/lib/tsb/builder.ts b/build/lib/tsb/builder.ts index 1a68131f86d..3ca3a0d6ead 100644 --- a/build/lib/tsb/builder.ts +++ b/build/lib/tsb/builder.ts @@ -44,7 +44,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str const host = new LanguageServiceHost(cmd, projectFile, _log); const outHost = new LanguageServiceHost({ ...cmd, options: { ...cmd.options, sourceRoot: cmd.options.outDir } }, cmd.options.outDir ?? '', _log); - let lastCycleCheckVersion: string; + const toBeCheckedForCycles: string[] = []; const service = ts.createLanguageService(host, ts.createDocumentRegistry()); const lastBuildVersion: { [path: string]: string } = Object.create(null); @@ -315,6 +315,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str const jsValue = value.files.find(candidate => candidate.basename.endsWith('.js')); if (jsValue) { outHost.addScriptSnapshot(jsValue.path, new ScriptSnapshot(String(jsValue.contents), new Date())); + toBeCheckedForCycles.push(normalize(jsValue.path)); } }).catch(e => { @@ -424,25 +425,22 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str }).then(() => { // check for cyclic dependencies - const thisCycleCheckVersion = outHost.getProjectVersion(); - if (thisCycleCheckVersion === lastCycleCheckVersion) { - return; - } - const oneCycle = outHost.hasCyclicDependency(); - lastCycleCheckVersion = thisCycleCheckVersion; - delete oldErrors[projectFile]; + const cycles = outHost.getCyclicDependencies(toBeCheckedForCycles); + toBeCheckedForCycles.length = 0; - if (oneCycle) { - const cycleError: ts.Diagnostic = { - category: ts.DiagnosticCategory.Error, - code: 1, - file: undefined, - start: undefined, - length: undefined, - messageText: `CYCLIC dependency between ${oneCycle}` - }; - onError(cycleError); - newErrors[projectFile] = [cycleError]; + for (const [filename, error] of cycles) { + const cyclicDepErrors: ts.Diagnostic[] = []; + if (error) { + cyclicDepErrors.push({ + category: ts.DiagnosticCategory.Error, + code: 1, + file: undefined, + start: undefined, + length: undefined, + messageText: `CYCLIC dependency: ${error}` + }); + } + newErrors[filename] = cyclicDepErrors; } }).then(() => { @@ -666,15 +664,17 @@ class LanguageServiceHost implements ts.LanguageServiceHost { } } - hasCyclicDependency(): string | undefined { + getCyclicDependencies(filenames: string[]): Map { // Ensure dependencies are up to date while (this._dependenciesRecomputeList.length) { this._processFile(this._dependenciesRecomputeList.pop()!); } - const cycle = this._dependencies.findCycle(); - return cycle - ? cycle.join(' -> ') - : undefined; + const cycles = this._dependencies.findCycles(filenames.sort((a, b) => a.localeCompare(b))); + const result = new Map(); + for (const [key, value] of cycles) { + result.set(key, value?.join(' -> ')); + } + return result; } _processFile(filename: string): void { diff --git a/build/lib/tsb/utils.js b/build/lib/tsb/utils.js index 29cccf0f833..2ea820c6e6b 100644 --- a/build/lib/tsb/utils.js +++ b/build/lib/tsb/utils.js @@ -55,44 +55,39 @@ var graph; lookup(data) { return this._nodes.get(data) ?? null; } - findCycle() { - let result; - let foundStartNodes = false; + findCycles(allData) { + const result = new Map(); const checked = new Set(); - for (const [_start, value] of this._nodes) { - if (Object.values(value.incoming).length > 0) { + for (const data of allData) { + const node = this.lookup(data); + if (!node) { continue; } - foundStartNodes = true; - const dfs = (node, visited) => { - if (checked.has(node)) { - return; - } - if (visited.has(node)) { - result = [...visited, node].map(n => n.data); - const idx = result.indexOf(node.data); - result = result.slice(idx); - return; - } - visited.add(node); - for (const child of Object.values(node.outgoing)) { - dfs(child, visited); - if (result) { - break; - } - } - visited.delete(node); - checked.add(node); - }; - dfs(value, new Set()); + const r = this._findCycle(node, checked, new Set()); + result.set(node.data, r); + } + return result; + } + _findCycle(node, checked, seen) { + if (checked.has(node.data)) { + return undefined; + } + let result; + for (const child of node.outgoing.values()) { + if (seen.has(child.data)) { + const seenArr = Array.from(seen); + const idx = seenArr.indexOf(child.data); + seenArr.push(child.data); + return idx > 0 ? seenArr.slice(idx) : seenArr; + } + seen.add(child.data); + result = this._findCycle(child, checked, seen); + seen.delete(child.data); if (result) { break; } } - if (!foundStartNodes) { - // everything is a cycle - return Array.from(this._nodes.keys()); - } + checked.add(node.data); return result; } } diff --git a/build/lib/tsb/utils.ts b/build/lib/tsb/utils.ts index 59d1cab36d3..16f93d6838f 100644 --- a/build/lib/tsb/utils.ts +++ b/build/lib/tsb/utils.ts @@ -63,53 +63,42 @@ export namespace graph { return this._nodes.get(data) ?? null; } - findCycle(): T[] | undefined { - - let result: T[] | undefined; - let foundStartNodes = false; - const checked = new Set>(); - - for (const [_start, value] of this._nodes) { - - if (Object.values(value.incoming).length > 0) { + findCycles(allData: T[]): Map { + const result = new Map(); + const checked = new Set(); + for (const data of allData) { + const node = this.lookup(data); + if (!node) { continue; } + const r = this._findCycle(node, checked, new Set()); + result.set(node.data, r); + } + return result; + } - foundStartNodes = true; + private _findCycle(node: Node, checked: Set, seen: Set): T[] | undefined { - const dfs = (node: Node, visited: Set>) => { + if (checked.has(node.data)) { + return undefined; + } - if (checked.has(node)) { - return; - } - - if (visited.has(node)) { - result = [...visited, node].map(n => n.data); - const idx = result.indexOf(node.data); - result = result.slice(idx); - return; - } - visited.add(node); - for (const child of Object.values(node.outgoing)) { - dfs(child, visited); - if (result) { - break; - } - } - visited.delete(node); - checked.add(node); - }; - dfs(value, new Set()); + let result: T[] | undefined; + for (const child of node.outgoing.values()) { + if (seen.has(child.data)) { + const seenArr = Array.from(seen); + const idx = seenArr.indexOf(child.data); + seenArr.push(child.data); + return idx > 0 ? seenArr.slice(idx) : seenArr; + } + seen.add(child.data); + result = this._findCycle(child, checked, seen); + seen.delete(child.data); if (result) { break; } } - - if (!foundStartNodes) { - // everything is a cycle - return Array.from(this._nodes.keys()); - } - + checked.add(node.data); return result; } } diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 553c584a63d..97f35ccc34f 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -8,7 +8,7 @@ import { BrowserFeatures } from './canIUse.js'; import { IKeyboardEvent, StandardKeyboardEvent } from './keyboardEvent.js'; import { IMouseEvent, StandardMouseEvent } from './mouseEvent.js'; import { AbstractIdleValue, IntervalTimer, TimeoutTimer, _runWhenIdle, IdleDeadline } from '../common/async.js'; -import { onUnexpectedError } from '../common/errors.js'; +import { BugIndicatingError, onUnexpectedError } from '../common/errors.js'; import * as event from '../common/event.js'; import dompurify from './dompurify/dompurify.js'; import { KeyCode } from '../common/keyCodes.js'; @@ -19,8 +19,7 @@ import { URI } from '../common/uri.js'; import { hash } from '../common/hash.js'; import { CodeWindow, ensureCodeWindow, mainWindow } from './window.js'; import { isPointWithinTriangle } from '../common/numbers.js'; -export * from './domImpl/domObservable.js'; -export * from './domImpl/n.js'; +import { IObservable, derived, derivedOpts, IReader, observableValue } from '../common/observable.js'; export interface IRegisteredCodeWindow { readonly window: CodeWindow; @@ -2395,3 +2394,353 @@ export class SafeTriangle { return false; } } + + +export namespace n { + function nodeNs>(elementNs: string | undefined = undefined): DomTagCreateFn { + return (tag, attributes, children) => { + const className = attributes.class; + delete attributes.class; + const ref = attributes.ref; + delete attributes.ref; + const obsRef = attributes.obsRef; + delete attributes.obsRef; + + return new ObserverNodeWithElement(tag as any, ref, obsRef, elementNs, className, attributes, children); + }; + } + + function node, TKey extends keyof TMap>(tag: TKey, elementNs: string | undefined = undefined): DomCreateFn { + const f = nodeNs(elementNs) as any; + return (attributes, children) => { + return f(tag, attributes, children); + }; + } + + export const div: DomCreateFn = node('div'); + + export const elem = nodeNs(undefined); + + export const svg: DomCreateFn = node('svg', 'http://www.w3.org/2000/svg'); + + export const svgElem = nodeNs('http://www.w3.org/2000/svg'); + + export function ref(): IRefWithVal { + let value: T | undefined = undefined; + const result: IRef = function (val: T) { + value = val; + }; + Object.defineProperty(result, 'element', { + get() { + if (!value) { + throw new BugIndicatingError('Make sure the ref is set before accessing the element. Maybe wrong initialization order?'); + } + return value; + } + }); + return result as any; + } +} +type Value = T | IObservable; +type ValueOrList = Value | ValueOrList[]; +type ValueOrList2 = ValueOrList | ValueOrList>; +type HTMLOrSVGElement = HTMLElement | SVGElement; +type SVGElementTagNameMap2 = { + svg: SVGElement & { + width: number; + height: number; + transform: string; + viewBox: string; + fill: string; + }; + path: SVGElement & { + d: string; + stroke: string; + fill: string; + }; + linearGradient: SVGElement & { + id: string; + x1: string | number; + x2: string | number; + }; + stop: SVGElement & { + offset: string; + }; + rect: SVGElement & { + x: number; + y: number; + width: number; + height: number; + fill: string; + }; + defs: SVGElement; +}; +type DomTagCreateFn> = ( + tag: TTag, + attributes: ElementAttributeKeys & { class?: ValueOrList; ref?: IRef; obsRef?: IRef | null> }, + children?: ChildNode +) => ObserverNode; +type DomCreateFn = ( + attributes: ElementAttributeKeys & { class?: ValueOrList; ref?: IRef; obsRef?: IRef | null> }, + children?: ChildNode +) => ObserverNode; + +export type ChildNode = ValueOrList2; + +export type IRef = (value: T) => void; + +export interface IRefWithVal extends IRef { + readonly element: T; +} + +export abstract class ObserverNode { + private readonly _deriveds: (IObservable)[] = []; + + protected readonly _element: T; + + constructor( + tag: string, + ref: IRef | undefined, + obsRef: IRef | null> | undefined, + ns: string | undefined, + className: ValueOrList | undefined, + attributes: ElementAttributeKeys, + children: ChildNode + ) { + this._element = (ns ? document.createElementNS(ns, tag) : document.createElement(tag)) as unknown as T; + if (ref) { + ref(this._element); + } + if (obsRef) { + this._deriveds.push(derived((_reader) => { + obsRef(this as unknown as ObserverNodeWithElement); + _reader.store.add({ + dispose: () => { + obsRef(null); + } + }); + })); + } + + if (className) { + if (hasObservable(className)) { + this._deriveds.push(derived(this, reader => { + /** @description set.class */ + setClassName(this._element, getClassName(className, reader)); + })); + } else { + setClassName(this._element, getClassName(className, undefined)); + } + } + + for (const [key, value] of Object.entries(attributes)) { + if (key === 'style') { + for (const [cssKey, cssValue] of Object.entries(value)) { + const key = camelCaseToHyphenCase(cssKey); + if (isObservable(cssValue)) { + this._deriveds.push(derivedOpts({ owner: this, debugName: () => `set.style.${key}` }, reader => { + this._element.style.setProperty(key, convertCssValue(cssValue.read(reader))); + })); + } else { + this._element.style.setProperty(key, convertCssValue(cssValue)); + } + } + } else if (key === 'tabIndex') { + if (isObservable(value)) { + this._deriveds.push(derived(this, reader => { + /** @description set.tabIndex */ + this._element.tabIndex = value.read(reader) as any; + })); + } else { + this._element.tabIndex = value; + } + } else if (key.startsWith('on')) { + (this._element as any)[key] = value; + } else { + if (isObservable(value)) { + this._deriveds.push(derivedOpts({ owner: this, debugName: () => `set.${key}` }, reader => { + setOrRemoveAttribute(this._element, key, value.read(reader)); + })); + } else { + setOrRemoveAttribute(this._element, key, value); + } + } + } + + if (children) { + function getChildren(reader: IReader | undefined, children: ValueOrList2): (HTMLOrSVGElement | string)[] { + if (isObservable(children)) { + return getChildren(reader, children.read(reader)); + } + if (Array.isArray(children)) { + return children.flatMap(c => getChildren(reader, c)); + } + if (children instanceof ObserverNode) { + if (reader) { + children.readEffect(reader); + } + return [children._element]; + } + if (children) { + return [children]; + } + return []; + } + + const d = derived(this, reader => { + /** @description set.children */ + this._element.replaceChildren(...getChildren(reader, children)); + }); + this._deriveds.push(d); + if (!childrenIsObservable(children)) { + d.get(); + } + } + } + + readEffect(reader: IReader | undefined): void { + for (const d of this._deriveds) { + d.read(reader); + } + } + + keepUpdated(store: DisposableStore): ObserverNodeWithElement { + derived(reader => { + /** update */ + this.readEffect(reader); + }).recomputeInitiallyAndOnChange(store); + return this as unknown as ObserverNodeWithElement; + } + + /** + * Creates a live element that will keep the element updated as long as the returned object is not disposed. + */ + toDisposableLiveElement() { + const store = new DisposableStore(); + this.keepUpdated(store); + return new LiveElement(this._element, store); + } +} +function setClassName(domNode: HTMLOrSVGElement, className: string) { + if (isSVGElement(domNode)) { + domNode.setAttribute('class', className); + } else { + domNode.className = className; + } +} +function resolve(value: ValueOrList, reader: IReader | undefined, cb: (val: T) => void): void { + if (isObservable(value)) { + cb(value.read(reader)); + return; + } + if (Array.isArray(value)) { + for (const v of value) { + resolve(v, reader, cb); + } + return; + } + cb(value as any); +} +function getClassName(className: ValueOrList | undefined, reader: IReader | undefined): string { + let result = ''; + resolve(className, reader, val => { + if (val) { + if (result.length === 0) { + result = val; + } else { + result += ' ' + val; + } + } + }); + return result; +} +function hasObservable(value: ValueOrList): boolean { + if (isObservable(value)) { + return true; + } + if (Array.isArray(value)) { + return value.some(v => hasObservable(v)); + } + return false; +} +function convertCssValue(value: any): string { + if (typeof value === 'number') { + return value + 'px'; + } + return value; +} +function childrenIsObservable(children: ValueOrList2): boolean { + if (isObservable(children)) { + return true; + } + if (Array.isArray(children)) { + return children.some(c => childrenIsObservable(c)); + } + return false; +} + +export class LiveElement { + constructor( + public readonly element: T, + private readonly _disposable: IDisposable + ) { } + + dispose() { + this._disposable.dispose(); + } +} + +export class ObserverNodeWithElement extends ObserverNode { + public get element() { + return this._element; + } + + private _isHovered: IObservable | undefined = undefined; + + get isHovered(): IObservable { + if (!this._isHovered) { + const hovered = observableValue('hovered', false); + this._element.addEventListener('mouseenter', (_e) => hovered.set(true, undefined)); + this._element.addEventListener('mouseleave', (_e) => hovered.set(false, undefined)); + this._isHovered = hovered; + } + return this._isHovered; + } + + private _didMouseMoveDuringHover: IObservable | undefined = undefined; + + get didMouseMoveDuringHover(): IObservable { + if (!this._didMouseMoveDuringHover) { + let _hovering = false; + const hovered = observableValue('didMouseMoveDuringHover', false); + this._element.addEventListener('mouseenter', (_e) => { + _hovering = true; + }); + this._element.addEventListener('mousemove', (_e) => { + if (_hovering) { + hovered.set(true, undefined); + } + }); + this._element.addEventListener('mouseleave', (_e) => { + _hovering = false; + hovered.set(false, undefined); + }); + this._didMouseMoveDuringHover = hovered; + } + return this._didMouseMoveDuringHover; + } +} +function setOrRemoveAttribute(element: HTMLOrSVGElement, key: string, value: unknown) { + if (value === null || value === undefined) { + element.removeAttribute(camelCaseToHyphenCase(key)); + } else { + element.setAttribute(camelCaseToHyphenCase(key), String(value)); + } +} + +function isObservable(obj: unknown): obj is IObservable { + return !!obj && (>obj).read !== undefined && (>obj).reportChanges !== undefined; +} +type ElementAttributeKeys = Partial<{ + [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? ElementAttributeKeys : Value; +}>; diff --git a/src/vs/base/browser/domImpl/domObservable.ts b/src/vs/base/browser/domImpl/domObservable.ts deleted file mode 100644 index c66a031de67..00000000000 --- a/src/vs/base/browser/domImpl/domObservable.ts +++ /dev/null @@ -1,17 +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 { DisposableStore, IDisposable } from '../../common/lifecycle.js'; -import { autorun, IObservable } from '../../common/observable.js'; -import { createStyleSheet2 } from '../domStylesheets.js'; - -export function createStyleSheetFromObservable(css: IObservable): IDisposable { - const store = new DisposableStore(); - const w = store.add(createStyleSheet2()); - store.add(autorun(reader => { - w.setStyle(css.read(reader)); - })); - return store; -} diff --git a/src/vs/base/browser/domImpl/n.ts b/src/vs/base/browser/domImpl/n.ts deleted file mode 100644 index a3c7653a51c..00000000000 --- a/src/vs/base/browser/domImpl/n.ts +++ /dev/null @@ -1,373 +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 { BugIndicatingError } from '../../common/errors.js'; -import { DisposableStore, IDisposable } from '../../common/lifecycle.js'; -import { derived, derivedOpts, IObservable, IReader, observableValue } from '../../common/observable.js'; -import { isSVGElement } from '../dom.js'; - -export namespace n { - function nodeNs>(elementNs: string | undefined = undefined): DomTagCreateFn { - return (tag, attributes, children) => { - const className = attributes.class; - delete attributes.class; - const ref = attributes.ref; - delete attributes.ref; - const obsRef = attributes.obsRef; - delete attributes.obsRef; - - return new ObserverNodeWithElement(tag as any, ref, obsRef, elementNs, className, attributes, children); - }; - } - - function node, TKey extends keyof TMap>(tag: TKey, elementNs: string | undefined = undefined): DomCreateFn { - const f = nodeNs(elementNs) as any; - return (attributes, children) => { - return f(tag, attributes, children); - }; - } - - export const div: DomCreateFn = node('div'); - - export const elem = nodeNs(undefined); - - export const svg: DomCreateFn = node('svg', 'http://www.w3.org/2000/svg'); - - export const svgElem = nodeNs('http://www.w3.org/2000/svg'); - - export function ref(): IRefWithVal { - let value: T | undefined = undefined; - const result: IRef = function (val: T) { - value = val; - }; - Object.defineProperty(result, 'element', { - get() { - if (!value) { - throw new BugIndicatingError('Make sure the ref is set before accessing the element. Maybe wrong initialization order?'); - } - return value; - } - }); - return result as any; - } -} - -type Value = T | IObservable; -type ValueOrList = Value | ValueOrList[]; -type ValueOrList2 = ValueOrList | ValueOrList>; -type Element = HTMLElement | SVGElement; -type SVGElementTagNameMap2 = { - svg: SVGElement & { - width: number; - height: number; - transform: string; - viewBox: string; - fill: string; - }; - path: SVGElement & { - d: string; - stroke: string; - fill: string; - }; - linearGradient: SVGElement & { - id: string; - x1: string | number; - x2: string | number; - }; - stop: SVGElement & { - offset: string; - }; - rect: SVGElement & { - x: number; - y: number; - width: number; - height: number; - fill: string; - }; - defs: SVGElement; -}; - -type DomTagCreateFn> = ( - tag: TTag, - attributes: ElementAttributeKeys & { class?: ValueOrList; ref?: IRef; obsRef?: IRef | null> }, - children?: ChildNode -) => ObserverNode; - -type DomCreateFn = ( - attributes: ElementAttributeKeys & { class?: ValueOrList; ref?: IRef; obsRef?: IRef | null> }, - children?: ChildNode -) => ObserverNode; - -export type ChildNode = ValueOrList2; - -export type IRef = (value: T) => void; - -export interface IRefWithVal extends IRef { - readonly element: T; -} - -export abstract class ObserverNode { - private readonly _deriveds: (IObservable)[] = []; - - protected readonly _element: T; - - constructor( - tag: string, - ref: IRef | undefined, - obsRef: IRef | null> | undefined, - ns: string | undefined, - className: ValueOrList | undefined, - attributes: ElementAttributeKeys, - children: ChildNode - ) { - this._element = (ns ? document.createElementNS(ns, tag) : document.createElement(tag)) as unknown as T; - if (ref) { - ref(this._element); - } - if (obsRef) { - this._deriveds.push(derived((_reader) => { - obsRef(this as unknown as ObserverNodeWithElement); - _reader.store.add({ - dispose: () => { - obsRef(null); - } - }); - })); - } - - if (className) { - if (hasObservable(className)) { - this._deriveds.push(derived(this, reader => { - /** @description set.class */ - setClassName(this._element, getClassName(className, reader)); - })); - } else { - setClassName(this._element, getClassName(className, undefined)); - } - } - - for (const [key, value] of Object.entries(attributes)) { - if (key === 'style') { - for (const [cssKey, cssValue] of Object.entries(value)) { - const key = camelCaseToHyphenCase(cssKey); - if (isObservable(cssValue)) { - this._deriveds.push(derivedOpts({ owner: this, debugName: () => `set.style.${key}` }, reader => { - this._element.style.setProperty(key, convertCssValue(cssValue.read(reader))); - })); - } else { - this._element.style.setProperty(key, convertCssValue(cssValue)); - } - } - } else if (key === 'tabIndex') { - if (isObservable(value)) { - this._deriveds.push(derived(this, reader => { - /** @description set.tabIndex */ - this._element.tabIndex = value.read(reader) as any; - })); - } else { - this._element.tabIndex = value; - } - } else if (key.startsWith('on')) { - (this._element as any)[key] = value; - } else { - if (isObservable(value)) { - this._deriveds.push(derivedOpts({ owner: this, debugName: () => `set.${key}` }, reader => { - setOrRemoveAttribute(this._element, key, value.read(reader)); - })); - } else { - setOrRemoveAttribute(this._element, key, value); - } - } - } - - if (children) { - function getChildren(reader: IReader | undefined, children: ValueOrList2): (Element | string)[] { - if (isObservable(children)) { - return getChildren(reader, children.read(reader)); - } - if (Array.isArray(children)) { - return children.flatMap(c => getChildren(reader, c)); - } - if (children instanceof ObserverNode) { - if (reader) { - children.readEffect(reader); - } - return [children._element]; - } - if (children) { - return [children]; - } - return []; - } - - const d = derived(this, reader => { - /** @description set.children */ - this._element.replaceChildren(...getChildren(reader, children)); - }); - this._deriveds.push(d); - if (!childrenIsObservable(children)) { - d.get(); - } - } - } - - readEffect(reader: IReader | undefined): void { - for (const d of this._deriveds) { - d.read(reader); - } - } - - keepUpdated(store: DisposableStore): ObserverNodeWithElement { - derived(reader => { - /** update */ - this.readEffect(reader); - }).recomputeInitiallyAndOnChange(store); - return this as unknown as ObserverNodeWithElement; - } - - /** - * Creates a live element that will keep the element updated as long as the returned object is not disposed. - */ - toDisposableLiveElement() { - const store = new DisposableStore(); - this.keepUpdated(store); - return new LiveElement(this._element, store); - } -} - -function setClassName(domNode: Element, className: string) { - if (isSVGElement(domNode)) { - domNode.setAttribute('class', className); - } else { - domNode.className = className; - } -} - -function resolve(value: ValueOrList, reader: IReader | undefined, cb: (val: T) => void): void { - if (isObservable(value)) { - cb(value.read(reader)); - return; - } - if (Array.isArray(value)) { - for (const v of value) { - resolve(v, reader, cb); - } - return; - } - cb(value as any); -} - -function getClassName(className: ValueOrList | undefined, reader: IReader | undefined): string { - let result = ''; - resolve(className, reader, val => { - if (val) { - if (result.length === 0) { - result = val; - } else { - result += ' ' + val; - } - } - }); - return result; -} - -function hasObservable(value: ValueOrList): boolean { - if (isObservable(value)) { - return true; - } - if (Array.isArray(value)) { - return value.some(v => hasObservable(v)); - } - return false; -} - -function convertCssValue(value: any): string { - if (typeof value === 'number') { - return value + 'px'; - } - return value; -} - -function childrenIsObservable(children: ValueOrList2): boolean { - if (isObservable(children)) { - return true; - } - if (Array.isArray(children)) { - return children.some(c => childrenIsObservable(c)); - } - return false; -} - -export class LiveElement { - constructor( - public readonly element: T, - private readonly _disposable: IDisposable - ) { } - - dispose() { - this._disposable.dispose(); - } -} - -export class ObserverNodeWithElement extends ObserverNode { - public get element() { - return this._element; - } - - private _isHovered: IObservable | undefined = undefined; - - get isHovered(): IObservable { - if (!this._isHovered) { - const hovered = observableValue('hovered', false); - this._element.addEventListener('mouseenter', (_e) => hovered.set(true, undefined)); - this._element.addEventListener('mouseleave', (_e) => hovered.set(false, undefined)); - this._isHovered = hovered; - } - return this._isHovered; - } - - private _didMouseMoveDuringHover: IObservable | undefined = undefined; - - get didMouseMoveDuringHover(): IObservable { - if (!this._didMouseMoveDuringHover) { - let _hovering = false; - const hovered = observableValue('didMouseMoveDuringHover', false); - this._element.addEventListener('mouseenter', (_e) => { - _hovering = true; - }); - this._element.addEventListener('mousemove', (_e) => { - if (_hovering) { - hovered.set(true, undefined); - } - }); - this._element.addEventListener('mouseleave', (_e) => { - _hovering = false; - hovered.set(false, undefined); - }); - this._didMouseMoveDuringHover = hovered; - } - return this._didMouseMoveDuringHover; - } -} - -function setOrRemoveAttribute(element: Element, key: string, value: unknown) { - if (value === null || value === undefined) { - element.removeAttribute(camelCaseToHyphenCase(key)); - } else { - element.setAttribute(camelCaseToHyphenCase(key), String(value)); - } -} - -function camelCaseToHyphenCase(str: string) { - return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); -} - -function isObservable(obj: unknown): obj is IObservable { - return !!obj && (>obj).read !== undefined && (>obj).reportChanges !== undefined; -} - -type ElementAttributeKeys = Partial<{ - [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? ElementAttributeKeys : Value; -}>; diff --git a/src/vs/base/browser/domStylesheets.ts b/src/vs/base/browser/domStylesheets.ts index ebc249c6608..76a71c5402d 100644 --- a/src/vs/base/browser/domStylesheets.ts +++ b/src/vs/base/browser/domStylesheets.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore, toDisposable, IDisposable } from '../common/lifecycle.js'; +import { autorun, IObservable } from '../common/observable.js'; import { getWindows, sharedMutationObserver } from './dom.js'; import { mainWindow } from './window.js'; @@ -166,3 +167,12 @@ export function removeCSSRulesContainingSelector(ruleName: string, style = getSh function isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule { return typeof (rule as CSSStyleRule).selectorText === 'string'; } + +export function createStyleSheetFromObservable(css: IObservable): IDisposable { + const store = new DisposableStore(); + const w = store.add(createStyleSheet2()); + store.add(autorun(reader => { + w.setStyle(css.read(reader)); + })); + return store; +} diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 193f687da24..f640014499e 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -84,6 +84,9 @@ export namespace Schemas { /** Scheme used for the chat input editor. */ export const vscodeChatSesssion = 'vscode-chat-editor'; + /** Scheme used for the chat input part */ + export const vscodeChatInput = 'chatSessionInput'; + /** * Scheme used internally for webviews that aren't linked to a resource (i.e. not custom editors) */ diff --git a/src/vs/base/common/observableInternal/map.ts b/src/vs/base/common/observableInternal/map.ts index 9959bd1a1e5..1db8c9ebb26 100644 --- a/src/vs/base/common/observableInternal/map.ts +++ b/src/vs/base/common/observableInternal/map.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { observableValueOpts, IObservable, ITransaction } from '../observable.js'; +import { IObservable, ITransaction } from '../observable.js'; +import { observableValueOpts } from './observables/observableValueOpts.js'; + export class ObservableMap implements Map { private readonly _data = new Map(); diff --git a/src/vs/base/common/observableInternal/set.ts b/src/vs/base/common/observableInternal/set.ts index 10a624dcbaa..294cdc73ca4 100644 --- a/src/vs/base/common/observableInternal/set.ts +++ b/src/vs/base/common/observableInternal/set.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { observableValueOpts, IObservable, ITransaction } from '../observable.js'; - +import { IObservable, ITransaction } from '../observable.js'; +import { observableValueOpts } from './observables/observableValueOpts.js'; export class ObservableSet implements Set { diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index d5e39554d43..ce0cce592e4 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -58,7 +58,7 @@ import { ISignService } from '../../platform/sign/common/sign.js'; import { SignService } from '../../platform/sign/node/signService.js'; import { IStateReadService, IStateService } from '../../platform/state/node/state.js'; import { NullTelemetryService } from '../../platform/telemetry/common/telemetryUtils.js'; -import { IThemeMainService, ThemeMainService } from '../../platform/theme/electron-main/themeMainService.js'; +import { IThemeMainService } from '../../platform/theme/electron-main/themeMainService.js'; import { IUserDataProfilesMainService, UserDataProfilesMainService } from '../../platform/userDataProfile/electron-main/userDataProfile.js'; import { IPolicyService, NullPolicyService } from '../../platform/policy/common/policy.js'; import { NativePolicyService } from '../../platform/policy/node/nativePolicyService.js'; @@ -72,6 +72,7 @@ import { massageMessageBoxOptions } from '../../platform/dialogs/common/dialogs. import { SaveStrategy, StateService } from '../../platform/state/node/stateService.js'; import { FileUserDataProvider } from '../../platform/userData/common/fileUserDataProvider.js'; import { addUNCHostToAllowlist, getUNCHost } from '../../base/node/unc.js'; +import { ThemeMainService } from '../../platform/theme/electron-main/themeMainServiceImpl.js'; /** * The main VS Code entry point. diff --git a/src/vs/editor/browser/gpu/atlas/textureAtlas.ts b/src/vs/editor/browser/gpu/atlas/textureAtlas.ts index a4e5865c011..252a44596ef 100644 --- a/src/vs/editor/browser/gpu/atlas/textureAtlas.ts +++ b/src/vs/editor/browser/gpu/atlas/textureAtlas.ts @@ -12,6 +12,7 @@ import { NKeyMap } from '../../../../base/common/map.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { MetadataConsts } from '../../../common/encodedTokenAttributes.js'; +import type { DecorationStyleCache } from '../css/decorationStyleCache.js'; import { GlyphRasterizer } from '../raster/glyphRasterizer.js'; import type { IGlyphRasterizer } from '../raster/raster.js'; import { IdleTaskQueue, type ITaskQueue } from '../taskQueue.js'; @@ -59,6 +60,7 @@ export class TextureAtlas extends Disposable { /** The maximum texture size supported by the GPU. */ private readonly _maxTextureSize: number, options: ITextureAtlasOptions | undefined, + private readonly _decorationStyleCache: DecorationStyleCache, @IThemeService private readonly _themeService: IThemeService, @IInstantiationService private readonly _instantiationService: IInstantiationService ) { @@ -88,7 +90,7 @@ export class TextureAtlas extends Disposable { // IMPORTANT: The first glyph on the first page must be an empty glyph such that zeroed out // cells end up rendering nothing // TODO: This currently means the first slab is for 0x0 glyphs and is wasted - const nullRasterizer = new GlyphRasterizer(1, '', 1); + const nullRasterizer = new GlyphRasterizer(1, '', 1, this._decorationStyleCache); firstPage.getGlyph(nullRasterizer, '', 0, 0); nullRasterizer.dispose(); } diff --git a/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts b/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts index d06871cc24d..cc41c8dcaa4 100644 --- a/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts +++ b/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts @@ -8,8 +8,8 @@ import { Disposable } from '../../../../base/common/lifecycle.js'; import { isMacintosh } from '../../../../base/common/platform.js'; import { StringBuilder } from '../../../common/core/stringBuilder.js'; import { FontStyle, TokenMetadata } from '../../../common/encodedTokenAttributes.js'; +import type { DecorationStyleCache } from '../css/decorationStyleCache.js'; import { ensureNonNullable } from '../gpuUtils.js'; -import { ViewGpuContext } from '../viewGpuContext.js'; import { type IBoundingBox, type IGlyphRasterizer, type IRasterizedGlyph } from './raster.js'; let nextId = 0; @@ -50,7 +50,8 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { constructor( readonly fontSize: number, readonly fontFamily: string, - readonly devicePixelRatio: number + readonly devicePixelRatio: number, + private readonly _decorationStyleCache: DecorationStyleCache, ) { super(); @@ -119,7 +120,7 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { const bgId = TokenMetadata.getBackground(tokenMetadata); const bg = colorMap[bgId]; - const decorationStyleSet = ViewGpuContext.decorationStyleCache.getStyleSet(decorationStyleSetId); + const decorationStyleSet = this._decorationStyleCache.getStyleSet(decorationStyleSetId); // When SPAA is used, the background color must be present to get the right glyph if (this._antiAliasing === 'subpixel') { diff --git a/src/vs/editor/browser/gpu/viewGpuContext.ts b/src/vs/editor/browser/gpu/viewGpuContext.ts index 181c468f12c..953e7c23f7a 100644 --- a/src/vs/editor/browser/gpu/viewGpuContext.ts +++ b/src/vs/editor/browser/gpu/viewGpuContext.ts @@ -24,13 +24,12 @@ import { Event } from '../../../base/common/event.js'; import { EditorOption, type IEditorOptions } from '../../common/config/editorOptions.js'; import { InlineDecorationType } from '../../common/viewModel.js'; import { DecorationStyleCache } from './css/decorationStyleCache.js'; -import { ViewportRenderStrategy } from './renderStrategy/viewportRenderStrategy.js'; export class ViewGpuContext extends Disposable { /** * The hard cap for line columns rendered by the GPU renderer. */ - readonly maxGpuCols = ViewportRenderStrategy.maxSupportedColumns; + readonly maxGpuCols = 2000; readonly canvas: FastDomNode; readonly ctx: GPUCanvasContext; @@ -111,7 +110,7 @@ export class ViewGpuContext extends Disposable { }).then(ref => { ViewGpuContext.deviceSync = ref.object; if (!ViewGpuContext._atlas) { - ViewGpuContext._atlas = this._instantiationService.createInstance(TextureAtlas, ref.object.limits.maxTextureDimension2D, undefined); + ViewGpuContext._atlas = this._instantiationService.createInstance(TextureAtlas, ref.object.limits.maxTextureDimension2D, undefined, ViewGpuContext.decorationStyleCache); } return ref.object; }); diff --git a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts index dd0666b3e3b..dca6206ec1f 100644 --- a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts +++ b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts @@ -196,7 +196,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { const fontFamily = this._context.configuration.options.get(EditorOption.fontFamily); const fontSize = this._context.configuration.options.get(EditorOption.fontSize); - this._glyphRasterizer.value = this._register(new GlyphRasterizer(fontSize, fontFamily, this._viewGpuContext.devicePixelRatio.get())); + this._glyphRasterizer.value = this._register(new GlyphRasterizer(fontSize, fontFamily, this._viewGpuContext.devicePixelRatio.get(), ViewGpuContext.decorationStyleCache)); this._register(runOnChange(this._viewGpuContext.devicePixelRatio, () => { this._refreshGlyphRasterizer(); })); @@ -454,7 +454,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { glyphRasterizer.fontSize !== fontSize || glyphRasterizer.devicePixelRatio !== devicePixelRatio ) { - this._glyphRasterizer.value = new GlyphRasterizer(fontSize, fontFamily, devicePixelRatio); + this._glyphRasterizer.value = new GlyphRasterizer(fontSize, fontFamily, devicePixelRatio, ViewGpuContext.decorationStyleCache); } } diff --git a/src/vs/editor/common/core/text/abstractText.ts b/src/vs/editor/common/core/text/abstractText.ts index 274a05f2e0e..fe1d35213e6 100644 --- a/src/vs/editor/common/core/text/abstractText.ts +++ b/src/vs/editor/common/core/text/abstractText.ts @@ -6,7 +6,7 @@ import { assert } from '../../../../base/common/assert.js'; import { splitLines } from '../../../../base/common/strings.js'; import { Position } from '../position.js'; -import { PositionOffsetTransformer } from './positionToOffset.js'; +import { PositionOffsetTransformer } from './positionToOffsetImpl.js'; import { Range } from '../range.js'; import { LineRange } from '../ranges/lineRange.js'; import { TextLength } from '../text/textLength.js'; diff --git a/src/vs/editor/common/core/text/positionToOffset.ts b/src/vs/editor/common/core/text/positionToOffset.ts index ed0e5f879ba..97d6ee3bf7c 100644 --- a/src/vs/editor/common/core/text/positionToOffset.ts +++ b/src/vs/editor/common/core/text/positionToOffset.ts @@ -3,117 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { findLastIdxMonotonous } from '../../../../base/common/arraysFind.js'; import { StringEdit, StringReplacement } from '../edits/stringEdit.js'; -import { OffsetRange } from '../ranges/offsetRange.js'; -import { Position } from '../position.js'; -import { Range } from '../range.js'; -import { TextReplacement, TextEdit } from '../edits/textEdit.js'; -import { TextLength } from '../text/textLength.js'; +import { TextEdit, TextReplacement } from '../edits/textEdit.js'; +import { _setPositionOffsetTransformerDependencies } from './positionToOffsetImpl.js'; +import { TextLength } from './textLength.js'; -export abstract class PositionOffsetTransformerBase { - abstract getOffset(position: Position): number; +export { PositionOffsetTransformerBase, PositionOffsetTransformer } from './positionToOffsetImpl.js'; - getOffsetRange(range: Range): OffsetRange { - return new OffsetRange( - this.getOffset(range.getStartPosition()), - this.getOffset(range.getEndPosition()) - ); - } - - abstract getPosition(offset: number): Position; - - getRange(offsetRange: OffsetRange): Range { - return Range.fromPositions( - this.getPosition(offsetRange.start), - this.getPosition(offsetRange.endExclusive) - ); - } - - getStringEdit(edit: TextEdit): StringEdit { - const edits = edit.replacements.map(e => this.getStringReplacement(e)); - return new StringEdit(edits); - } - - getStringReplacement(edit: TextReplacement): StringReplacement { - return new StringReplacement(this.getOffsetRange(edit.range), edit.text); - } - - getSingleTextEdit(edit: StringReplacement): TextReplacement { - return new TextReplacement(this.getRange(edit.replaceRange), edit.newText); - } - - getTextEdit(edit: StringEdit): TextEdit { - const edits = edit.replacements.map(e => this.getSingleTextEdit(e)); - return new TextEdit(edits); - } -} - -export class PositionOffsetTransformer extends PositionOffsetTransformerBase { - private readonly lineStartOffsetByLineIdx: number[]; - private readonly lineEndOffsetByLineIdx: number[]; - - constructor(public readonly text: string) { - super(); - - this.lineStartOffsetByLineIdx = []; - this.lineEndOffsetByLineIdx = []; - - this.lineStartOffsetByLineIdx.push(0); - for (let i = 0; i < text.length; i++) { - if (text.charAt(i) === '\n') { - this.lineStartOffsetByLineIdx.push(i + 1); - if (i > 0 && text.charAt(i - 1) === '\r') { - this.lineEndOffsetByLineIdx.push(i - 1); - } else { - this.lineEndOffsetByLineIdx.push(i); - } - } - } - this.lineEndOffsetByLineIdx.push(text.length); - } - - override getOffset(position: Position): number { - const valPos = this._validatePosition(position); - return this.lineStartOffsetByLineIdx[valPos.lineNumber - 1] + valPos.column - 1; - } - - private _validatePosition(position: Position): Position { - if (position.lineNumber < 1) { - return new Position(1, 1); - } - const lineCount = this.textLength.lineCount + 1; - if (position.lineNumber > lineCount) { - const lineLength = this.getLineLength(lineCount); - return new Position(lineCount, lineLength + 1); - } - if (position.column < 1) { - return new Position(position.lineNumber, 1); - } - const lineLength = this.getLineLength(position.lineNumber); - if (position.column - 1 > lineLength) { - return new Position(position.lineNumber, lineLength + 1); - } - return position; - } - - override getPosition(offset: number): Position { - const idx = findLastIdxMonotonous(this.lineStartOffsetByLineIdx, i => i <= offset); - const lineNumber = idx + 1; - const column = offset - this.lineStartOffsetByLineIdx[idx] + 1; - return new Position(lineNumber, column); - } - - getTextLength(offsetRange: OffsetRange): TextLength { - return TextLength.ofRange(this.getRange(offsetRange)); - } - - get textLength(): TextLength { - const lineIdx = this.lineStartOffsetByLineIdx.length - 1; - return new TextLength(lineIdx, this.text.length - this.lineStartOffsetByLineIdx[lineIdx]); - } - - getLineLength(lineNumber: number): number { - return this.lineEndOffsetByLineIdx[lineNumber - 1] - this.lineStartOffsetByLineIdx[lineNumber - 1]; - } -} +_setPositionOffsetTransformerDependencies({ + StringEdit: StringEdit, + StringReplacement: StringReplacement, + TextReplacement: TextReplacement, + TextEdit: TextEdit, + TextLength: TextLength, +}); diff --git a/src/vs/editor/common/core/text/positionToOffsetImpl.ts b/src/vs/editor/common/core/text/positionToOffsetImpl.ts new file mode 100644 index 00000000000..deb847b1322 --- /dev/null +++ b/src/vs/editor/common/core/text/positionToOffsetImpl.ts @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { findLastIdxMonotonous } from '../../../../base/common/arraysFind.js'; +import { StringEdit, StringReplacement } from '../edits/stringEdit.js'; +import { OffsetRange } from '../ranges/offsetRange.js'; +import { Position } from '../position.js'; +import { Range } from '../range.js'; +import type { TextReplacement, TextEdit } from '../edits/textEdit.js'; +import type { TextLength } from '../text/textLength.js'; + +export abstract class PositionOffsetTransformerBase { + abstract getOffset(position: Position): number; + + getOffsetRange(range: Range): OffsetRange { + return new OffsetRange( + this.getOffset(range.getStartPosition()), + this.getOffset(range.getEndPosition()) + ); + } + + abstract getPosition(offset: number): Position; + + getRange(offsetRange: OffsetRange): Range { + return Range.fromPositions( + this.getPosition(offsetRange.start), + this.getPosition(offsetRange.endExclusive) + ); + } + + getStringEdit(edit: TextEdit): StringEdit { + const edits = edit.replacements.map(e => this.getStringReplacement(e)); + return new Deps.deps.StringEdit(edits); + } + + getStringReplacement(edit: TextReplacement): StringReplacement { + return new Deps.deps.StringReplacement(this.getOffsetRange(edit.range), edit.text); + } + + getSingleTextEdit(edit: StringReplacement): TextReplacement { + return new Deps.deps.TextReplacement(this.getRange(edit.replaceRange), edit.newText); + } + + getTextEdit(edit: StringEdit): TextEdit { + const edits = edit.replacements.map(e => this.getSingleTextEdit(e)); + return new Deps.deps.TextEdit(edits); + } +} + +interface IDeps { + StringEdit: typeof StringEdit; + StringReplacement: typeof StringReplacement; + TextReplacement: typeof TextReplacement; + TextEdit: typeof TextEdit; + TextLength: typeof TextLength; +} + +class Deps { + static _deps: IDeps | undefined = undefined; + static get deps(): IDeps { + if (!this._deps) { + throw new Error('Dependencies not set. Call _setDependencies first.'); + } + return this._deps; + } +} + +/** This is to break circular module dependencies. */ +export function _setPositionOffsetTransformerDependencies(deps: IDeps): void { + Deps._deps = deps; +} + +export class PositionOffsetTransformer extends PositionOffsetTransformerBase { + private readonly lineStartOffsetByLineIdx: number[]; + private readonly lineEndOffsetByLineIdx: number[]; + + constructor(public readonly text: string) { + super(); + + this.lineStartOffsetByLineIdx = []; + this.lineEndOffsetByLineIdx = []; + + this.lineStartOffsetByLineIdx.push(0); + for (let i = 0; i < text.length; i++) { + if (text.charAt(i) === '\n') { + this.lineStartOffsetByLineIdx.push(i + 1); + if (i > 0 && text.charAt(i - 1) === '\r') { + this.lineEndOffsetByLineIdx.push(i - 1); + } else { + this.lineEndOffsetByLineIdx.push(i); + } + } + } + this.lineEndOffsetByLineIdx.push(text.length); + } + + override getOffset(position: Position): number { + const valPos = this._validatePosition(position); + return this.lineStartOffsetByLineIdx[valPos.lineNumber - 1] + valPos.column - 1; + } + + private _validatePosition(position: Position): Position { + if (position.lineNumber < 1) { + return new Position(1, 1); + } + const lineCount = this.textLength.lineCount + 1; + if (position.lineNumber > lineCount) { + const lineLength = this.getLineLength(lineCount); + return new Position(lineCount, lineLength + 1); + } + if (position.column < 1) { + return new Position(position.lineNumber, 1); + } + const lineLength = this.getLineLength(position.lineNumber); + if (position.column - 1 > lineLength) { + return new Position(position.lineNumber, lineLength + 1); + } + return position; + } + + override getPosition(offset: number): Position { + const idx = findLastIdxMonotonous(this.lineStartOffsetByLineIdx, i => i <= offset); + const lineNumber = idx + 1; + const column = offset - this.lineStartOffsetByLineIdx[idx] + 1; + return new Position(lineNumber, column); + } + + getTextLength(offsetRange: OffsetRange): TextLength { + return Deps.deps.TextLength.ofRange(this.getRange(offsetRange)); + } + + get textLength(): TextLength { + const lineIdx = this.lineStartOffsetByLineIdx.length - 1; + return new Deps.deps.TextLength(lineIdx, this.text.length - this.lineStartOffsetByLineIdx[lineIdx]); + } + + getLineLength(lineNumber: number): number { + return this.lineEndOffsetByLineIdx[lineNumber - 1] - this.lineStartOffsetByLineIdx[lineNumber - 1]; + } +} diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index ad56a01c508..e0cdf27692b 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -23,7 +23,7 @@ import { IModelContentChange, IModelContentChangedEvent, IModelDecorationsChange import { IGuidesTextModelPart } from './textModelGuides.js'; import { ITokenizationTextModelPart } from './tokenizationTextModelPart.js'; import { UndoRedoGroup } from '../../platform/undoRedo/common/undoRedo.js'; -import { TokenArray } from './tokens/tokenArray.js'; +import { TokenArray } from './tokens/lineTokens.js'; import { IEditorModel } from './editorCommon.js'; import { TextModelEditReason } from './textModelEditReason.js'; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 9497bc5a304..ddcc455dde6 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -46,7 +46,7 @@ import { ITokenizationTextModelPart } from '../tokenizationTextModelPart.js'; import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; import { IColorTheme } from '../../../platform/theme/common/themeService.js'; import { IUndoRedoService, ResourceEditStackSnapshot, UndoRedoGroup } from '../../../platform/undoRedo/common/undoRedo.js'; -import { TokenArray } from '../tokens/tokenArray.js'; +import { TokenArray } from '../tokens/lineTokens.js'; import { SetWithKey } from '../../../base/common/collections.js'; import { TextModelEditReason } from '../textModelEditReason.js'; diff --git a/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts b/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts index 02f789bce6a..5d61d664043 100644 --- a/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts +++ b/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as TreeSitter from '@vscode/tree-sitter-wasm'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { toDisposable } from '../../../../../base/common/lifecycle.js'; import { StandardTokenType } from '../../../encodedTokenAttributes.js'; @@ -196,17 +195,3 @@ export class TreeSitterSyntaxTokenBackend extends AbstractSyntaxTokenBackend { return model.hasTokens(); } } - -export function rangesEqual(a: TreeSitter.Range, b: TreeSitter.Range) { - return (a.startPosition.row === b.startPosition.row) - && (a.startPosition.column === b.startPosition.column) - && (a.endPosition.row === b.endPosition.row) - && (a.endPosition.column === b.endPosition.column) - && (a.startIndex === b.startIndex) - && (a.endIndex === b.endIndex); -} - -export function rangesIntersect(a: TreeSitter.Range, b: TreeSitter.Range) { - return (a.startIndex <= b.startIndex && a.endIndex >= b.startIndex) || - (b.startIndex <= a.startIndex && b.endIndex >= a.startIndex); -} diff --git a/src/vs/editor/common/model/tokens/treeSitter/treeSitterTree.ts b/src/vs/editor/common/model/tokens/treeSitter/treeSitterTree.ts index 2ced0ac10d4..e60618d03d2 100644 --- a/src/vs/editor/common/model/tokens/treeSitter/treeSitterTree.ts +++ b/src/vs/editor/common/model/tokens/treeSitter/treeSitterTree.ts @@ -13,7 +13,6 @@ import { TextLength } from '../../../core/text/textLength.js'; import { IModelContentChangedEvent, IModelContentChange } from '../../../textModelEvents.js'; import { TextModel } from '../../textModel.js'; import { gotoParent, getClosestPreviousNodes, nextSiblingOrParentSibling, gotoNthChild } from './cursorUtils.js'; -import { rangesIntersect, rangesEqual } from './treeSitterSyntaxTokenBackend.js'; import { Range } from '../../../core/range.js'; export class TreeSitterTree extends Disposable { @@ -445,3 +444,16 @@ function newTimeOutProgressCallback(): (state: TreeSitter.ParseState) => void { return false; }; } +export function rangesEqual(a: TreeSitter.Range, b: TreeSitter.Range) { + return (a.startPosition.row === b.startPosition.row) + && (a.startPosition.column === b.startPosition.column) + && (a.endPosition.row === b.endPosition.row) + && (a.endPosition.column === b.endPosition.column) + && (a.startIndex === b.startIndex) + && (a.endIndex === b.endIndex); +} + +export function rangesIntersect(a: TreeSitter.Range, b: TreeSitter.Range) { + return (a.startIndex <= b.startIndex && a.endIndex >= b.startIndex) || + (b.startIndex <= a.startIndex && b.endIndex >= a.startIndex); +} diff --git a/src/vs/editor/common/tokens/lineTokens.ts b/src/vs/editor/common/tokens/lineTokens.ts index 5beed680735..ef2ec6cd642 100644 --- a/src/vs/editor/common/tokens/lineTokens.ts +++ b/src/vs/editor/common/tokens/lineTokens.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { ILanguageIdCodec } from '../languages.js'; -import { FontStyle, ColorId, StandardTokenType, MetadataConsts, TokenMetadata, ITokenPresentation } from '../encodedTokenAttributes.js'; +import { FontStyle, ColorId, StandardTokenType, MetadataConsts, ITokenPresentation, TokenMetadata } from '../encodedTokenAttributes.js'; import { IPosition } from '../core/position.js'; import { ITextModel } from '../model.js'; import { OffsetRange } from '../core/ranges/offsetRange.js'; -import { TokenArray, TokenArrayBuilder } from './tokenArray.js'; import { onUnexpectedError } from '../../../base/common/errors.js'; @@ -422,3 +421,107 @@ export function getStandardTokenTypeAtPosition(model: ITextModel, position: IPos const tokenType = lineTokens.getStandardTokenType(tokenIndex); return tokenType; } + + + +/** + * This class represents a sequence of tokens. + * Conceptually, each token has a length and a metadata number. + * A token array might be used to annotate a string with metadata. + * Use {@link TokenArrayBuilder} to efficiently create a token array. + * + * TODO: Make this class more efficient (e.g. by using a Int32Array). +*/ +export class TokenArray { + public static fromLineTokens(lineTokens: LineTokens): TokenArray { + const tokenInfo: TokenInfo[] = []; + for (let i = 0; i < lineTokens.getCount(); i++) { + tokenInfo.push(new TokenInfo(lineTokens.getEndOffset(i) - lineTokens.getStartOffset(i), lineTokens.getMetadata(i))); + } + return TokenArray.create(tokenInfo); + } + + public static create(tokenInfo: TokenInfo[]): TokenArray { + return new TokenArray(tokenInfo); + } + + private constructor( + private readonly _tokenInfo: TokenInfo[] + ) { } + + public toLineTokens(lineContent: string, decoder: ILanguageIdCodec): LineTokens { + return LineTokens.createFromTextAndMetadata(this.map((r, t) => ({ text: r.substring(lineContent), metadata: t.metadata })), decoder); + } + + public forEach(cb: (range: OffsetRange, tokenInfo: TokenInfo) => void): void { + let lengthSum = 0; + for (const tokenInfo of this._tokenInfo) { + const range = new OffsetRange(lengthSum, lengthSum + tokenInfo.length); + cb(range, tokenInfo); + lengthSum += tokenInfo.length; + } + } + + public map(cb: (range: OffsetRange, tokenInfo: TokenInfo) => T): T[] { + const result: T[] = []; + let lengthSum = 0; + for (const tokenInfo of this._tokenInfo) { + const range = new OffsetRange(lengthSum, lengthSum + tokenInfo.length); + result.push(cb(range, tokenInfo)); + lengthSum += tokenInfo.length; + } + return result; + } + + public slice(range: OffsetRange): TokenArray { + const result: TokenInfo[] = []; + let lengthSum = 0; + for (const tokenInfo of this._tokenInfo) { + const tokenStart = lengthSum; + const tokenEndEx = tokenStart + tokenInfo.length; + if (tokenEndEx > range.start) { + if (tokenStart >= range.endExclusive) { + break; + } + + const deltaBefore = Math.max(0, range.start - tokenStart); + const deltaAfter = Math.max(0, tokenEndEx - range.endExclusive); + + result.push(new TokenInfo(tokenInfo.length - deltaBefore - deltaAfter, tokenInfo.metadata)); + } + + lengthSum += tokenInfo.length; + } + return TokenArray.create(result); + } + + public append(other: TokenArray): TokenArray { + const result: TokenInfo[] = this._tokenInfo.concat(other._tokenInfo); + return TokenArray.create(result); + } +} + +export type ITokenMetadata = number; + +export class TokenInfo { + constructor( + public readonly length: number, + public readonly metadata: ITokenMetadata + ) { } +} +/** + * TODO: Make this class more efficient (e.g. by using a Int32Array). +*/ + +export class TokenArrayBuilder { + private readonly _tokens: TokenInfo[] = []; + + public add(length: number, metadata: ITokenMetadata): void { + this._tokens.push(new TokenInfo(length, metadata)); + } + + public build(): TokenArray { + return TokenArray.create(this._tokens); + } +} + diff --git a/src/vs/editor/common/tokens/tokenArray.ts b/src/vs/editor/common/tokens/tokenArray.ts deleted file mode 100644 index 93f19a8ec97..00000000000 --- a/src/vs/editor/common/tokens/tokenArray.ts +++ /dev/null @@ -1,109 +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 { OffsetRange } from '../core/ranges/offsetRange.js'; -import { ILanguageIdCodec } from '../languages.js'; -import { LineTokens } from './lineTokens.js'; - -/** - * This class represents a sequence of tokens. - * Conceptually, each token has a length and a metadata number. - * A token array might be used to annotate a string with metadata. - * Use {@link TokenArrayBuilder} to efficiently create a token array. - * - * TODO: Make this class more efficient (e.g. by using a Int32Array). -*/ -export class TokenArray { - public static fromLineTokens(lineTokens: LineTokens): TokenArray { - const tokenInfo: TokenInfo[] = []; - for (let i = 0; i < lineTokens.getCount(); i++) { - tokenInfo.push(new TokenInfo(lineTokens.getEndOffset(i) - lineTokens.getStartOffset(i), lineTokens.getMetadata(i))); - } - return TokenArray.create(tokenInfo); - } - - public static create(tokenInfo: TokenInfo[]): TokenArray { - return new TokenArray(tokenInfo); - } - - private constructor( - private readonly _tokenInfo: TokenInfo[], - ) { } - - public toLineTokens(lineContent: string, decoder: ILanguageIdCodec): LineTokens { - return LineTokens.createFromTextAndMetadata(this.map((r, t) => ({ text: r.substring(lineContent), metadata: t.metadata })), decoder); - } - - public forEach(cb: (range: OffsetRange, tokenInfo: TokenInfo) => void): void { - let lengthSum = 0; - for (const tokenInfo of this._tokenInfo) { - const range = new OffsetRange(lengthSum, lengthSum + tokenInfo.length); - cb(range, tokenInfo); - lengthSum += tokenInfo.length; - } - } - - public map(cb: (range: OffsetRange, tokenInfo: TokenInfo) => T): T[] { - const result: T[] = []; - let lengthSum = 0; - for (const tokenInfo of this._tokenInfo) { - const range = new OffsetRange(lengthSum, lengthSum + tokenInfo.length); - result.push(cb(range, tokenInfo)); - lengthSum += tokenInfo.length; - } - return result; - } - - public slice(range: OffsetRange): TokenArray { - const result: TokenInfo[] = []; - let lengthSum = 0; - for (const tokenInfo of this._tokenInfo) { - const tokenStart = lengthSum; - const tokenEndEx = tokenStart + tokenInfo.length; - if (tokenEndEx > range.start) { - if (tokenStart >= range.endExclusive) { - break; - } - - const deltaBefore = Math.max(0, range.start - tokenStart); - const deltaAfter = Math.max(0, tokenEndEx - range.endExclusive); - - result.push(new TokenInfo(tokenInfo.length - deltaBefore - deltaAfter, tokenInfo.metadata)); - } - - lengthSum += tokenInfo.length; - } - return TokenArray.create(result); - } - - public append(other: TokenArray): TokenArray { - const result: TokenInfo[] = this._tokenInfo.concat(other._tokenInfo); - return TokenArray.create(result); - } -} - -export type TokenMetadata = number; - -export class TokenInfo { - constructor( - public readonly length: number, - public readonly metadata: TokenMetadata, - ) { } -} - -/** - * TODO: Make this class more efficient (e.g. by using a Int32Array). -*/ -export class TokenArrayBuilder { - private readonly _tokens: TokenInfo[] = []; - - public add(length: number, metadata: TokenMetadata): void { - this._tokens.push(new TokenInfo(length, metadata)); - } - - public build(): TokenArray { - return TokenArray.create(this._tokens); - } -} diff --git a/src/vs/editor/contrib/gpu/browser/gpuActions.ts b/src/vs/editor/contrib/gpu/browser/gpuActions.ts index a1a3afe9a5b..a323d67b1fc 100644 --- a/src/vs/editor/contrib/gpu/browser/gpuActions.ts +++ b/src/vs/editor/contrib/gpu/browser/gpuActions.ts @@ -105,7 +105,7 @@ class DebugEditorGpuRendererAction extends EditorAction { const atlas = ViewGpuContext.atlas; const fontFamily = configurationService.getValue('editor.fontFamily'); const fontSize = configurationService.getValue('editor.fontSize'); - const rasterizer = new GlyphRasterizer(fontSize, fontFamily, getActiveWindow().devicePixelRatio); + const rasterizer = new GlyphRasterizer(fontSize, fontFamily, getActiveWindow().devicePixelRatio, ViewGpuContext.decorationStyleCache); let chars = await quickInputService.input({ prompt: 'Enter a character to draw (prefix with 0x for code point))' }); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts index 0aca2fe89e5..f487e2a5bcd 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createStyleSheetFromObservable } from '../../../../../base/browser/dom.js'; +import { createStyleSheetFromObservable } from '../../../../../base/browser/domStylesheets.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { derived, mapObservableArrayCached, derivedDisposable, constObservable, derivedObservableWithCache, IObservable, ISettableObservable } from '../../../../../base/common/observable.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsCustomView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsCustomView.ts index 9a58349ef2f..01ca906fb37 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsCustomView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsCustomView.ts @@ -12,14 +12,13 @@ import { asCssVariable } from '../../../../../../../platform/theme/common/colorU import { IThemeService } from '../../../../../../../platform/theme/common/themeService.js'; import { ICodeEditor } from '../../../../../../browser/editorBrowser.js'; import { ObservableCodeEditor, observableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; -import { Rect } from '../../../../../../common/core/2d/rect.js'; import { LineSource, renderLines, RenderOptions } from '../../../../../../browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; import { EditorOption } from '../../../../../../common/config/editorOptions.js'; +import { Rect } from '../../../../../../common/core/2d/rect.js'; import { LineRange } from '../../../../../../common/core/ranges/lineRange.js'; import { InlineCompletionDisplayLocation } from '../../../../../../common/languages.js'; import { ILanguageService } from '../../../../../../common/languages/language.js'; -import { LineTokens } from '../../../../../../common/tokens/lineTokens.js'; -import { TokenArray } from '../../../../../../common/tokens/tokenArray.js'; +import { LineTokens, TokenArray } from '../../../../../../common/tokens/lineTokens.js'; import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { getEditorBlendedColor, inlineEditIndicatorPrimaryBackground, inlineEditIndicatorSecondaryBackground, inlineEditIndicatorsuccessfulBackground } from '../theme.js'; import { maxContentWidthInRange, rectToProps } from '../utils/utils.js'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsInsertionView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsInsertionView.ts index fb3eec679d3..2a681756bf4 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsInsertionView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsInsertionView.ts @@ -12,16 +12,15 @@ import { editorBackground } from '../../../../../../../platform/theme/common/col import { asCssVariable } from '../../../../../../../platform/theme/common/colorUtils.js'; import { ICodeEditor } from '../../../../../../browser/editorBrowser.js'; import { ObservableCodeEditor, observableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; -import { Rect } from '../../../../../../common/core/2d/rect.js'; import { LineSource, renderLines, RenderOptions } from '../../../../../../browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; import { EditorOption } from '../../../../../../common/config/editorOptions.js'; -import { LineRange } from '../../../../../../common/core/ranges/lineRange.js'; -import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; +import { Rect } from '../../../../../../common/core/2d/rect.js'; import { Position } from '../../../../../../common/core/position.js'; import { Range } from '../../../../../../common/core/range.js'; +import { LineRange } from '../../../../../../common/core/ranges/lineRange.js'; +import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; import { ILanguageService } from '../../../../../../common/languages/language.js'; -import { LineTokens } from '../../../../../../common/tokens/lineTokens.js'; -import { TokenArray } from '../../../../../../common/tokens/tokenArray.js'; +import { LineTokens, TokenArray } from '../../../../../../common/tokens/lineTokens.js'; import { InlineDecoration, InlineDecorationType } from '../../../../../../common/viewModel.js'; import { GhostText, GhostTextPart } from '../../../model/ghostText.js'; import { GhostTextView } from '../../ghostText/ghostTextView.js'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts index 27447f6a1ac..e014a42f695 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts @@ -14,17 +14,16 @@ import { IThemeService } from '../../../../../../../platform/theme/common/themeS import { IEditorMouseEvent, IViewZoneChangeAccessor } from '../../../../../../browser/editorBrowser.js'; import { EditorMouseEvent } from '../../../../../../browser/editorDom.js'; import { ObservableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; -import { Point } from '../../../../../../common/core/2d/point.js'; -import { Rect } from '../../../../../../common/core/2d/rect.js'; import { LineSource, renderLines, RenderOptions } from '../../../../../../browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; import { EditorOption } from '../../../../../../common/config/editorOptions.js'; +import { Point } from '../../../../../../common/core/2d/point.js'; +import { Rect } from '../../../../../../common/core/2d/rect.js'; +import { Range } from '../../../../../../common/core/range.js'; import { LineRange } from '../../../../../../common/core/ranges/lineRange.js'; import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; -import { Range } from '../../../../../../common/core/range.js'; import { ILanguageService } from '../../../../../../common/languages/language.js'; import { IModelDecorationOptions, TrackedRangeStickiness } from '../../../../../../common/model.js'; -import { LineTokens } from '../../../../../../common/tokens/lineTokens.js'; -import { TokenArray } from '../../../../../../common/tokens/tokenArray.js'; +import { LineTokens, TokenArray } from '../../../../../../common/tokens/lineTokens.js'; import { InlineDecoration, InlineDecorationType } from '../../../../../../common/viewModel.js'; import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { getEditorBlendedColor, getModifiedBorderColor, getOriginalBorderColor, modifiedChangedLineBackgroundColor, originalBackgroundColor } from '../theme.js'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordReplacementView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordReplacementView.ts index 09e0f708ed4..93d45294f36 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordReplacementView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordReplacementView.ts @@ -11,16 +11,15 @@ import { constObservable, derived, IObservable, observableValue } from '../../.. import { editorBackground, editorHoverForeground } from '../../../../../../../platform/theme/common/colorRegistry.js'; import { asCssVariable } from '../../../../../../../platform/theme/common/colorUtils.js'; import { ObservableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; -import { Point } from '../../../../../../common/core/2d/point.js'; -import { Rect } from '../../../../../../common/core/2d/rect.js'; import { LineSource, renderLines, RenderOptions } from '../../../../../../browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; import { EditorOption } from '../../../../../../common/config/editorOptions.js'; +import { Point } from '../../../../../../common/core/2d/point.js'; +import { Rect } from '../../../../../../common/core/2d/rect.js'; import { StringReplacement } from '../../../../../../common/core/edits/stringEdit.js'; -import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; import { TextReplacement } from '../../../../../../common/core/edits/textEdit.js'; +import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; import { ILanguageService } from '../../../../../../common/languages/language.js'; -import { LineTokens } from '../../../../../../common/tokens/lineTokens.js'; -import { TokenArray } from '../../../../../../common/tokens/tokenArray.js'; +import { LineTokens, TokenArray } from '../../../../../../common/tokens/lineTokens.js'; import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { getModifiedBorderColor, getOriginalBorderColor, modifiedChangedTextOverlayColor, originalChangedTextOverlayColor } from '../theme.js'; import { getEditorValidOverlayRect, mapOutFalsy, rectToProps } from '../utils/utils.js'; diff --git a/src/vs/editor/test/browser/gpu/atlas/textureAtlas.test.ts b/src/vs/editor/test/browser/gpu/atlas/textureAtlas.test.ts index 2b0e55aab2c..41682b417f1 100644 --- a/src/vs/editor/test/browser/gpu/atlas/textureAtlas.test.ts +++ b/src/vs/editor/test/browser/gpu/atlas/textureAtlas.test.ts @@ -12,6 +12,7 @@ import { TextureAtlas } from '../../../../browser/gpu/atlas/textureAtlas.js'; import { createCodeEditorServices } from '../../testCodeEditor.js'; import { assertIsValidGlyph } from './testUtil.js'; import { TextureAtlasSlabAllocator } from '../../../../browser/gpu/atlas/textureAtlasSlabAllocator.js'; +import { DecorationStyleCache } from '../../../../browser/gpu/css/decorationStyleCache.js'; const blackInt = 0x000000FF; const nullCharMetadata = 0x0; @@ -79,7 +80,7 @@ suite('TextureAtlas', () => { setup(() => { instantiationService = createCodeEditorServices(store); - atlas = store.add(instantiationService.createInstance(TextureAtlas, 2, undefined)); + atlas = store.add(instantiationService.createInstance(TextureAtlas, 2, undefined, new DecorationStyleCache())); glyphRasterizer = new TestGlyphRasterizer(); glyphRasterizer.nextGlyphDimensions = [1, 1]; glyphRasterizer.nextGlyphColor = [0, 0, 0, 0xFF]; @@ -90,7 +91,7 @@ suite('TextureAtlas', () => { }); test('get multiple glyphs', () => { - atlas = store.add(instantiationService.createInstance(TextureAtlas, 32, undefined)); + atlas = store.add(instantiationService.createInstance(TextureAtlas, 32, undefined, new DecorationStyleCache())); for (let i = 0; i < 10; i++) { assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas); } @@ -119,14 +120,14 @@ suite('TextureAtlas', () => { glyphRasterizer.nextGlyphDimensions = [2, 2]; atlas = store.add(instantiationService.createInstance(TextureAtlas, 32, { allocatorType: (canvas, textureIndex) => new TextureAtlasSlabAllocator(canvas, textureIndex, { slabW: 1, slabH: 1 }) - })); + }, new DecorationStyleCache())); assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas); }); test('adding a non-first glyph larger than the standard slab size, causing an overflow to a new page', () => { atlas = store.add(instantiationService.createInstance(TextureAtlas, 2, { allocatorType: (canvas, textureIndex) => new TextureAtlasSlabAllocator(canvas, textureIndex, { slabW: 1, slabH: 1 }) - })); + }, new DecorationStyleCache())); assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas); strictEqual(atlas.pages.length, 1); glyphRasterizer.nextGlyphDimensions = [2, 2]; diff --git a/src/vs/platform/theme/electron-main/themeMainService.ts b/src/vs/platform/theme/electron-main/themeMainService.ts index d530126a31a..531fc6171e6 100644 --- a/src/vs/platform/theme/electron-main/themeMainService.ts +++ b/src/vs/platform/theme/electron-main/themeMainService.ts @@ -3,57 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import electron from 'electron'; -import { Emitter, Event } from '../../../base/common/event.js'; -import { Disposable } from '../../../base/common/lifecycle.js'; -import { isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js'; -import { IConfigurationService } from '../../configuration/common/configuration.js'; +import { Event } from '../../../base/common/event.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; -import { IStateService } from '../../state/node/state.js'; import { IPartsSplash } from '../common/themeService.js'; import { IColorScheme } from '../../window/common/window.js'; -import { ThemeTypeSelector } from '../common/theme.js'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from '../../workspace/common/workspace.js'; -import { coalesce } from '../../../base/common/arrays.js'; -import { getAllWindowsExcludingOffscreen } from '../../windows/electron-main/windows.js'; -import { ILogService } from '../../log/common/log.js'; - -// These default colors match our default themes -// editor background color ("Dark Modern", etc...) -const DEFAULT_BG_LIGHT = '#FFFFFF'; -const DEFAULT_BG_DARK = '#1F1F1F'; -const DEFAULT_BG_HC_BLACK = '#000000'; -const DEFAULT_BG_HC_LIGHT = '#FFFFFF'; - -const THEME_STORAGE_KEY = 'theme'; -const THEME_BG_STORAGE_KEY = 'themeBackground'; - -const THEME_WINDOW_SPLASH_KEY = 'windowSplash'; -const THEME_WINDOW_SPLASH_OVERRIDE_KEY = 'windowSplashWorkspaceOverride'; - -const AUXILIARYBAR_DEFAULT_VISIBILITY = 'workbench.secondarySideBar.defaultVisibility'; - -namespace ThemeSettings { - export const DETECT_COLOR_SCHEME = 'window.autoDetectColorScheme'; - export const DETECT_HC = 'window.autoDetectHighContrast'; - export const SYSTEM_COLOR_THEME = 'window.systemColorTheme'; -} - -interface IPartSplashOverrideWorkspaces { - [workspaceId: string]: { - sideBarVisible: boolean; - auxiliaryBarVisible: boolean; - }; -} - -interface IPartsSplashOverride { - layoutInfo: { - sideBarWidth: number; - auxiliaryBarWidth: number; - - workspaces: IPartSplashOverrideWorkspaces; - }; -} export const IThemeMainService = createDecorator('themeMainService'); @@ -70,340 +24,3 @@ export interface IThemeMainService { getColorScheme(): IColorScheme; } - -export class ThemeMainService extends Disposable implements IThemeMainService { - - declare readonly _serviceBrand: undefined; - - private static readonly DEFAULT_BAR_WIDTH = 300; - - private static readonly WORKSPACE_OVERRIDE_LIMIT = 50; - - private readonly _onDidChangeColorScheme = this._register(new Emitter()); - readonly onDidChangeColorScheme = this._onDidChangeColorScheme.event; - - constructor( - @IStateService private stateService: IStateService, - @IConfigurationService private configurationService: IConfigurationService, - @ILogService private logService: ILogService - ) { - super(); - - // System Theme - if (!isLinux) { - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(ThemeSettings.SYSTEM_COLOR_THEME) || e.affectsConfiguration(ThemeSettings.DETECT_COLOR_SCHEME)) { - this.updateSystemColorTheme(); - } - })); - } - this.updateSystemColorTheme(); - - // Color Scheme changes - this._register(Event.fromNodeEventEmitter(electron.nativeTheme, 'updated')(() => this._onDidChangeColorScheme.fire(this.getColorScheme()))); - } - - private updateSystemColorTheme(): void { - if (isLinux || this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { - electron.nativeTheme.themeSource = 'system'; // only with `system` we can detect the system color scheme - } else { - switch (this.configurationService.getValue<'default' | 'auto' | 'light' | 'dark'>(ThemeSettings.SYSTEM_COLOR_THEME)) { - case 'dark': - electron.nativeTheme.themeSource = 'dark'; - break; - case 'light': - electron.nativeTheme.themeSource = 'light'; - break; - case 'auto': - switch (this.getPreferredBaseTheme() ?? this.getStoredBaseTheme()) { - case ThemeTypeSelector.VS: electron.nativeTheme.themeSource = 'light'; break; - case ThemeTypeSelector.VS_DARK: electron.nativeTheme.themeSource = 'dark'; break; - default: electron.nativeTheme.themeSource = 'system'; - } - break; - default: - electron.nativeTheme.themeSource = 'system'; - break; - } - } - } - - getColorScheme(): IColorScheme { - - // high contrast is reflected by the shouldUseInvertedColorScheme property - if (isWindows) { - if (electron.nativeTheme.shouldUseHighContrastColors) { - // shouldUseInvertedColorScheme is dark, !shouldUseInvertedColorScheme is light - return { dark: electron.nativeTheme.shouldUseInvertedColorScheme, highContrast: true }; - } - } - - // high contrast is set if one of shouldUseInvertedColorScheme or shouldUseHighContrastColors is set, - // reflecting the 'Invert colours' and `Increase contrast` settings in MacOS - else if (isMacintosh) { - if (electron.nativeTheme.shouldUseInvertedColorScheme || electron.nativeTheme.shouldUseHighContrastColors) { - return { dark: electron.nativeTheme.shouldUseDarkColors, highContrast: true }; - } - } - - // ubuntu gnome seems to have 3 states, light dark and high contrast - else if (isLinux) { - if (electron.nativeTheme.shouldUseHighContrastColors) { - return { dark: true, highContrast: true }; - } - } - - return { - dark: electron.nativeTheme.shouldUseDarkColors, - highContrast: false - }; - } - - getPreferredBaseTheme(): ThemeTypeSelector | undefined { - const colorScheme = this.getColorScheme(); - if (this.configurationService.getValue(ThemeSettings.DETECT_HC) && colorScheme.highContrast) { - return colorScheme.dark ? ThemeTypeSelector.HC_BLACK : ThemeTypeSelector.HC_LIGHT; - } - - if (this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { - return colorScheme.dark ? ThemeTypeSelector.VS_DARK : ThemeTypeSelector.VS; - } - - return undefined; - } - - getBackgroundColor(): string { - const preferred = this.getPreferredBaseTheme(); - const stored = this.getStoredBaseTheme(); - - // If the stored theme has the same base as the preferred, we can return the stored background - if (preferred === undefined || preferred === stored) { - const storedBackground = this.stateService.getItem(THEME_BG_STORAGE_KEY, null); - if (storedBackground) { - return storedBackground; - } - } - - // Otherwise we return the default background for the preferred base theme. If there's no preferred, use the stored one. - switch (preferred ?? stored) { - case ThemeTypeSelector.VS: return DEFAULT_BG_LIGHT; - case ThemeTypeSelector.HC_BLACK: return DEFAULT_BG_HC_BLACK; - case ThemeTypeSelector.HC_LIGHT: return DEFAULT_BG_HC_LIGHT; - default: return DEFAULT_BG_DARK; - } - } - - private getStoredBaseTheme(): ThemeTypeSelector { - const baseTheme = this.stateService.getItem(THEME_STORAGE_KEY, ThemeTypeSelector.VS_DARK).split(' ')[0]; - switch (baseTheme) { - case ThemeTypeSelector.VS: return ThemeTypeSelector.VS; - case ThemeTypeSelector.HC_BLACK: return ThemeTypeSelector.HC_BLACK; - case ThemeTypeSelector.HC_LIGHT: return ThemeTypeSelector.HC_LIGHT; - default: return ThemeTypeSelector.VS_DARK; - } - } - - saveWindowSplash(windowId: number | undefined, workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined, splash: IPartsSplash): void { - - // Update override as needed - const splashOverride = this.updateWindowSplashOverride(workspace, splash); - - // Update in storage - this.stateService.setItems(coalesce([ - { key: THEME_STORAGE_KEY, data: splash.baseTheme }, - { key: THEME_BG_STORAGE_KEY, data: splash.colorInfo.background }, - { key: THEME_WINDOW_SPLASH_KEY, data: splash }, - splashOverride ? { key: THEME_WINDOW_SPLASH_OVERRIDE_KEY, data: splashOverride } : undefined - ])); - - // Update in opened windows - if (typeof windowId === 'number') { - this.updateBackgroundColor(windowId, splash); - } - - // Update system theme - this.updateSystemColorTheme(); - } - - private updateWindowSplashOverride(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined, splash: IPartsSplash): IPartsSplashOverride | undefined { - let splashOverride: IPartsSplashOverride | undefined = undefined; - let changed = false; - if (workspace) { - splashOverride = { ...this.getWindowSplashOverride() }; // make a copy for modifications - - changed = this.doUpdateWindowSplashOverride(workspace, splash, splashOverride, 'sideBar'); - changed = this.doUpdateWindowSplashOverride(workspace, splash, splashOverride, 'auxiliaryBar') || changed; - } - - return changed ? splashOverride : undefined; - } - - private doUpdateWindowSplashOverride(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier, splash: IPartsSplash, splashOverride: IPartsSplashOverride, part: 'sideBar' | 'auxiliaryBar'): boolean { - const currentWidth = part === 'sideBar' ? splash.layoutInfo?.sideBarWidth : splash.layoutInfo?.auxiliarySideBarWidth; - const overrideWidth = part === 'sideBar' ? splashOverride.layoutInfo.sideBarWidth : splashOverride.layoutInfo.auxiliaryBarWidth; - - // No layout info: remove override - let changed = false; - if (typeof currentWidth !== 'number') { - if (splashOverride.layoutInfo.workspaces[workspace.id]) { - delete splashOverride.layoutInfo.workspaces[workspace.id]; - changed = true; - } - - return changed; - } - - let workspaceOverride = splashOverride.layoutInfo.workspaces[workspace.id]; - if (!workspaceOverride) { - const workspaceEntries = Object.keys(splashOverride.layoutInfo.workspaces); - if (workspaceEntries.length >= ThemeMainService.WORKSPACE_OVERRIDE_LIMIT) { - delete splashOverride.layoutInfo.workspaces[workspaceEntries[0]]; - changed = true; - } - - workspaceOverride = { sideBarVisible: false, auxiliaryBarVisible: false }; - splashOverride.layoutInfo.workspaces[workspace.id] = workspaceOverride; - changed = true; - } - - // Part has width: update width & visibility override - if (currentWidth > 0) { - if (overrideWidth !== currentWidth) { - splashOverride.layoutInfo[part === 'sideBar' ? 'sideBarWidth' : 'auxiliaryBarWidth'] = currentWidth; - changed = true; - } - - switch (part) { - case 'sideBar': - if (!workspaceOverride.sideBarVisible) { - workspaceOverride.sideBarVisible = true; - changed = true; - } - break; - case 'auxiliaryBar': - if (!workspaceOverride.auxiliaryBarVisible) { - workspaceOverride.auxiliaryBarVisible = true; - changed = true; - } - break; - } - } - - // Part is hidden: update visibility override - else { - switch (part) { - case 'sideBar': - if (workspaceOverride.sideBarVisible) { - workspaceOverride.sideBarVisible = false; - changed = true; - } - break; - case 'auxiliaryBar': - if (workspaceOverride.auxiliaryBarVisible) { - workspaceOverride.auxiliaryBarVisible = false; - changed = true; - } - break; - } - } - - return changed; - } - - private updateBackgroundColor(windowId: number, splash: IPartsSplash): void { - for (const window of getAllWindowsExcludingOffscreen()) { - if (window.id === windowId) { - window.setBackgroundColor(splash.colorInfo.background); - break; - } - } - } - - getWindowSplash(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined): IPartsSplash | undefined { - try { - return this.doGetWindowSplash(workspace); - } catch (error) { - this.logService.error('[theme main service] Failed to get window splash', error); - - return undefined; - } - } - - private doGetWindowSplash(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined): IPartsSplash | undefined { - const partSplash = this.stateService.getItem(THEME_WINDOW_SPLASH_KEY); - if (!partSplash?.layoutInfo) { - return partSplash; // return early: overrides currently only apply to layout info - } - - const override = this.getWindowSplashOverride(); - - // Figure out side bar width based on workspace and overrides - let sideBarWidth: number; - if (workspace) { - if (override.layoutInfo.workspaces[workspace.id]?.sideBarVisible === false) { - sideBarWidth = 0; - } else { - sideBarWidth = override.layoutInfo.sideBarWidth || partSplash.layoutInfo.sideBarWidth || ThemeMainService.DEFAULT_BAR_WIDTH; - } - } else { - sideBarWidth = 0; - } - - // Figure out auxiliary bar width based on workspace, configuration and overrides - const auxiliarySideBarDefaultVisibility = this.configurationService.getValue(AUXILIARYBAR_DEFAULT_VISIBILITY); - let auxiliarySideBarWidth: number; - if (workspace) { - const auxiliaryBarVisible = override.layoutInfo.workspaces[workspace.id]?.auxiliaryBarVisible; - if (auxiliaryBarVisible === true) { - auxiliarySideBarWidth = override.layoutInfo.auxiliaryBarWidth || partSplash.layoutInfo.auxiliarySideBarWidth || ThemeMainService.DEFAULT_BAR_WIDTH; - } else if (auxiliaryBarVisible === false) { - auxiliarySideBarWidth = 0; - } else { - if (auxiliarySideBarDefaultVisibility === 'visible' || auxiliarySideBarDefaultVisibility === 'visibleInWorkspace') { - auxiliarySideBarWidth = override.layoutInfo.auxiliaryBarWidth || partSplash.layoutInfo.auxiliarySideBarWidth || ThemeMainService.DEFAULT_BAR_WIDTH; - } else { - auxiliarySideBarWidth = 0; - } - } - } else { - auxiliarySideBarWidth = 0; // technically not true if configured 'visible', but we never store splash per empty window, so we decide on a default here - } - - return { - ...partSplash, - layoutInfo: { - ...partSplash.layoutInfo, - sideBarWidth, - auxiliarySideBarWidth - } - }; - } - - private getWindowSplashOverride(): IPartsSplashOverride { - let override = this.stateService.getItem(THEME_WINDOW_SPLASH_OVERRIDE_KEY); - - if (!override?.layoutInfo) { - override = { - layoutInfo: { - sideBarWidth: ThemeMainService.DEFAULT_BAR_WIDTH, - auxiliaryBarWidth: ThemeMainService.DEFAULT_BAR_WIDTH, - workspaces: {} - } - }; - } - - if (!override.layoutInfo.sideBarWidth) { - override.layoutInfo.sideBarWidth = ThemeMainService.DEFAULT_BAR_WIDTH; - } - - if (!override.layoutInfo.auxiliaryBarWidth) { - override.layoutInfo.auxiliaryBarWidth = ThemeMainService.DEFAULT_BAR_WIDTH; - } - - if (!override.layoutInfo.workspaces) { - override.layoutInfo.workspaces = {}; - } - - return override; - } -} diff --git a/src/vs/platform/theme/electron-main/themeMainServiceImpl.ts b/src/vs/platform/theme/electron-main/themeMainServiceImpl.ts new file mode 100644 index 00000000000..a888ae35bf1 --- /dev/null +++ b/src/vs/platform/theme/electron-main/themeMainServiceImpl.ts @@ -0,0 +1,393 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import electron from 'electron'; +import { Emitter, Event } from '../../../base/common/event.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; +import { isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js'; +import { IConfigurationService } from '../../configuration/common/configuration.js'; +import { IStateService } from '../../state/node/state.js'; +import { IPartsSplash } from '../common/themeService.js'; +import { IColorScheme } from '../../window/common/window.js'; +import { ThemeTypeSelector } from '../common/theme.js'; +import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from '../../workspace/common/workspace.js'; +import { coalesce } from '../../../base/common/arrays.js'; +import { getAllWindowsExcludingOffscreen } from '../../windows/electron-main/windows.js'; +import { ILogService } from '../../log/common/log.js'; +import { IThemeMainService } from './themeMainService.js'; + +// These default colors match our default themes +// editor background color ("Dark Modern", etc...) +const DEFAULT_BG_LIGHT = '#FFFFFF'; +const DEFAULT_BG_DARK = '#1F1F1F'; +const DEFAULT_BG_HC_BLACK = '#000000'; +const DEFAULT_BG_HC_LIGHT = '#FFFFFF'; + +const THEME_STORAGE_KEY = 'theme'; +const THEME_BG_STORAGE_KEY = 'themeBackground'; + +const THEME_WINDOW_SPLASH_KEY = 'windowSplash'; +const THEME_WINDOW_SPLASH_OVERRIDE_KEY = 'windowSplashWorkspaceOverride'; + +const AUXILIARYBAR_DEFAULT_VISIBILITY = 'workbench.secondarySideBar.defaultVisibility'; + +namespace ThemeSettings { + export const DETECT_COLOR_SCHEME = 'window.autoDetectColorScheme'; + export const DETECT_HC = 'window.autoDetectHighContrast'; + export const SYSTEM_COLOR_THEME = 'window.systemColorTheme'; +} + +interface IPartSplashOverrideWorkspaces { + [workspaceId: string]: { + sideBarVisible: boolean; + auxiliaryBarVisible: boolean; + }; +} + +interface IPartsSplashOverride { + layoutInfo: { + sideBarWidth: number; + auxiliaryBarWidth: number; + + workspaces: IPartSplashOverrideWorkspaces; + }; +} + +export class ThemeMainService extends Disposable implements IThemeMainService { + + declare readonly _serviceBrand: undefined; + + private static readonly DEFAULT_BAR_WIDTH = 300; + + private static readonly WORKSPACE_OVERRIDE_LIMIT = 50; + + private readonly _onDidChangeColorScheme = this._register(new Emitter()); + readonly onDidChangeColorScheme = this._onDidChangeColorScheme.event; + + constructor( + @IStateService private stateService: IStateService, + @IConfigurationService private configurationService: IConfigurationService, + @ILogService private logService: ILogService + ) { + super(); + + // System Theme + if (!isLinux) { + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(ThemeSettings.SYSTEM_COLOR_THEME) || e.affectsConfiguration(ThemeSettings.DETECT_COLOR_SCHEME)) { + this.updateSystemColorTheme(); + } + })); + } + this.updateSystemColorTheme(); + + // Color Scheme changes + this._register(Event.fromNodeEventEmitter(electron.nativeTheme, 'updated')(() => this._onDidChangeColorScheme.fire(this.getColorScheme()))); + } + + private updateSystemColorTheme(): void { + if (isLinux || this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { + electron.nativeTheme.themeSource = 'system'; // only with `system` we can detect the system color scheme + } else { + switch (this.configurationService.getValue<'default' | 'auto' | 'light' | 'dark'>(ThemeSettings.SYSTEM_COLOR_THEME)) { + case 'dark': + electron.nativeTheme.themeSource = 'dark'; + break; + case 'light': + electron.nativeTheme.themeSource = 'light'; + break; + case 'auto': + switch (this.getPreferredBaseTheme() ?? this.getStoredBaseTheme()) { + case ThemeTypeSelector.VS: electron.nativeTheme.themeSource = 'light'; break; + case ThemeTypeSelector.VS_DARK: electron.nativeTheme.themeSource = 'dark'; break; + default: electron.nativeTheme.themeSource = 'system'; + } + break; + default: + electron.nativeTheme.themeSource = 'system'; + break; + } + } + } + + getColorScheme(): IColorScheme { + + // high contrast is reflected by the shouldUseInvertedColorScheme property + if (isWindows) { + if (electron.nativeTheme.shouldUseHighContrastColors) { + // shouldUseInvertedColorScheme is dark, !shouldUseInvertedColorScheme is light + return { dark: electron.nativeTheme.shouldUseInvertedColorScheme, highContrast: true }; + } + } + + // high contrast is set if one of shouldUseInvertedColorScheme or shouldUseHighContrastColors is set, + // reflecting the 'Invert colours' and `Increase contrast` settings in MacOS + else if (isMacintosh) { + if (electron.nativeTheme.shouldUseInvertedColorScheme || electron.nativeTheme.shouldUseHighContrastColors) { + return { dark: electron.nativeTheme.shouldUseDarkColors, highContrast: true }; + } + } + + // ubuntu gnome seems to have 3 states, light dark and high contrast + else if (isLinux) { + if (electron.nativeTheme.shouldUseHighContrastColors) { + return { dark: true, highContrast: true }; + } + } + + return { + dark: electron.nativeTheme.shouldUseDarkColors, + highContrast: false + }; + } + + getPreferredBaseTheme(): ThemeTypeSelector | undefined { + const colorScheme = this.getColorScheme(); + if (this.configurationService.getValue(ThemeSettings.DETECT_HC) && colorScheme.highContrast) { + return colorScheme.dark ? ThemeTypeSelector.HC_BLACK : ThemeTypeSelector.HC_LIGHT; + } + + if (this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { + return colorScheme.dark ? ThemeTypeSelector.VS_DARK : ThemeTypeSelector.VS; + } + + return undefined; + } + + getBackgroundColor(): string { + const preferred = this.getPreferredBaseTheme(); + const stored = this.getStoredBaseTheme(); + + // If the stored theme has the same base as the preferred, we can return the stored background + if (preferred === undefined || preferred === stored) { + const storedBackground = this.stateService.getItem(THEME_BG_STORAGE_KEY, null); + if (storedBackground) { + return storedBackground; + } + } + + // Otherwise we return the default background for the preferred base theme. If there's no preferred, use the stored one. + switch (preferred ?? stored) { + case ThemeTypeSelector.VS: return DEFAULT_BG_LIGHT; + case ThemeTypeSelector.HC_BLACK: return DEFAULT_BG_HC_BLACK; + case ThemeTypeSelector.HC_LIGHT: return DEFAULT_BG_HC_LIGHT; + default: return DEFAULT_BG_DARK; + } + } + + private getStoredBaseTheme(): ThemeTypeSelector { + const baseTheme = this.stateService.getItem(THEME_STORAGE_KEY, ThemeTypeSelector.VS_DARK).split(' ')[0]; + switch (baseTheme) { + case ThemeTypeSelector.VS: return ThemeTypeSelector.VS; + case ThemeTypeSelector.HC_BLACK: return ThemeTypeSelector.HC_BLACK; + case ThemeTypeSelector.HC_LIGHT: return ThemeTypeSelector.HC_LIGHT; + default: return ThemeTypeSelector.VS_DARK; + } + } + + saveWindowSplash(windowId: number | undefined, workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined, splash: IPartsSplash): void { + + // Update override as needed + const splashOverride = this.updateWindowSplashOverride(workspace, splash); + + // Update in storage + this.stateService.setItems(coalesce([ + { key: THEME_STORAGE_KEY, data: splash.baseTheme }, + { key: THEME_BG_STORAGE_KEY, data: splash.colorInfo.background }, + { key: THEME_WINDOW_SPLASH_KEY, data: splash }, + splashOverride ? { key: THEME_WINDOW_SPLASH_OVERRIDE_KEY, data: splashOverride } : undefined + ])); + + // Update in opened windows + if (typeof windowId === 'number') { + this.updateBackgroundColor(windowId, splash); + } + + // Update system theme + this.updateSystemColorTheme(); + } + + private updateWindowSplashOverride(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined, splash: IPartsSplash): IPartsSplashOverride | undefined { + let splashOverride: IPartsSplashOverride | undefined = undefined; + let changed = false; + if (workspace) { + splashOverride = { ...this.getWindowSplashOverride() }; // make a copy for modifications + + changed = this.doUpdateWindowSplashOverride(workspace, splash, splashOverride, 'sideBar'); + changed = this.doUpdateWindowSplashOverride(workspace, splash, splashOverride, 'auxiliaryBar') || changed; + } + + return changed ? splashOverride : undefined; + } + + private doUpdateWindowSplashOverride(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier, splash: IPartsSplash, splashOverride: IPartsSplashOverride, part: 'sideBar' | 'auxiliaryBar'): boolean { + const currentWidth = part === 'sideBar' ? splash.layoutInfo?.sideBarWidth : splash.layoutInfo?.auxiliarySideBarWidth; + const overrideWidth = part === 'sideBar' ? splashOverride.layoutInfo.sideBarWidth : splashOverride.layoutInfo.auxiliaryBarWidth; + + // No layout info: remove override + let changed = false; + if (typeof currentWidth !== 'number') { + if (splashOverride.layoutInfo.workspaces[workspace.id]) { + delete splashOverride.layoutInfo.workspaces[workspace.id]; + changed = true; + } + + return changed; + } + + let workspaceOverride = splashOverride.layoutInfo.workspaces[workspace.id]; + if (!workspaceOverride) { + const workspaceEntries = Object.keys(splashOverride.layoutInfo.workspaces); + if (workspaceEntries.length >= ThemeMainService.WORKSPACE_OVERRIDE_LIMIT) { + delete splashOverride.layoutInfo.workspaces[workspaceEntries[0]]; + changed = true; + } + + workspaceOverride = { sideBarVisible: false, auxiliaryBarVisible: false }; + splashOverride.layoutInfo.workspaces[workspace.id] = workspaceOverride; + changed = true; + } + + // Part has width: update width & visibility override + if (currentWidth > 0) { + if (overrideWidth !== currentWidth) { + splashOverride.layoutInfo[part === 'sideBar' ? 'sideBarWidth' : 'auxiliaryBarWidth'] = currentWidth; + changed = true; + } + + switch (part) { + case 'sideBar': + if (!workspaceOverride.sideBarVisible) { + workspaceOverride.sideBarVisible = true; + changed = true; + } + break; + case 'auxiliaryBar': + if (!workspaceOverride.auxiliaryBarVisible) { + workspaceOverride.auxiliaryBarVisible = true; + changed = true; + } + break; + } + } + + // Part is hidden: update visibility override + else { + switch (part) { + case 'sideBar': + if (workspaceOverride.sideBarVisible) { + workspaceOverride.sideBarVisible = false; + changed = true; + } + break; + case 'auxiliaryBar': + if (workspaceOverride.auxiliaryBarVisible) { + workspaceOverride.auxiliaryBarVisible = false; + changed = true; + } + break; + } + } + + return changed; + } + + private updateBackgroundColor(windowId: number, splash: IPartsSplash): void { + for (const window of getAllWindowsExcludingOffscreen()) { + if (window.id === windowId) { + window.setBackgroundColor(splash.colorInfo.background); + break; + } + } + } + + getWindowSplash(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined): IPartsSplash | undefined { + try { + return this.doGetWindowSplash(workspace); + } catch (error) { + this.logService.error('[theme main service] Failed to get window splash', error); + + return undefined; + } + } + + private doGetWindowSplash(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined): IPartsSplash | undefined { + const partSplash = this.stateService.getItem(THEME_WINDOW_SPLASH_KEY); + if (!partSplash?.layoutInfo) { + return partSplash; // return early: overrides currently only apply to layout info + } + + const override = this.getWindowSplashOverride(); + + // Figure out side bar width based on workspace and overrides + let sideBarWidth: number; + if (workspace) { + if (override.layoutInfo.workspaces[workspace.id]?.sideBarVisible === false) { + sideBarWidth = 0; + } else { + sideBarWidth = override.layoutInfo.sideBarWidth || partSplash.layoutInfo.sideBarWidth || ThemeMainService.DEFAULT_BAR_WIDTH; + } + } else { + sideBarWidth = 0; + } + + // Figure out auxiliary bar width based on workspace, configuration and overrides + const auxiliarySideBarDefaultVisibility = this.configurationService.getValue(AUXILIARYBAR_DEFAULT_VISIBILITY); + let auxiliarySideBarWidth: number; + if (workspace) { + const auxiliaryBarVisible = override.layoutInfo.workspaces[workspace.id]?.auxiliaryBarVisible; + if (auxiliaryBarVisible === true) { + auxiliarySideBarWidth = override.layoutInfo.auxiliaryBarWidth || partSplash.layoutInfo.auxiliarySideBarWidth || ThemeMainService.DEFAULT_BAR_WIDTH; + } else if (auxiliaryBarVisible === false) { + auxiliarySideBarWidth = 0; + } else { + if (auxiliarySideBarDefaultVisibility === 'visible' || auxiliarySideBarDefaultVisibility === 'visibleInWorkspace') { + auxiliarySideBarWidth = override.layoutInfo.auxiliaryBarWidth || partSplash.layoutInfo.auxiliarySideBarWidth || ThemeMainService.DEFAULT_BAR_WIDTH; + } else { + auxiliarySideBarWidth = 0; + } + } + } else { + auxiliarySideBarWidth = 0; // technically not true if configured 'visible', but we never store splash per empty window, so we decide on a default here + } + + return { + ...partSplash, + layoutInfo: { + ...partSplash.layoutInfo, + sideBarWidth, + auxiliarySideBarWidth + } + }; + } + + private getWindowSplashOverride(): IPartsSplashOverride { + let override = this.stateService.getItem(THEME_WINDOW_SPLASH_OVERRIDE_KEY); + + if (!override?.layoutInfo) { + override = { + layoutInfo: { + sideBarWidth: ThemeMainService.DEFAULT_BAR_WIDTH, + auxiliaryBarWidth: ThemeMainService.DEFAULT_BAR_WIDTH, + workspaces: {} + } + }; + } + + if (!override.layoutInfo.sideBarWidth) { + override.layoutInfo.sideBarWidth = ThemeMainService.DEFAULT_BAR_WIDTH; + } + + if (!override.layoutInfo.auxiliaryBarWidth) { + override.layoutInfo.auxiliaryBarWidth = ThemeMainService.DEFAULT_BAR_WIDTH; + } + + if (!override.layoutInfo.workspaces) { + override.layoutInfo.workspaces = {}; + } + + return override; + } +} diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index a43107d7502..557a2376556 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -12,6 +12,7 @@ import { revive } from '../../../base/common/marshalling.js'; import { escapeRegExpCharacters } from '../../../base/common/strings.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; +import { Schemas } from '../../../base/common/network.js'; import { Position } from '../../../editor/common/core/position.js'; import { Range } from '../../../editor/common/core/range.js'; import { getWordAtText } from '../../../editor/common/core/wordHelper.js'; @@ -23,7 +24,6 @@ import { IInstantiationService } from '../../../platform/instantiation/common/in import { ILogService } from '../../../platform/log/common/log.js'; import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIdentity.js'; import { IChatWidgetService } from '../../contrib/chat/browser/chat.js'; -import { ChatInputPart } from '../../contrib/chat/browser/chatInputPart.js'; import { AddDynamicVariableAction, IAddDynamicVariableContext } from '../../contrib/chat/browser/contrib/chatDynamicVariables.js'; import { IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentService } from '../../contrib/chat/common/chatAgents.js'; import { IChatEditingService, IChatRelatedFileProviderMetadata } from '../../contrib/chat/common/chatEditingService.js'; @@ -313,7 +313,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA }; this._agentIdsToCompletionProviders.set(id, this._chatAgentService.registerAgentCompletionProvider(id, provide)); - this._agentCompletionProviders.set(handle, this._languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._agentCompletionProviders.set(handle, this._languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'chatAgentCompletions:' + handle, triggerCharacters, provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 97768fd239b..02f78542290 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -40,7 +40,7 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from '../../common/editor.js'; import { IViewBadge } from '../../common/views.js'; import { IChatAgentRequest, IChatAgentResult } from '../../contrib/chat/common/chatAgents.js'; import { IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; -import { IChatRequestVariableEntry, isImageVariableEntry } from '../../contrib/chat/common/chatModel.js'; +import { IChatRequestVariableEntry, isImageVariableEntry } from '../../contrib/chat/common/chatVariableEntries.js'; import { IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatMoveMessage, IChatPrepareToolInvocationPart, IChatProgressMessage, IChatResponseCodeblockUriPart, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from '../../contrib/chat/common/chatService.js'; import { IToolData, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; import * as chatProvider from '../../contrib/chat/common/languageModels.js'; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index e8c46616b85..d96be5941b6 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -59,7 +59,7 @@ import { CopilotUsageExtensionFeatureId } from '../../common/languageModelStats. import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; import { ChatViewId, IChatWidget, IChatWidgetService, showChatView, showCopilotView } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; -import { ChatEditorInput } from '../chatEditorInput.js'; +import { ChatEditorInput, shouldShowClearEditingSessionConfirmation, showClearEditingSessionConfirmation } from '../chatEditorInput.js'; import { ChatViewPane } from '../chatViewPane.js'; import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; import { clearChatEditor } from './chatClear.js'; @@ -990,52 +990,6 @@ export interface IClearEditingSessionConfirmationOptions { messageOverride?: string; } -export async function showClearEditingSessionConfirmation(editingSession: IChatEditingSession, dialogService: IDialogService, options?: IClearEditingSessionConfirmationOptions): Promise { - const defaultPhrase = localize('chat.startEditing.confirmation.pending.message.default', "Starting a new chat will end your current edit session."); - const defaultTitle = localize('chat.startEditing.confirmation.title', "Start new chat?"); - const phrase = options?.messageOverride ?? defaultPhrase; - const title = options?.titleOverride ?? defaultTitle; - - const currentEdits = editingSession.entries.get(); - const undecidedEdits = currentEdits.filter((edit) => edit.state.get() === ModifiedFileEntryState.Modified); - - const { result } = await dialogService.prompt({ - title, - message: phrase + ' ' + localize('chat.startEditing.confirmation.pending.message.2', "Do you want to keep pending edits to {0} files?", undecidedEdits.length), - type: 'info', - cancelButton: true, - buttons: [ - { - label: localize('chat.startEditing.confirmation.acceptEdits', "Keep & Continue"), - run: async () => { - await editingSession.accept(); - return true; - } - }, - { - label: localize('chat.startEditing.confirmation.discardEdits', "Undo & Continue"), - run: async () => { - await editingSession.reject(); - return true; - } - } - ], - }); - - return Boolean(result); -} - -export function shouldShowClearEditingSessionConfirmation(editingSession: IChatEditingSession): boolean { - const currentEdits = editingSession.entries.get(); - const currentEditCount = currentEdits.length; - - if (currentEditCount) { - const undecidedEdits = currentEdits.filter((edit) => edit.state.get() === ModifiedFileEntryState.Modified); - return !!undecidedEdits.length; - } - - return false; -} // --- Chat Submenus in various Components diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts index 24391e9596d..1e32fa1041a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts @@ -23,7 +23,7 @@ import { FileEditorInput } from '../../../files/browser/editors/fileEditorInput. import { NotebookEditorInput } from '../../../notebook/common/notebookEditorInput.js'; import { IChatContextPickService, IChatContextValueItem, IChatContextPickerItem, IChatContextPickerPickItem } from '../chatContextPickService.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; -import { IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IImageVariableEntry, OmittedState } from '../../common/chatModel.js'; +import { IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IImageVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; import { IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; import { IChatWidget } from '../chat.js'; import { imageToHash, isImage } from '../chatPasteProviders.js'; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index ec16a17ef53..8100a6e6cfd 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -37,7 +37,7 @@ import { isSearchTreeFileMatch, isSearchTreeMatch } from '../../../search/browse import { ISymbolQuickPickItem, SymbolsQuickAccessProvider } from '../../../search/browser/symbolsQuickAccess.js'; import { SearchContext } from '../../../search/common/constants.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { IChatRequestVariableEntry, OmittedState } from '../../common/chatModel.js'; +import { IChatRequestVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; import { ChatAgentLocation } from '../../common/constants.js'; import { IChatWidget, IChatWidgetService, IQuickChatService, showChatView } from '../chat.js'; import { IChatContextPickerItem, IChatContextPickService, IChatContextValueItem, isChatContextPickerPickItem } from '../chatContextPickService.js'; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts index 8206f1c2585..112327bde07 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts @@ -17,10 +17,10 @@ import { ServicesAccessor } from '../../../../../platform/instantiation/common/i import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../../platform/quickinput/common/quickInput.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js'; -import { AddConfigurationAction } from '../../../mcp/browser/mcpCommands.js'; +import { McpCommandIds } from '../../../mcp/common/mcpCommandIds.js'; import { IMcpRegistry } from '../../../mcp/common/mcpRegistryTypes.js'; import { IMcpServer, IMcpService, McpConnectionState } from '../../../mcp/common/mcpTypes.js'; -import { IToolData, ToolSet, ToolDataSource, ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; +import { ILanguageModelToolsService, IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; import { ConfigureToolSets } from '../tools/toolSetsContribution.js'; @@ -86,7 +86,7 @@ export async function showToolsPicker( picked: false, }; - const addMcpPick: CallbackPick = { type: 'item', label: localize('addServer', "Add MCP Server..."), iconClass: ThemeIcon.asClassName(Codicon.add), pickable: false, run: () => commandService.executeCommand(AddConfigurationAction.ID) }; + const addMcpPick: CallbackPick = { type: 'item', label: localize('addServer', "Add MCP Server..."), iconClass: ThemeIcon.asClassName(Codicon.add), pickable: false, run: () => commandService.executeCommand(McpCommandIds.AddConfiguration) }; const configureToolSetsPick: CallbackPick = { type: 'item', label: localize('configToolSet', "Configure Tool Sets..."), iconClass: ThemeIcon.asClassName(Codicon.gear), pickable: false, run: () => commandService.executeCommand(ConfigureToolSets.ID) }; const addExpPick: CallbackPick = { type: 'item', label: localize('addExtension', "Install Extension..."), iconClass: ThemeIcon.asClassName(Codicon.add), pickable: false, run: () => extensionWorkbenchService.openSearch('@tag:language-model-tools') }; const addPick: CallbackPick = { diff --git a/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts b/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts index e23f62baf59..c4e5e14c583 100644 --- a/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts +++ b/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts @@ -24,7 +24,7 @@ import { FileKind, IFileService } from '../../../../../platform/files/common/fil import { ILabelService } from '../../../../../platform/label/common/label.js'; import { ResourceLabels } from '../../../../browser/labels.js'; import { ResourceContextKey } from '../../../../common/contextkeys.js'; -import { IChatRequestImplicitVariableEntry } from '../../common/chatModel.js'; +import { IChatRequestImplicitVariableEntry } from '../../common/chatVariableEntries.js'; import { IChatWidgetService } from '../chat.js'; import { ChatAttachmentModel } from '../chatAttachmentModel.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index f9d9e7c8a63..0a9390b5740 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -96,7 +96,7 @@ import { ChatResponseAccessibleView } from './chatResponseAccessibleView.js'; import { ChatSetupContribution } from './chatSetup.js'; import { ChatStatusBarEntry } from './chatStatus.js'; import { ChatVariablesService } from './chatVariables.js'; -import { ChatWidgetService } from './chatWidget.js'; +import { ChatWidget, ChatWidgetService } from './chatWidget.js'; import { ChatCodeBlockContextProviderService } from './codeBlockContextProviderService.js'; import { ChatImplicitContextContribution } from './contrib/chatImplicitContext.js'; import './contrib/chatInputCompletions.js'; @@ -110,6 +110,7 @@ import product from '../../../../platform/product/common/product.js'; import { ChatModeService, IChatModeService } from '../common/chatModes.js'; import { ChatResponseResourceFileSystemProvider } from '../common/chatResponseResourceFileSystemProvider.js'; import { runSaveToPromptAction, SAVE_TO_PROMPT_SLASH_COMMAND_NAME } from './promptSyntax/saveToPromptAction.js'; +import { ChatDynamicVariableModel } from './contrib/chatDynamicVariables.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -762,3 +763,5 @@ registerPromptFileContributions(); registerWorkbenchContribution2(UserToolSetsContributions.ID, UserToolSetsContributions, WorkbenchPhase.Eventually); registerAction2(ConfigureToolSets); + +ChatWidget.CONTRIBS.push(ChatDynamicVariableModel); diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts index abec2fdf5eb..4ee4a59ea99 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts @@ -8,7 +8,7 @@ import { Emitter } from '../../../../base/common/event.js'; import { basename } from '../../../../base/common/resources.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { IChatRequestFileEntry, IChatRequestVariableEntry } from '../common/chatModel.js'; +import { IChatRequestFileEntry, IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ChatPromptAttachmentsCollection } from './chatAttachmentModel/chatPromptAttachmentsCollection.js'; import { IFileService } from '../../../../platform/files/common/files.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachmentsCollection.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachmentsCollection.ts index bab83dc9130..1732ee9508a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachmentsCollection.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachmentsCollection.ts @@ -3,16 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from '../../../../../base/common/uri.js'; import { Emitter } from '../../../../../base/common/event.js'; +import { Disposable, DisposableMap } from '../../../../../base/common/lifecycle.js'; import { basename, isEqual } from '../../../../../base/common/resources.js'; -import { ChatPromptAttachmentModel } from './chatPromptAttachmentModel.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IChatRequestVariableEntry, IPromptVariableEntry, isChatRequestFileEntry } from '../../common/chatVariableEntries.js'; import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; import { IPromptFileReference } from '../../common/promptSyntax/parsers/types.js'; -import { Disposable, DisposableMap } from '../../../../../base/common/lifecycle.js'; -import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { IChatRequestVariableEntry, IPromptVariableEntry, isChatRequestFileEntry } from '../../common/chatModel.js'; +import { ChatPromptAttachmentModel } from './chatPromptAttachmentModel.js'; /** * Prefix for all prompt instruction variable IDs. diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentResolve.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentResolve.ts index 87338b0e95c..934e20d3c5e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentResolve.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentResolve.ts @@ -24,7 +24,8 @@ import { UntitledTextEditorInput } from '../../../services/untitled/common/untit import { createNotebookOutputVariableEntry, NOTEBOOK_CELL_OUTPUT_MIME_TYPE_LIST_FOR_CHAT_CONST } from '../../notebook/browser/contrib/chat/notebookChatUtils.js'; import { getOutputViewModelFromId } from '../../notebook/browser/controller/cellOutputActions.js'; import { getNotebookEditorFromEditorPane } from '../../notebook/browser/notebookBrowser.js'; -import { CHAT_ATTACHABLE_IMAGE_MIME_TYPES, getAttachableImageExtension, IChatRequestVariableEntry, IDiagnosticVariableEntry, IDiagnosticVariableEntryFilterData, ISymbolVariableEntry, OmittedState } from '../common/chatModel.js'; +import { CHAT_ATTACHABLE_IMAGE_MIME_TYPES, getAttachableImageExtension } from '../common/chatModel.js'; +import { IChatRequestVariableEntry, OmittedState, IDiagnosticVariableEntry, IDiagnosticVariableEntryFilterData, ISymbolVariableEntry } from '../common/chatVariableEntries.js'; import { imageToHash } from './chatPasteProviders.js'; import { resizeImage } from './imageUtils.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index 2eb7abd3f61..eb6be7eaaf0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -25,12 +25,11 @@ import { IOpenerService, OpenInternalOptions } from '../../../../platform/opener import { IThemeService, FolderThemeIcon } from '../../../../platform/theme/common/themeService.js'; import { IResourceLabel, ResourceLabels, IFileLabelOptions } from '../../../browser/labels.js'; import { revealInSideBarCommand } from '../../files/browser/fileActions.contribution.js'; -import { IChatRequestPasteVariableEntry, IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, ISCMHistoryItemVariableEntry, OmittedState } from '../common/chatModel.js'; +import { IChatRequestPasteVariableEntry, IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, ISCMHistoryItemVariableEntry, OmittedState } from '../common/chatVariableEntries.js'; import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js'; -import { chatAttachmentResourceContextKey } from './chatContentParts/chatAttachmentsContentPart.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; import { basename, dirname } from '../../../../base/common/path.js'; -import { IContextKey, IContextKeyService, IScopedContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextKey, IContextKeyService, IScopedContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { INotebookService } from '../../notebook/common/notebookService.js'; @@ -815,3 +814,5 @@ function addBasicContextMenu(accessor: ServicesAccessor, widget: HTMLElement, sc }); }); } + +export const chatAttachmentResourceContextKey = new RawContextKey('chatAttachmentResource', undefined, { type: 'URI', description: localize('resource', "The full value of the chat attachment resource, including scheme and path") }); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts index ff5fcc3eda6..3482a2296c2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts @@ -10,17 +10,12 @@ import { Disposable, DisposableStore } from '../../../../../base/common/lifecycl import { basename } from '../../../../../base/common/path.js'; import { URI } from '../../../../../base/common/uri.js'; import { Range } from '../../../../../editor/common/core/range.js'; -import { localize } from '../../../../../nls.js'; -import { RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ResourceLabels } from '../../../../browser/labels.js'; -import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isSCMHistoryItemVariableEntry, OmittedState } from '../../common/chatModel.js'; +import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isSCMHistoryItemVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../common/chatService.js'; import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, SCMHistoryItemAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../chatAttachmentWidgets.js'; -export const chatAttachmentResourceContextKey = new RawContextKey('chatAttachmentResource', undefined, { type: 'URI', description: localize('resource', "The full value of the chat attachment resource, including scheme and path") }); - - export class ChatAttachmentsContentPart extends Disposable { private readonly attachedContextDisposables = this._register(new DisposableStore()); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollapsibleContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollapsibleContentPart.ts index 976b3b74674..d0c6a600bad 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollapsibleContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollapsibleContentPart.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { $ } from '../../../../../base/browser/dom.js'; import { ButtonWithIcon } from '../../../../../base/browser/ui/button/button.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter } from '../../../../../base/common/event.js'; @@ -13,7 +14,6 @@ import { localize } from '../../../../../nls.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; import { ChatTreeItem } from '../chat.js'; import { IChatContentPart, IChatContentPartRenderContext } from './chatContentParts.js'; -import { $ } from './chatReferencesContentPart.js'; export abstract class ChatCollapsibleContentPart extends Disposable implements IChatContentPart { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts index 282f80349a8..83ec99a7af8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts @@ -46,7 +46,7 @@ import { ChatCollapsibleContentPart } from './chatCollapsibleContentPart.js'; import { IDisposableReference, ResourcePool } from './chatCollections.js'; import { IChatContentPartRenderContext } from './chatContentParts.js'; -export const $ = dom.$; +const $ = dom.$; export interface IChatReferenceListItem extends IChatContentReference { title?: string; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts index ffc765e7940..873796c8a59 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts @@ -31,7 +31,8 @@ import { INotificationService } from '../../../../../platform/notification/commo import { IProgressService, ProgressLocation } from '../../../../../platform/progress/common/progress.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { REVEAL_IN_EXPLORER_COMMAND_ID } from '../../../files/browser/fileConstants.js'; -import { getAttachableImageExtension, IChatRequestVariableEntry } from '../../common/chatModel.js'; +import { getAttachableImageExtension } from '../../common/chatModel.js'; +import { IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; import { ChatTreeItem, IChatCodeBlockInfo } from '../chat.js'; import { CodeBlockPart, ICodeBlockData, ICodeBlockRenderOptions } from '../codeBlockPart.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContextPickService.ts b/src/vs/workbench/contrib/chat/browser/chatContextPickService.ts index 9220bee6f48..fc478c46c38 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContextPickService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContextPickService.ts @@ -10,7 +10,7 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { isObject } from '../../../../base/common/types.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js'; -import { IChatRequestVariableEntry } from '../common/chatModel.js'; +import { IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; import { IChatWidget } from './chat.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts b/src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts index e3f054946e5..bd7d0d9a249 100644 --- a/src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts +++ b/src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts @@ -23,7 +23,7 @@ import { IThemeService, Themable } from '../../../../platform/theme/common/theme import { ISharedWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; -import { IChatRequestVariableEntry } from '../common/chatModel.js'; +import { IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; import { IChatWidgetService } from './chat.js'; import { ImageTransferData, resolveEditorAttachContext, resolveImageAttachContext, resolveMarkerAttachContext, resolveNotebookOutputAttachContext, resolveSymbolsAttachContext } from './chatAttachmentResolve.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatEdinputInputContentProvider.ts b/src/vs/workbench/contrib/chat/browser/chatEdinputInputContentProvider.ts index cbc084b3b82..d114f65a86f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEdinputInputContentProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEdinputInputContentProvider.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from '../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../base/common/network.js'; import { URI } from '../../../../base/common/uri.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { ITextModel } from '../../../../editor/common/model.js'; import { IModelService } from '../../../../editor/common/services/model.js'; import { ITextModelContentProvider, ITextModelService } from '../../../../editor/common/services/resolverService.js'; -import { ChatInputPart } from './chatInputPart.js'; export class ChatInputBoxContentProvider extends Disposable implements ITextModelContentProvider { @@ -19,7 +19,7 @@ export class ChatInputBoxContentProvider extends Disposable implements ITextMode @ILanguageService private readonly languageService: ILanguageService ) { super(); - this._register(textModelService.registerTextModelContentProvider(ChatInputPart.INPUT_SCHEME, this)); + this._register(textModelService.registerTextModelContentProvider(Schemas.vscodeChatInput, this)); } async provideTextContent(resource: URI): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts index 39bfb47d8c2..a42e9b3ce6f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts @@ -41,11 +41,11 @@ import { IEditorPane, SaveReason } from '../../../../common/editor.js'; import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js'; import { ITextFileService, isTextFileEditorModel, stringToSnapshot } from '../../../../services/textfile/common/textfiles.js'; import { ICellEditOperation } from '../../../notebook/common/notebookCommon.js'; -import { ChatEditKind, IModifiedFileEntry, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { ChatEditKind, IModifiedEntryTelemetryInfo, IModifiedFileEntry, IModifiedFileEntryEditorIntegration, ISnapshotEntry, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { ChatEditingCodeEditorIntegration, IDocumentDiff2 } from './chatEditingCodeEditorIntegration.js'; -import { AbstractChatEditingModifiedFileEntry, IModifiedEntryTelemetryInfo, ISnapshotEntry, pendingRewriteMinimap } from './chatEditingModifiedFileEntry.js'; +import { AbstractChatEditingModifiedFileEntry, pendingRewriteMinimap } from './chatEditingModifiedFileEntry.js'; import { ChatEditingSnapshotTextModelContentProvider, ChatEditingTextModelContentProvider } from './chatEditingTextModelContentProviders.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts index 08328ef44d4..ac4474ae213 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts @@ -10,7 +10,6 @@ import { Schemas } from '../../../../../base/common/network.js'; import { clamp } from '../../../../../base/common/numbers.js'; import { autorun, derived, IObservable, ITransaction, observableValue, observableValueOpts, transaction } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; -import { StringEdit } from '../../../../../editor/common/core/edits/stringEdit.js'; import { TextEdit } from '../../../../../editor/common/languages.js'; import { localize } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; @@ -22,8 +21,7 @@ import { IUndoRedoElement, IUndoRedoService } from '../../../../../platform/undo import { IEditorPane } from '../../../../common/editor.js'; import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js'; import { ICellEditOperation } from '../../../notebook/common/notebookCommon.js'; -import { IChatAgentResult } from '../../common/chatAgents.js'; -import { ChatEditKind, IModifiedFileEntry, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { ChatEditKind, IModifiedEntryTelemetryInfo, IModifiedFileEntry, IModifiedFileEntryEditorIntegration, ISnapshotEntry, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; @@ -318,22 +316,3 @@ export abstract class AbstractChatEditingModifiedFileEntry extends Disposable im abstract initialContent: string; } - -export interface IModifiedEntryTelemetryInfo { - readonly agentId: string | undefined; - readonly command: string | undefined; - readonly sessionId: string; - readonly requestId: string; - readonly result: IChatAgentResult | undefined; -} - -export interface ISnapshotEntry { - readonly resource: URI; - readonly languageId: string; - readonly snapshotUri: URI; - readonly original: string; - readonly current: string; - readonly originalToCurrentEdit: StringEdit; - readonly state: ModifiedFileEntryState; - telemetryInfo: IModifiedEntryTelemetryInfo; -} diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts index cc23b401827..d5616bc8221 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts @@ -43,10 +43,10 @@ import { INotebookEditorModelResolverService } from '../../../notebook/common/no import { INotebookLoggingService } from '../../../notebook/common/notebookLoggingService.js'; import { INotebookService } from '../../../notebook/common/notebookService.js'; import { INotebookEditorWorkerService } from '../../../notebook/common/services/notebookWorkerService.js'; -import { ChatEditKind, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { ChatEditKind, IModifiedEntryTelemetryInfo, IModifiedFileEntryEditorIntegration, ISnapshotEntry, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; -import { AbstractChatEditingModifiedFileEntry, IModifiedEntryTelemetryInfo, ISnapshotEntry } from './chatEditingModifiedFileEntry.js'; +import { AbstractChatEditingModifiedFileEntry } from './chatEditingModifiedFileEntry.js'; import { createSnapshot, deserializeSnapshot, getNotebookSnapshotFileURI, restoreSnapshot, SnapshotComparer } from './notebook/chatEditingModifiedNotebookSnapshot.js'; import { ChatEditingNewNotebookContentEdits } from './notebook/chatEditingNewNotebookContentEdits.js'; import { ChatEditingNotebookCellEntry } from './notebook/chatEditingNotebookCellEntry.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index c41d8026148..422c4045c42 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -35,11 +35,11 @@ import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEdito import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js'; import { CellUri, ICellEditOperation } from '../../../notebook/common/notebookCommon.js'; import { INotebookService } from '../../../notebook/common/notebookService.js'; -import { ChatEditingSessionState, ChatEditKind, getMultiDiffSourceUri, IChatEditingSession, IEditSessionEntryDiff, IModifiedFileEntry, IStreamingEdits, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { ChatEditingSessionState, ChatEditKind, getMultiDiffSourceUri, IChatEditingSession, IEditSessionEntryDiff, IModifiedEntryTelemetryInfo, IModifiedFileEntry, ISnapshotEntry, IStreamingEdits, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatRequestDisablement, IChatResponseModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { ChatEditingModifiedDocumentEntry } from './chatEditingModifiedDocumentEntry.js'; -import { AbstractChatEditingModifiedFileEntry, IModifiedEntryTelemetryInfo, ISnapshotEntry } from './chatEditingModifiedFileEntry.js'; +import { AbstractChatEditingModifiedFileEntry } from './chatEditingModifiedFileEntry.js'; import { ChatEditingModifiedNotebookEntry } from './chatEditingModifiedNotebookEntry.js'; import { ChatEditingSessionStorage, IChatEditingSessionSnapshot, IChatEditingSessionStop, StoredSessionState } from './chatEditingSessionStorage.js'; import { ChatEditingTextModelContentProvider } from './chatEditingTextModelContentProviders.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts index b44fc546d1f..93b386d4fb4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts @@ -13,8 +13,7 @@ import { IEnvironmentService } from '../../../../../platform/environment/common/ import { IFileService } from '../../../../../platform/files/common/files.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; -import { ISnapshotEntry } from './chatEditingModifiedFileEntry.js'; -import { WorkingSetDisplayMetadata, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { WorkingSetDisplayMetadata, ModifiedFileEntryState, ISnapshotEntry } from '../../common/chatEditingService.js'; const STORAGE_CONTENTS_FOLDER = 'contents'; const STORAGE_STATE_FILE = 'state.json'; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts index 20b3d767623..a2654b956bc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts @@ -8,7 +8,6 @@ import { ITextModel } from '../../../../../editor/common/model.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { ITextModelContentProvider } from '../../../../../editor/common/services/resolverService.js'; import { chatEditingSnapshotScheme, IChatEditingService } from '../../common/chatEditingService.js'; -import { ChatEditingSession } from './chatEditingSession.js'; type ChatEditingTextModelContentQueryData = { kind: 'doc'; documentId: string; chatSessionId: string }; @@ -72,7 +71,7 @@ export class ChatEditingSnapshotTextModelContentProvider implements ITextModelCo const data: ChatEditingSnapshotTextModelContentQueryData = JSON.parse(resource.query); const session = this._chatEditingService.getEditingSession(data.sessionId); - if (!(session instanceof ChatEditingSession) || !data.requestId) { + if (!session || !data.requestId) { return null; } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookDiff.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookDiff.ts index 5521a153318..4d7726defd0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookDiff.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookDiff.ts @@ -8,8 +8,8 @@ import { computeDiff } from '../../../../notebook/common/notebookDiff.js'; import { INotebookEditorModelResolverService } from '../../../../notebook/common/notebookEditorModelResolverService.js'; import { INotebookLoggingService } from '../../../../notebook/common/notebookLoggingService.js'; import { INotebookEditorWorkerService } from '../../../../notebook/common/services/notebookWorkerService.js'; -import { IEditSessionEntryDiff } from '../../../common/chatEditingService.js'; -import { ISnapshotEntry } from '../chatEditingModifiedFileEntry.js'; +import { IEditSessionEntryDiff, ISnapshotEntry } from '../../../common/chatEditingService.js'; + export class ChatEditingModifiedNotebookDiff { static NewModelCounter: number = 0; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts index b79dd102d43..885797824de 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts @@ -16,7 +16,6 @@ import { IWorkbenchContribution } from '../../../../../common/contributions.js'; import { INotebookService } from '../../../../notebook/common/notebookService.js'; import { IChatEditingService } from '../../../common/chatEditingService.js'; import { ChatEditingNotebookSnapshotScheme, deserializeSnapshot } from './chatEditingModifiedNotebookSnapshot.js'; -import { ChatEditingSession } from '../chatEditingSession.js'; export class ChatEditingNotebookFileSystemProviderContrib extends Disposable implements IWorkbenchContribution { @@ -89,7 +88,7 @@ export class ChatEditingNotebookFileSystemProvider implements IFileSystemProvide throw new Error('File not found, viewType not found'); } const session = this._chatEditingService.getEditingSession(queryData.sessionId); - if (!(session instanceof ChatEditingSession) || !queryData.requestId) { + if (!session || !queryData.requestId) { throw new Error('File not found, session not found'); } const snapshotEntry = session.getSnapshot(queryData.requestId, queryData.undoStop || undefined, resource); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts index 5ba3da96625..5015b2af65c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts @@ -31,7 +31,7 @@ import { IFileService } from '../../../../../platform/files/common/files.js'; import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js'; import { URI } from '../../../../../base/common/uri.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; -import { IChatRequestVariableEntry } from '../../common/chatModel.js'; +import { IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; import { IPreferencesService } from '../../../../services/preferences/common/preferences.js'; import { IBrowserElementsService } from '../../../../services/browserElements/browser/browserElementsService.js'; import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 6dad621002c..e4ff9246db0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -20,7 +20,8 @@ import { IChatModel } from '../common/chatModel.js'; import { IChatService } from '../common/chatService.js'; import { ChatAgentLocation } from '../common/constants.js'; import { ConfirmResult, IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; -import { shouldShowClearEditingSessionConfirmation, showClearEditingSessionConfirmation } from './actions/chatActions.js'; +import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditingService.js'; +import { IClearEditingSessionConfirmationOptions } from './actions/chatActions.js'; const ChatEditorIcon = registerIcon('chat-editor-label-icon', Codicon.commentDiscussion, nls.localize('chatEditorLabelIcon', 'Icon of the chat editor label.')); @@ -231,3 +232,50 @@ export class ChatEditorInputSerializer implements IEditorSerializer { } } } + +export async function showClearEditingSessionConfirmation(editingSession: IChatEditingSession, dialogService: IDialogService, options?: IClearEditingSessionConfirmationOptions): Promise { + const defaultPhrase = nls.localize('chat.startEditing.confirmation.pending.message.default1', "Starting a new chat will end your current edit session."); + const defaultTitle = nls.localize('chat.startEditing.confirmation.title', "Start new chat?"); + const phrase = options?.messageOverride ?? defaultPhrase; + const title = options?.titleOverride ?? defaultTitle; + + const currentEdits = editingSession.entries.get(); + const undecidedEdits = currentEdits.filter((edit) => edit.state.get() === ModifiedFileEntryState.Modified); + + const { result } = await dialogService.prompt({ + title, + message: phrase + ' ' + nls.localize('chat.startEditing.confirmation.pending.message.2', "Do you want to keep pending edits to {0} files?", undecidedEdits.length), + type: 'info', + cancelButton: true, + buttons: [ + { + label: nls.localize('chat.startEditing.confirmation.acceptEdits', "Keep & Continue"), + run: async () => { + await editingSession.accept(); + return true; + } + }, + { + label: nls.localize('chat.startEditing.confirmation.discardEdits', "Undo & Continue"), + run: async () => { + await editingSession.reject(); + return true; + } + } + ], + }); + + return Boolean(result); +} + +export function shouldShowClearEditingSessionConfirmation(editingSession: IChatEditingSession): boolean { + const currentEdits = editingSession.entries.get(); + const currentEditCount = currentEdits.length; + + if (currentEditCount) { + const undecidedEdits = currentEdits.filter((edit) => edit.state.get() === ModifiedFileEntryState.Modified); + return !!undecidedEdits.length; + } + + return false; +} diff --git a/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts b/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts index 59f0cc16d5c..db18152a9be 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts @@ -41,8 +41,7 @@ import { ExplorerFolderContext } from '../../files/common/files.js'; import { IWorkspaceSymbol } from '../../search/common/search.js'; import { IChatContentInlineReference } from '../common/chatService.js'; import { IChatWidgetService } from './chat.js'; -import { hookUpSymbolAttachmentDragAndContextMenu } from './chatAttachmentWidgets.js'; -import { chatAttachmentResourceContextKey } from './chatContentParts/chatAttachmentsContentPart.js'; +import { chatAttachmentResourceContextKey, hookUpSymbolAttachmentDragAndContextMenu } from './chatAttachmentWidgets.js'; import { IChatMarkdownAnchorService } from './chatContentParts/chatMarkdownAnchorService.js'; type ContentRefData = diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index d93c620c611..c495c5cb178 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -27,6 +27,7 @@ import { isMacintosh } from '../../../../base/common/platform.js'; import { isEqual } from '../../../../base/common/resources.js'; import { assertType } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; +import { Schemas } from '../../../../base/common/network.js'; import { IEditorConstructionOptions } from '../../../../editor/browser/config/editorConfiguration.js'; import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; import { CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; @@ -75,7 +76,7 @@ import { IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatEditingSession } from '../common/chatEditingService.js'; import { ChatEntitlement, IChatEntitlementService } from '../common/chatEntitlementService.js'; -import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatModel.js'; +import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatVariableEntries.js'; import { ChatMode2, IChatMode, IChatModeService, isBuiltinChatMode, validateChatMode2 } from '../common/chatModes.js'; import { IChatFollowup } from '../common/chatService.js'; import { IChatVariablesService } from '../common/chatVariables.js'; @@ -137,7 +138,6 @@ export interface IWorkingSetEntry { const GlobalLastChatModeKey = 'chat.lastChatMode'; export class ChatInputPart extends Disposable implements IHistoryNavigationWidget { - static readonly INPUT_SCHEME = 'chatSessionInput'; private static _counter = 0; private _onDidLoadInputState: Emitter; @@ -427,7 +427,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._onDidChangeCurrentChatMode = this._register(new Emitter()); this.onDidChangeCurrentChatMode = this._onDidChangeCurrentChatMode.event; this._currentMode = ChatMode2.Ask; - this.inputUri = URI.parse(`${ChatInputPart.INPUT_SCHEME}:input-${ChatInputPart._counter++}`); + this.inputUri = URI.parse(`${Schemas.vscodeChatInput}:input-${ChatInputPart._counter++}`); this._chatEditsActionsDisposables = this._register(new DisposableStore()); this._chatEditsDisposables = this._register(new DisposableStore()); this._attemptedWorkingSetEntriesCount = 0; diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 68378263466..280a613236c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -47,7 +47,7 @@ import { annotateSpecialMarkdownContent } from '../common/annotations.js'; import { checkModeOption } from '../common/chat.js'; import { IChatAgentMetadata } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { IChatRequestVariableEntry, IChatTextEditGroup } from '../common/chatModel.js'; +import { IChatTextEditGroup } from '../common/chatModel.js'; import { chatSubcommandLeader } from '../common/chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatErrorLevel, IChatConfirmation, IChatContentReference, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatTask, IChatTaskSerialized, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop } from '../common/chatService.js'; import { IChatCodeCitations, IChatErrorDetailsPart, IChatReferences, IChatRendererContent, IChatRequestViewModel, IChatResponseViewModel, IChatWorkingProgress, isRequestVM, isResponseVM } from '../common/chatViewModel.js'; @@ -79,6 +79,7 @@ import { ChatMarkdownRenderer } from './chatMarkdownRenderer.js'; import { ChatEditorOptions } from './chatOptions.js'; import { ChatCodeBlockContentProvider, CodeBlockPart } from './codeBlockPart.js'; import { canceledName } from '../../../../base/common/errors.js'; +import { IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; const $ = dom.$; diff --git a/src/vs/workbench/contrib/chat/browser/chatPasteProviders.ts b/src/vs/workbench/contrib/chat/browser/chatPasteProviders.ts index 6bdd4a6067f..c06fc723962 100644 --- a/src/vs/workbench/contrib/chat/browser/chatPasteProviders.ts +++ b/src/vs/workbench/contrib/chat/browser/chatPasteProviders.ts @@ -9,6 +9,7 @@ import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { revive } from '../../../../base/common/marshalling.js'; import { Mimes } from '../../../../base/common/mime.js'; +import { Schemas } from '../../../../base/common/network.js'; import { basename, joinPath } from '../../../../base/common/resources.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; import { IRange } from '../../../../editor/common/core/range.js'; @@ -22,10 +23,9 @@ import { IFileService } from '../../../../platform/files/common/files.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; -import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry } from '../common/chatModel.js'; +import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; import { IChatVariablesService, IDynamicVariable } from '../common/chatVariables.js'; import { IChatWidgetService } from './chat.js'; -import { ChatInputPart } from './chatInputPart.js'; import { ChatDynamicVariableModel } from './contrib/chatDynamicVariables.js'; import { cleanupOldImages, createFileForMedia, resizeImage } from './imageUtils.js'; @@ -180,7 +180,7 @@ export class CopyTextProvider implements DocumentPasteEditProvider { public readonly pasteMimeTypes = []; async prepareDocumentPaste(model: ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { - if (model.uri.scheme === ChatInputPart.INPUT_SCHEME) { + if (model.uri.scheme === Schemas.vscodeChatInput) { return; } @@ -304,7 +304,7 @@ export class PasteTextProvider implements DocumentPasteEditProvider { ) { } async provideDocumentPasteEdits(model: ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, _context: DocumentPasteContext, token: CancellationToken): Promise { - if (model.uri.scheme !== ChatInputPart.INPUT_SCHEME) { + if (model.uri.scheme !== Schemas.vscodeChatInput) { return; } const text = dataTransfer.get(Mimes.text); @@ -442,9 +442,10 @@ export class ChatPasteProvidersFeature extends Disposable { @ILogService logService: ILogService, ) { super(); - this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, pattern: '*', hasAccessToAllModels: true }, instaService.createInstance(CopyAttachmentsProvider))); - this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, pattern: '*', hasAccessToAllModels: true }, new PasteImageProvider(chatWidgetService, extensionService, fileService, environmentService, logService))); - this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, pattern: '*', hasAccessToAllModels: true }, new PasteTextProvider(chatWidgetService, modelService))); + this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: Schemas.vscodeChatInput, pattern: '*', hasAccessToAllModels: true }, instaService.createInstance(CopyAttachmentsProvider))); + this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: Schemas.vscodeChatInput, pattern: '*', hasAccessToAllModels: true }, new PasteImageProvider(chatWidgetService, extensionService, fileService, environmentService, logService))); + this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: Schemas.vscodeChatInput, pattern: '*', hasAccessToAllModels: true }, new PasteTextProvider(chatWidgetService, modelService))); + this._register(languageFeaturesService.documentPasteEditProvider.register('*', new CopyTextProvider())); this._register(languageFeaturesService.documentPasteEditProvider.register('*', new CopyTextProvider())); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 0883840ff6f..1b504e9e195 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -58,7 +58,8 @@ import { IExtensionsWorkbenchService } from '../../extensions/common/extensions. import { IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, ChatEntitlementService, IChatEntitlementService, isProUser } from '../common/chatEntitlementService.js'; -import { ChatModel, ChatRequestModel, IChatRequestModel, IChatRequestToolEntry, IChatRequestVariableData } from '../common/chatModel.js'; +import { ChatModel, ChatRequestModel, IChatRequestModel, IChatRequestVariableData } from '../common/chatModel.js'; +import { IChatRequestToolEntry } from '../common/chatVariableEntries.js'; import { ChatRequestAgentPart, ChatRequestToolPart } from '../common/chatParserTypes.js'; import { IChatProgress, IChatService } from '../common/chatService.js'; import { ChatAgentLocation, ChatConfiguration, ChatMode, validateChatMode } from '../common/constants.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 727ae4db8e3..d24c33e180a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -43,7 +43,7 @@ import { checkModeOption } from '../common/chat.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentService, IChatWelcomeMessageContent } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { applyingChatEditsFailedContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, inChatEditingSessionContextKey, ModifiedFileEntryState } from '../common/chatEditingService.js'; -import { ChatPauseState, IChatModel, IChatRequestVariableEntry, IChatResponseModel } from '../common/chatModel.js'; +import { ChatPauseState, IChatModel, IChatResponseModel } from '../common/chatModel.js'; import { chatAgentLeader, ChatRequestAgentPart, ChatRequestDynamicVariablePart, ChatRequestSlashPromptPart, ChatRequestToolPart, ChatRequestToolSetPart, chatSubcommandLeader, formatChatQuestion, IParsedChatRequest } from '../common/chatParserTypes.js'; import { ChatRequestParser } from '../common/chatRequestParser.js'; import { IChatLocationData, IChatSendRequestOptions, IChatService } from '../common/chatService.js'; @@ -68,6 +68,7 @@ import './media/chatAgentHover.css'; import './media/chatViewWelcome.css'; import { ChatViewWelcomePart } from './viewsWelcome/chatViewWelcomeController.js'; import { MicrotaskDelay } from '../../../../base/common/symbols.js'; +import { IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; const $ = dom.$; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts index 3fafbb4cbc0..476653ffe4f 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts @@ -15,10 +15,10 @@ import { ICommandService } from '../../../../../platform/commands/common/command import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; -import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; import { IChatRequestVariableValue, IDynamicVariable } from '../../common/chatVariables.js'; +import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; import { IChatWidget } from '../chat.js'; -import { ChatWidget, IChatWidgetContrib } from '../chatWidget.js'; +import { IChatWidgetContrib } from '../chatWidget.js'; import { ChatFileReference } from './chatDynamicVariables/chatFileReference.js'; export const dynamicVariableDecorationType = 'chat-dynamic-variable'; @@ -221,9 +221,6 @@ function isDynamicVariable(obj: any): obj is IDynamicVariable { 'data' in obj; } -ChatWidget.CONTRIBS.push(ChatDynamicVariableModel); - - export interface IAddDynamicVariableContext { diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts index 15e36a441f6..1d570246e68 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts @@ -21,7 +21,7 @@ import { EditorsOrder } from '../../../../common/editor.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { getNotebookEditorFromEditorPane, INotebookEditor } from '../../../notebook/browser/notebookBrowser.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; -import { IChatRequestFileEntry, IChatRequestImplicitVariableEntry } from '../../common/chatModel.js'; +import { IChatRequestFileEntry, IChatRequestImplicitVariableEntry } from '../../common/chatVariableEntries.js'; import { IChatService } from '../../common/chatService.js'; import { ChatAgentLocation } from '../../common/constants.js'; import { ILanguageModelIgnoredFilesService } from '../../common/ignoredFiles.js'; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 04d8f730e70..13067fe7199 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -46,16 +46,16 @@ import { IMcpPrompt, IMcpPromptMessage, IMcpServer, IMcpService, McpResourceURI import { searchFilesAndFolders } from '../../../search/browser/chatContributions.js'; import { IChatAgentData, IChatAgentNameService, IChatAgentService, getFullyQualifiedId } from '../../common/chatAgents.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; -import { getAttachableImageExtension, IChatRequestVariableEntry } from '../../common/chatModel.js'; +import { getAttachableImageExtension } from '../../common/chatModel.js'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashPromptPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestToolSetPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from '../../common/chatParserTypes.js'; import { IChatSlashCommandService } from '../../common/chatSlashCommands.js'; +import { IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; import { IDynamicVariable } from '../../common/chatVariables.js'; import { ChatAgentLocation, ChatMode } from '../../common/constants.js'; import { ToolSet } from '../../common/languageModelToolsService.js'; import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; import { ChatSubmitAction } from '../actions/chatExecuteActions.js'; import { IChatWidget, IChatWidgetService } from '../chat.js'; -import { ChatInputPart } from '../chatInputPart.js'; import { ChatDynamicVariableModel } from './chatDynamicVariables.js'; class SlashCommandCompletions extends Disposable { @@ -68,7 +68,7 @@ class SlashCommandCompletions extends Disposable { ) { super(); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'globalSlashCommands', triggerCharacters: ['/'], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { @@ -115,7 +115,7 @@ class SlashCommandCompletions extends Disposable { }; } })); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'globalSlashCommandsAt', triggerCharacters: [chatAgentLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { @@ -156,7 +156,7 @@ class SlashCommandCompletions extends Disposable { }; } })); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'promptSlashCommands', triggerCharacters: ['/'], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { @@ -204,7 +204,7 @@ class SlashCommandCompletions extends Disposable { } })); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'mcpPromptSlashCommands', triggerCharacters: ['/'], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { @@ -306,9 +306,9 @@ class AgentCompletions extends Disposable { }; } }; - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, subCommandProvider)); + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, subCommandProvider)); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'chatAgentAndSubcommand', triggerCharacters: [chatAgentLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { @@ -398,7 +398,7 @@ class AgentCompletions extends Disposable { } })); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'chatAgentAndSubcommand', triggerCharacters: [chatSubcommandLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { @@ -456,7 +456,7 @@ class AgentCompletions extends Disposable { } })); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'installChatExtensions', triggerCharacters: [chatAgentLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { @@ -841,7 +841,7 @@ class BuiltinDynamicCompletions extends Disposable { } private registerVariableCompletions(debugName: string, provider: (details: IVariableCompletionsDetails, token: CancellationToken) => ProviderResult, wordPattern: RegExp = BuiltinDynamicCompletions.VariableNameDef) { - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: `chatVarCompletions-${debugName}`, triggerCharacters: [chatVariableLeader], provideCompletionItems: async (model: ITextModel, position: Position, context: CompletionContext, token: CancellationToken) => { @@ -1094,7 +1094,7 @@ class ToolCompletions extends Disposable { ) { super(); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'chatVariables', triggerCharacters: [chatVariableLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { diff --git a/src/vs/workbench/contrib/chat/browser/contrib/screenshot.ts b/src/vs/workbench/contrib/chat/browser/contrib/screenshot.ts index f030477d0df..8e18015fc20 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/screenshot.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/screenshot.ts @@ -5,7 +5,7 @@ import { VSBuffer } from '../../../../../base/common/buffer.js'; import { localize } from '../../../../../nls.js'; -import { IChatRequestVariableEntry } from '../../common/chatModel.js'; +import { IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; export const ScreenshotVariableId = 'screenshot-focused-window'; diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts index 17839752416..d7ec82126ac 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts @@ -24,7 +24,7 @@ import { INSTRUCTIONS_LANGUAGE_ID, PromptsType } from '../../common/promptSyntax import { compare } from '../../../../../base/common/strings.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; import { dirname } from '../../../../../base/common/resources.js'; -import { IPromptFileVariableEntry } from '../../common/chatModel.js'; +import { IPromptFileVariableEntry } from '../../common/chatVariableEntries.js'; import { KeyMod, KeyCode } from '../../../../../base/common/keyCodes.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index cf1f0161b1a..97a3f3a499f 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -8,12 +8,15 @@ import { Event } from '../../../../base/common/event.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; import { IObservable, IReader } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; +import { StringEdit } from '../../../../editor/common/core/edits/stringEdit.js'; import { TextEdit } from '../../../../editor/common/languages.js'; +import { ITextModel } from '../../../../editor/common/model.js'; import { localize } from '../../../../nls.js'; import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IEditorPane } from '../../../common/editor.js'; import { ICellEditOperation } from '../../notebook/common/notebookCommon.js'; +import { IChatAgentResult } from './chatAgents.js'; import { ChatModel, IChatResponseModel } from './chatModel.js'; export const IChatEditingService = createDecorator('chatEditingService'); @@ -79,6 +82,25 @@ export interface IStreamingEdits { export const chatEditingSnapshotScheme = 'chat-editing-snapshot-text-model'; +export interface IModifiedEntryTelemetryInfo { + readonly agentId: string | undefined; + readonly command: string | undefined; + readonly sessionId: string; + readonly requestId: string; + readonly result: IChatAgentResult | undefined; +} + +export interface ISnapshotEntry { + readonly resource: URI; + readonly languageId: string; + readonly snapshotUri: URI; + readonly original: string; + readonly current: string; + readonly originalToCurrentEdit: StringEdit; + readonly state: ModifiedFileEntryState; + telemetryInfo: IModifiedEntryTelemetryInfo; +} + export interface IChatEditingSession extends IDisposable { readonly isGlobalEditingSession: boolean; readonly chatSessionId: string; @@ -99,6 +121,10 @@ export interface IChatEditingSession extends IDisposable { */ getSnapshotUri(requestId: string, uri: URI, stopId: string | undefined): URI | undefined; + getSnapshotModel(requestId: string, undoStop: string | undefined, snapshotUri: URI): Promise; + + getSnapshot(requestId: string, undoStop: string | undefined, snapshotUri: URI): ISnapshotEntry | undefined; + /** * Will lead to this object getting disposed */ diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 1512b137bfc..4acb8302d77 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { asArray } from '../../../../base/common/arrays.js'; -import { Codicon } from '../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { IMarkdownString, MarkdownString, isMarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; @@ -18,265 +17,18 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI, UriComponents, UriDto, isUriComponents } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { IRange } from '../../../../editor/common/core/range.js'; -import { IOffsetRange, OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; -import { Location, SymbolKind, TextEdit, isLocation } from '../../../../editor/common/languages.js'; +import { OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; +import { TextEdit } from '../../../../editor/common/languages.js'; import { localize } from '../../../../nls.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { IMarker, MarkerSeverity } from '../../../../platform/markers/common/markers.js'; import { CellUri, ICellEditOperation } from '../../notebook/common/notebookCommon.js'; -import { ISCMHistoryItem } from '../../scm/common/history.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, reviveSerializedAgent } from './chatAgents.js'; import { IChatEditingService, IChatEditingSession } from './chatEditingService.js'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from './chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, isIUsedContext } from './chatService.js'; -import { IChatRequestVariableValue } from './chatVariables.js'; +import { IChatRequestVariableEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatMode } from './constants.js'; -interface IBaseChatRequestVariableEntry { - readonly id: string; - readonly fullName?: string; - readonly icon?: ThemeIcon; - readonly name: string; - readonly modelDescription?: string; - - /** - * The offset-range in the prompt. This means this entry has been explicitly typed out - * by the user. - */ - readonly range?: IOffsetRange; - readonly value: IChatRequestVariableValue; - readonly references?: IChatContentReference[]; - - omittedState?: OmittedState; -} - -export interface IGenericChatRequestVariableEntry extends IBaseChatRequestVariableEntry { - kind: 'generic'; -} - -export interface IChatRequestDirectoryEntry extends IBaseChatRequestVariableEntry { - kind: 'directory'; -} - -export interface IChatRequestFileEntry extends IBaseChatRequestVariableEntry { - kind: 'file'; -} - -export const enum OmittedState { - NotOmitted, - Partial, - Full, -} - -export interface IChatRequestToolEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'tool'; -} - -export interface IChatRequestToolSetEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'toolset'; - readonly value: IChatRequestToolEntry[]; -} - -export interface IChatRequestImplicitVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'implicit'; - readonly isFile: true; - readonly value: URI | Location | undefined; - readonly isSelection: boolean; - readonly isPromptFile: boolean; - readonly enabled: boolean; -} - -export interface IChatRequestPasteVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'paste'; - readonly code: string; - readonly language: string; - readonly pastedLines: string; - - // This is only used for old serialized data and should be removed once we no longer support it - readonly fileName: string; - - // This is only undefined on old serialized data - readonly copiedFrom: { - readonly uri: URI; - readonly range: IRange; - } | undefined; -} - -export interface ISymbolVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'symbol'; - readonly value: Location; - readonly symbolKind: SymbolKind; -} - -export interface ICommandResultVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'command'; -} - -export interface IImageVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'image'; - readonly isPasted?: boolean; - readonly isURL?: boolean; - readonly mimeType?: string; -} - -export interface INotebookOutputVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'notebookOutput'; - readonly outputIndex?: number; - readonly mimeType?: string; -} - -export interface IDiagnosticVariableEntryFilterData { - readonly owner?: string; - readonly problemMessage?: string; - readonly filterUri?: URI; - readonly filterSeverity?: MarkerSeverity; - readonly filterRange?: IRange; -} - -/** - * Chat variable that represents an attached prompt file. - */ -export interface IPromptVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'file'; - readonly value: URI | Location; - readonly isRoot: boolean; - readonly modelDescription: string; -} - -export namespace IDiagnosticVariableEntryFilterData { - export const icon = Codicon.error; - - export function fromMarker(marker: IMarker): IDiagnosticVariableEntryFilterData { - return { - filterUri: marker.resource, - owner: marker.owner, - problemMessage: marker.message, - filterRange: { startLineNumber: marker.startLineNumber, endLineNumber: marker.endLineNumber, startColumn: marker.startColumn, endColumn: marker.endColumn } - }; - } - - export function toEntry(data: IDiagnosticVariableEntryFilterData): IDiagnosticVariableEntry { - return { - id: id(data), - name: label(data), - icon, - value: data, - kind: 'diagnostic', - ...data, - }; - } - - export function id(data: IDiagnosticVariableEntryFilterData) { - return [data.filterUri, data.owner, data.filterSeverity, data.filterRange?.startLineNumber].join(':'); - } - - export function label(data: IDiagnosticVariableEntryFilterData) { - const enum TrimThreshold { - MaxChars = 30, - MaxSpaceLookback = 10, - } - if (data.problemMessage) { - if (data.problemMessage.length < TrimThreshold.MaxChars) { - return data.problemMessage; - } - - // Trim the message, on a space if it would not lose too much - // data (MaxSpaceLookback) or just blindly otherwise. - const lastSpace = data.problemMessage.lastIndexOf(' ', TrimThreshold.MaxChars); - if (lastSpace === -1 || lastSpace + TrimThreshold.MaxSpaceLookback < TrimThreshold.MaxChars) { - return data.problemMessage.substring(0, TrimThreshold.MaxChars) + '…'; - } - return data.problemMessage.substring(0, lastSpace) + '…'; - } - let labelStr = localize('chat.attachment.problems.all', "All Problems"); - if (data.filterUri) { - labelStr = localize('chat.attachment.problems.inFile', "Problems in {0}", basename(data.filterUri)); - } - - return labelStr; - } -} - -export interface IDiagnosticVariableEntry extends IBaseChatRequestVariableEntry, IDiagnosticVariableEntryFilterData { - readonly kind: 'diagnostic'; -} - -export interface IElementVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'element'; -} - -export interface IPromptFileVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'promptFile'; -} - -export interface ISCMHistoryItemVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'scmHistoryItem'; - readonly value: URI; - readonly historyItem: ISCMHistoryItem; -} - -export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry - | ISymbolVariableEntry | ICommandResultVariableEntry | IDiagnosticVariableEntry | IImageVariableEntry - | IChatRequestToolEntry | IChatRequestToolSetEntry - | IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry - | IPromptFileVariableEntry | ISCMHistoryItemVariableEntry; - - -export namespace IChatRequestVariableEntry { - - /** - * Returns URI of the passed variant entry. Return undefined if not found. - */ - export function toUri(entry: IChatRequestVariableEntry): URI | undefined { - return URI.isUri(entry.value) - ? entry.value - : isLocation(entry.value) - ? entry.value.uri - : undefined; - } -} - - -export function isImplicitVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestImplicitVariableEntry { - return obj.kind === 'implicit'; -} - -export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestPasteVariableEntry { - return obj.kind === 'paste'; -} - -export function isImageVariableEntry(obj: IChatRequestVariableEntry): obj is IImageVariableEntry { - return obj.kind === 'image'; -} - -export function isNotebookOutputVariableEntry(obj: IChatRequestVariableEntry): obj is INotebookOutputVariableEntry { - return obj.kind === 'notebookOutput'; -} - -export function isElementVariableEntry(obj: IChatRequestVariableEntry): obj is IElementVariableEntry { - return obj.kind === 'element'; -} - -export function isDiagnosticsVariableEntry(obj: IChatRequestVariableEntry): obj is IDiagnosticVariableEntry { - return obj.kind === 'diagnostic'; -} - -export function isChatRequestFileEntry(obj: IChatRequestVariableEntry): obj is IChatRequestFileEntry { - return obj.kind === 'file'; -} - -export function isChatRequestVariableEntry(obj: unknown): obj is IChatRequestVariableEntry { - const entry = obj as IChatRequestVariableEntry; - return typeof entry === 'object' && - entry !== null && - typeof entry.id === 'string' && - typeof entry.name === 'string'; -} - -export function isSCMHistoryItemVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemVariableEntry { - return obj.kind === 'scmHistoryItem'; -} - export const CHAT_ATTACHABLE_IMAGE_MIME_TYPES: Record = { png: 'image/png', diff --git a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index d53289147c7..fefa954edf1 100644 --- a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -8,12 +8,12 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { IOffsetRange, OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentService, reviveSerializedAgent } from './chatAgents.js'; -import { IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IDiagnosticVariableEntryFilterData } from './chatModel.js'; import { IChatSlashData } from './chatSlashCommands.js'; import { IChatRequestProblemsVariable, IChatRequestVariableValue } from './chatVariables.js'; import { ChatAgentLocation } from './constants.js'; import { IToolData } from './languageModelToolsService.js'; import { IChatPromptSlashCommand } from './promptSyntax/service/promptsService.js'; +import { IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IDiagnosticVariableEntryFilterData } from './chatVariableEntries.js'; // These are in a separate file to avoid circular dependencies with the dependencies of the parser diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 60c1f5d8684..fd1c29a7a04 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -18,9 +18,10 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { ICellEditOperation } from '../../notebook/common/notebookCommon.js'; import { IWorkspaceSymbol } from '../../search/common/search.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentResult } from './chatAgents.js'; -import { ChatModel, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatRequestVariableEntry, IChatResponseModel, IExportableChatData, ISerializableChatData } from './chatModel.js'; +import { ChatModel, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatResponseModel, IExportableChatData, ISerializableChatData } from './chatModel.js'; import { IParsedChatRequest } from './chatParserTypes.js'; import { IChatParserContext } from './chatRequestParser.js'; +import { IChatRequestVariableEntry } from './chatVariableEntries.js'; import { IChatRequestVariableValue } from './chatVariables.js'; import { ChatAgentLocation, ChatMode } from './constants.js'; import { IPreparedToolInvocation, IToolConfirmationMessages, IToolResult } from './languageModelToolsService.js'; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 3be67300df2..cec924742db 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -27,7 +27,7 @@ import { IWorkspaceContextService } from '../../../../platform/workspace/common/ import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IChatAgent, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from './chatAgents.js'; -import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatRequestVariableEntry, IChatResponseModel, IExportableChatData, ISerializableChatData, ISerializableChatDataIn, ISerializableChatsData, isImageVariableEntry, normalizeSerializableChatData, toChatHistoryContent, updateRanges } from './chatModel.js'; +import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatResponseModel, IExportableChatData, ISerializableChatData, ISerializableChatDataIn, ISerializableChatsData, normalizeSerializableChatData, toChatHistoryContent, updateRanges } from './chatModel.js'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, getPromptText } from './chatParserTypes.js'; import { ChatRequestParser } from './chatRequestParser.js'; import { IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatSendRequestData, IChatSendRequestOptions, IChatSendRequestResponseState, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from './chatService.js'; @@ -35,6 +35,7 @@ import { ChatServiceTelemetry } from './chatServiceTelemetry.js'; import { ChatSessionStore, IChatTransfer2 } from './chatSessionStore.js'; import { IChatSlashCommandService } from './chatSlashCommands.js'; import { IChatTransferService } from './chatTransferService.js'; +import { IChatRequestVariableEntry, isImageVariableEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatConfiguration, ChatMode } from './constants.js'; import { ChatMessageRole, IChatMessage, ILanguageModelsService } from './languageModels.js'; import { ILanguageModelToolsService } from './languageModelToolsService.js'; diff --git a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts new file mode 100644 index 00000000000..cd26353e890 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts @@ -0,0 +1,263 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from '../../../../base/common/codicons.js'; +import { basename } from '../../../../base/common/resources.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IRange } from '../../../../editor/common/core/range.js'; +import { IOffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; +import { isLocation, Location, SymbolKind } from '../../../../editor/common/languages.js'; +import { localize } from '../../../../nls.js'; +import { MarkerSeverity, IMarker } from '../../../../platform/markers/common/markers.js'; +import { ISCMHistoryItem } from '../../scm/common/history.js'; +import { IChatContentReference } from './chatService.js'; +import { IChatRequestVariableValue } from './chatVariables.js'; + + +interface IBaseChatRequestVariableEntry { + readonly id: string; + readonly fullName?: string; + readonly icon?: ThemeIcon; + readonly name: string; + readonly modelDescription?: string; + + /** + * The offset-range in the prompt. This means this entry has been explicitly typed out + * by the user. + */ + readonly range?: IOffsetRange; + readonly value: IChatRequestVariableValue; + readonly references?: IChatContentReference[]; + + omittedState?: OmittedState; +} + +export interface IGenericChatRequestVariableEntry extends IBaseChatRequestVariableEntry { + kind: 'generic'; +} + +export interface IChatRequestDirectoryEntry extends IBaseChatRequestVariableEntry { + kind: 'directory'; +} + +export interface IChatRequestFileEntry extends IBaseChatRequestVariableEntry { + kind: 'file'; +} + +export const enum OmittedState { + NotOmitted, + Partial, + Full, +} + +export interface IChatRequestToolEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'tool'; +} + +export interface IChatRequestToolSetEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'toolset'; + readonly value: IChatRequestToolEntry[]; +} + +export interface IChatRequestImplicitVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'implicit'; + readonly isFile: true; + readonly value: URI | Location | undefined; + readonly isSelection: boolean; + readonly isPromptFile: boolean; + readonly enabled: boolean; +} + +export interface IChatRequestPasteVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'paste'; + readonly code: string; + readonly language: string; + readonly pastedLines: string; + + // This is only used for old serialized data and should be removed once we no longer support it + readonly fileName: string; + + // This is only undefined on old serialized data + readonly copiedFrom: { + readonly uri: URI; + readonly range: IRange; + } | undefined; +} + +export interface ISymbolVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'symbol'; + readonly value: Location; + readonly symbolKind: SymbolKind; +} + +export interface ICommandResultVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'command'; +} + +export interface IImageVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'image'; + readonly isPasted?: boolean; + readonly isURL?: boolean; + readonly mimeType?: string; +} + +export interface INotebookOutputVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'notebookOutput'; + readonly outputIndex?: number; + readonly mimeType?: string; +} + +export interface IDiagnosticVariableEntryFilterData { + readonly owner?: string; + readonly problemMessage?: string; + readonly filterUri?: URI; + readonly filterSeverity?: MarkerSeverity; + readonly filterRange?: IRange; +} + +/** + * Chat variable that represents an attached prompt file. + */ +export interface IPromptVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'file'; + readonly value: URI | Location; + readonly isRoot: boolean; + readonly modelDescription: string; +} + +export namespace IDiagnosticVariableEntryFilterData { + export const icon = Codicon.error; + + export function fromMarker(marker: IMarker): IDiagnosticVariableEntryFilterData { + return { + filterUri: marker.resource, + owner: marker.owner, + problemMessage: marker.message, + filterRange: { startLineNumber: marker.startLineNumber, endLineNumber: marker.endLineNumber, startColumn: marker.startColumn, endColumn: marker.endColumn } + }; + } + + export function toEntry(data: IDiagnosticVariableEntryFilterData): IDiagnosticVariableEntry { + return { + id: id(data), + name: label(data), + icon, + value: data, + kind: 'diagnostic', + ...data, + }; + } + + export function id(data: IDiagnosticVariableEntryFilterData) { + return [data.filterUri, data.owner, data.filterSeverity, data.filterRange?.startLineNumber].join(':'); + } + + export function label(data: IDiagnosticVariableEntryFilterData) { + const enum TrimThreshold { + MaxChars = 30, + MaxSpaceLookback = 10, + } + if (data.problemMessage) { + if (data.problemMessage.length < TrimThreshold.MaxChars) { + return data.problemMessage; + } + + // Trim the message, on a space if it would not lose too much + // data (MaxSpaceLookback) or just blindly otherwise. + const lastSpace = data.problemMessage.lastIndexOf(' ', TrimThreshold.MaxChars); + if (lastSpace === -1 || lastSpace + TrimThreshold.MaxSpaceLookback < TrimThreshold.MaxChars) { + return data.problemMessage.substring(0, TrimThreshold.MaxChars) + '…'; + } + return data.problemMessage.substring(0, lastSpace) + '…'; + } + let labelStr = localize('chat.attachment.problems.all', "All Problems"); + if (data.filterUri) { + labelStr = localize('chat.attachment.problems.inFile', "Problems in {0}", basename(data.filterUri)); + } + + return labelStr; + } +} + +export interface IDiagnosticVariableEntry extends IBaseChatRequestVariableEntry, IDiagnosticVariableEntryFilterData { + readonly kind: 'diagnostic'; +} + +export interface IElementVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'element'; +} + +export interface IPromptFileVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'promptFile'; +} + +export interface ISCMHistoryItemVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'scmHistoryItem'; + readonly value: URI; + readonly historyItem: ISCMHistoryItem; +} + +export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry + | ISymbolVariableEntry | ICommandResultVariableEntry | IDiagnosticVariableEntry | IImageVariableEntry + | IChatRequestToolEntry | IChatRequestToolSetEntry + | IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry + | IPromptFileVariableEntry | ISCMHistoryItemVariableEntry; + + +export namespace IChatRequestVariableEntry { + + /** + * Returns URI of the passed variant entry. Return undefined if not found. + */ + export function toUri(entry: IChatRequestVariableEntry): URI | undefined { + return URI.isUri(entry.value) + ? entry.value + : isLocation(entry.value) + ? entry.value.uri + : undefined; + } +} + + +export function isImplicitVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestImplicitVariableEntry { + return obj.kind === 'implicit'; +} + +export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestPasteVariableEntry { + return obj.kind === 'paste'; +} + +export function isImageVariableEntry(obj: IChatRequestVariableEntry): obj is IImageVariableEntry { + return obj.kind === 'image'; +} + +export function isNotebookOutputVariableEntry(obj: IChatRequestVariableEntry): obj is INotebookOutputVariableEntry { + return obj.kind === 'notebookOutput'; +} + +export function isElementVariableEntry(obj: IChatRequestVariableEntry): obj is IElementVariableEntry { + return obj.kind === 'element'; +} + +export function isDiagnosticsVariableEntry(obj: IChatRequestVariableEntry): obj is IDiagnosticVariableEntry { + return obj.kind === 'diagnostic'; +} + +export function isChatRequestFileEntry(obj: IChatRequestVariableEntry): obj is IChatRequestFileEntry { + return obj.kind === 'file'; +} + +export function isChatRequestVariableEntry(obj: unknown): obj is IChatRequestVariableEntry { + const entry = obj as IChatRequestVariableEntry; + return typeof entry === 'object' && + entry !== null && + typeof entry.id === 'string' && + typeof entry.name === 'string'; +} + +export function isSCMHistoryItemVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemVariableEntry { + return obj.kind === 'scmHistoryItem'; +} diff --git a/src/vs/workbench/contrib/chat/common/chatVariables.ts b/src/vs/workbench/contrib/chat/common/chatVariables.ts index 93bbd3f343d..d1b1a554ea8 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariables.ts @@ -9,8 +9,9 @@ import { URI } from '../../../../base/common/uri.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { Location } from '../../../../editor/common/languages.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -import { IChatModel, IDiagnosticVariableEntryFilterData } from './chatModel.js'; +import { IChatModel } from './chatModel.js'; import { IChatContentReference, IChatProgressMessage } from './chatService.js'; +import { IDiagnosticVariableEntryFilterData } from './chatVariableEntries.js'; import { IToolData, ToolSet } from './languageModelToolsService.js'; export interface IChatVariableData { diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index a23e12b24cb..3ab1a20efe0 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -15,7 +15,8 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { ILogService } from '../../../../platform/log/common/log.js'; import { annotateVulnerabilitiesInText } from './annotations.js'; import { getFullyQualifiedId, IChatAgentCommand, IChatAgentData, IChatAgentNameService, IChatAgentResult } from './chatAgents.js'; -import { ChatPauseState, IChatModel, IChatProgressRenderableResponseContent, IChatRequestDisablement, IChatRequestModel, IChatRequestVariableEntry, IChatResponseModel, IChatTextEditGroup, IResponse } from './chatModel.js'; +import { ChatPauseState, IChatModel, IChatProgressRenderableResponseContent, IChatRequestDisablement, IChatRequestModel, IChatResponseModel, IChatTextEditGroup, IResponse } from './chatModel.js'; +import { IChatRequestVariableEntry } from './chatVariableEntries.js'; import { IParsedChatRequest } from './chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatCodeCitation, IChatContentReference, IChatFollowup, IChatProgressMessage, IChatResponseErrorDetails, IChatTask, IChatUsedContext } from './chatService.js'; import { countWords } from './chatWordCounter.js'; diff --git a/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts b/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts index 9386d7f3f46..ac2590f8388 100644 --- a/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts +++ b/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts @@ -9,7 +9,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { Memento } from '../../../common/memento.js'; import { ModifiedFileEntryState } from './chatEditingService.js'; -import { IChatRequestVariableEntry } from './chatModel.js'; +import { IChatRequestVariableEntry } from './chatVariableEntries.js'; import { IChatMode } from './chatModes.js'; import { CHAT_PROVIDER_ID } from './chatParticipantContribTypes.js'; import { ChatAgentLocation, ChatMode } from './constants.js'; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptSlashCommand.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptSlashCommand.ts index 72d1b4259bc..1069d4a7a89 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptSlashCommand.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptSlashCommand.ts @@ -4,9 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { PromptToken } from './promptToken.js'; -import { assert } from '../../../../../../../base/common/assert.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; -import { INVALID_NAME_CHARACTERS, STOP_CHARACTERS } from '../parsers/promptSlashCommandParser.js'; /** * All prompt at-mentions start with `/` character. @@ -24,14 +22,6 @@ export class PromptSlashCommand extends PromptToken { */ public readonly name: string, ) { - // sanity check of characters used in the provided command name - for (const character of name) { - assert( - (INVALID_NAME_CHARACTERS.includes(character) === false) && - (STOP_CHARACTERS.includes(character) === false), - `Slash command 'name' cannot contain character '${character}', got '${name}'.`, - ); - } super(range); } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptVariable.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptVariable.ts index c84903e544f..ec5dd2e7a36 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptVariable.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptVariable.ts @@ -4,9 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { PromptToken } from './promptToken.js'; -import { assert } from '../../../../../../../base/common/assert.js'; import { IRange, Range } from '../../../../../../../editor/common/core/range.js'; -import { INVALID_NAME_CHARACTERS, STOP_CHARACTERS } from '../parsers/promptVariableParser.js'; /** * All prompt variables start with `#` character. @@ -29,14 +27,6 @@ export class PromptVariable extends PromptToken { */ public readonly name: string, ) { - // sanity check of characters used in the provided variable name - for (const character of name) { - assert( - (INVALID_NAME_CHARACTERS.includes(character) === false) && - (STOP_CHARACTERS.includes(character) === false), - `Variable 'name' cannot contain character '${character}', got '${name}'.`, - ); - } super(range); } @@ -74,14 +64,6 @@ export class PromptVariableWithData extends PromptVariable { public readonly data: string, ) { super(fullRange, name); - - // sanity check of characters used in the provided variable data - for (const character of data) { - assert( - (STOP_CHARACTERS.includes(character) === false), - `Variable 'data' cannot contain character '${character}', got '${data}'.`, - ); - } } /** diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts index cd07b1d1c9d..c2b24bcfaeb 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts @@ -15,10 +15,9 @@ import { FileService } from '../../../../../platform/files/common/fileService.js import { InMemoryFileSystemProvider } from '../../../../../platform/files/common/inMemoryFilesystemProvider.js'; import { NullLogService } from '../../../../../platform/log/common/log.js'; import { TestEnvironmentService } from '../../../../test/browser/workbenchTestServices.js'; -import { ISnapshotEntry } from '../../browser/chatEditing/chatEditingModifiedFileEntry.js'; import { ChatEditingSessionStorage, IChatEditingSessionStop, StoredSessionState } from '../../browser/chatEditing/chatEditingSessionStorage.js'; import { ChatEditingSnapshotTextModelContentProvider } from '../../browser/chatEditing/chatEditingTextModelContentProviders.js'; -import { ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { ISnapshotEntry, ModifiedFileEntryState } from '../../common/chatEditingService.js'; suite('ChatEditingSessionStorage', () => { const ds = ensureNoDisposablesAreLeakedInTestSuite(); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 9ae4a8b1bda..b12fb8d7b14 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -43,7 +43,8 @@ import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/edit import { IViewsService } from '../../../services/views/common/viewsService.js'; import { showChatView } from '../../chat/browser/chat.js'; import { IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js'; -import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatRequestVariableEntry, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js'; +import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js'; +import { IChatRequestVariableEntry } from '../../chat/common/chatVariableEntries.js'; import { IChatService } from '../../chat/common/chatService.js'; import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js'; import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from '../common/inlineChat.js'; diff --git a/src/vs/workbench/contrib/markers/browser/markersChatContext.ts b/src/vs/workbench/contrib/markers/browser/markersChatContext.ts index 83bff8daa8c..204c19c4f62 100644 --- a/src/vs/workbench/contrib/markers/browser/markersChatContext.ts +++ b/src/vs/workbench/contrib/markers/browser/markersChatContext.ts @@ -15,7 +15,7 @@ import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/com import { IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService } from '../../chat/browser/chatContextPickService.js'; -import { IDiagnosticVariableEntryFilterData } from '../../chat/common/chatModel.js'; +import { IDiagnosticVariableEntryFilterData } from '../../chat/common/chatVariableEntries.js'; class MarkerChatContextPick implements IChatContextPickerItem { diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts index abf4f085d66..01a3ca06f6d 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts @@ -5,7 +5,6 @@ import { h } from '../../../../base/browser/dom.js'; import { assertNever } from '../../../../base/common/assert.js'; -import { raceTimeout } from '../../../../base/common/async.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { groupBy } from '../../../../base/common/collections.js'; import { Event } from '../../../../base/common/event.js'; @@ -34,9 +33,9 @@ import { ActiveEditorContext, ResourceContextKey } from '../../../common/context import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; -import { ChatViewId, IChatWidget, IChatWidgetService } from '../../chat/browser/chat.js'; +import { IChatWidgetService } from '../../chat/browser/chat.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; -import { ChatAgentLocation, ChatMode } from '../../chat/common/constants.js'; +import { ChatMode } from '../../chat/common/constants.js'; import { ILanguageModelsService } from '../../chat/common/languageModels.js'; import { extensionsFilterSubMenu, IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; import { TEXT_FILE_EDITOR_ID } from '../../files/common/files.js'; @@ -47,6 +46,7 @@ import { IMcpSamplingService, IMcpServer, IMcpServerStartOpts, IMcpService, IMcp import { McpAddConfigurationCommand } from './mcpCommandsAddConfiguration.js'; import { McpResourceQuickAccess, McpResourceQuickPick } from './mcpResourceQuickAccess.js'; import { McpUrlHandler } from './mcpUrlHandler.js'; +import { openPanelChatAndGetWidget } from './openPanelChatAndGetWidget.js'; // acroynms do not get localized const category: ILocalizedString = { @@ -135,7 +135,7 @@ export class ListMcpServerCommand extends Action2 { if (!picked) { // no-op } else if (picked.id === '$add') { - commandService.executeCommand(AddConfigurationAction.ID); + commandService.executeCommand(McpCommandIds.AddConfiguration); } else { commandService.executeCommand(McpCommandIds.ServerOptions, picked.id); } @@ -454,11 +454,9 @@ export class ResetMcpCachedTools extends Action2 { } export class AddConfigurationAction extends Action2 { - static readonly ID = 'workbench.mcp.addConfiguration'; - constructor() { super({ - id: AddConfigurationAction.ID, + id: McpCommandIds.AddConfiguration, title: localize2('mcp.addConfiguration', "Add Server..."), metadata: { description: localize2('mcp.addConfiguration.description', "Installs a new Model Context protocol to the mcp.json settings"), @@ -747,18 +745,4 @@ export class McpStartPromptingServerCommand extends Action2 { } } -export async function openPanelChatAndGetWidget(viewsService: IViewsService, chatService: IChatWidgetService): Promise { - await viewsService.openView(ChatViewId, true); - const widgets = chatService.getWidgetsByLocations(ChatAgentLocation.Panel); - if (widgets.length) { - return widgets[0]; - } - const eventPromise = Event.toPromise(Event.filter(chatService.onDidAddWidget, e => e.location === ChatAgentLocation.Panel)); - - return await raceTimeout( - eventPromise, - 10_000, // should be enough time for chat to initialize... - () => eventPromise.cancel(), - ); -} diff --git a/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts b/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts index d844736de57..84f38319c57 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts @@ -23,10 +23,10 @@ import { IEditorService } from '../../../services/editor/common/editorService.js import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IChatWidgetService } from '../../chat/browser/chat.js'; import { resolveImageEditorAttachContext } from '../../chat/browser/chatAttachmentResolve.js'; -import { IChatRequestVariableEntry } from '../../chat/common/chatModel.js'; +import { IChatRequestVariableEntry } from '../../chat/common/chatVariableEntries.js'; import { IMcpResource, IMcpResourceTemplate, IMcpServer, IMcpService, isMcpResourceTemplate, McpCapability, McpConnectionState, McpResourceURI } from '../common/mcpTypes.js'; import { IUriTemplateVariable } from '../common/uriTemplate.js'; -import { openPanelChatAndGetWidget } from './mcpCommands.js'; +import { openPanelChatAndGetWidget } from './openPanelChatAndGetWidget.js'; export class McpResourcePickHelper { public static sep(server: IMcpServer): IQuickPickSeparator { diff --git a/src/vs/workbench/contrib/mcp/browser/openPanelChatAndGetWidget.ts b/src/vs/workbench/contrib/mcp/browser/openPanelChatAndGetWidget.ts new file mode 100644 index 00000000000..96b7562386e --- /dev/null +++ b/src/vs/workbench/contrib/mcp/browser/openPanelChatAndGetWidget.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { raceTimeout } from '../../../../base/common/async.js'; +import { Event } from '../../../../base/common/event.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { IChatWidgetService, IChatWidget, ChatViewId } from '../../chat/browser/chat.js'; +import { ChatAgentLocation } from '../../chat/common/constants.js'; + + +export async function openPanelChatAndGetWidget(viewsService: IViewsService, chatService: IChatWidgetService): Promise { + await viewsService.openView(ChatViewId, true); + const widgets = chatService.getWidgetsByLocations(ChatAgentLocation.Panel); + if (widgets.length) { + return widgets[0]; + } + + const eventPromise = Event.toPromise(Event.filter(chatService.onDidAddWidget, e => e.location === ChatAgentLocation.Panel)); + + return await raceTimeout( + eventPromise, + 10000, // should be enough time for chat to initialize... + () => eventPromise.cancel() + ); +} diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chat/notebookChatUtils.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chat/notebookChatUtils.ts index 69f25f754e6..c67c5f4ad7a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chat/notebookChatUtils.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chat/notebookChatUtils.ts @@ -7,7 +7,7 @@ import { normalizeDriveLetter } from '../../../../../../base/common/labels.js'; import { basenameOrAuthority } from '../../../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; import { localize } from '../../../../../../nls.js'; -import { INotebookOutputVariableEntry } from '../../../../chat/common/chatModel.js'; +import { INotebookOutputVariableEntry } from '../../../../chat/common/chatVariableEntries.js'; import { CellUri } from '../../../common/notebookCommon.js'; import { ICellOutputViewModel, INotebookEditor } from '../../notebookBrowser.js'; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts index b1f977c9b33..b229cac0b9c 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts @@ -6,6 +6,7 @@ import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { codiconsLibrary } from '../../../../../../base/common/codiconsLibrary.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../../../base/common/network.js'; import { Position } from '../../../../../../editor/common/core/position.js'; import { Range } from '../../../../../../editor/common/core/range.js'; import { IWordAtPosition } from '../../../../../../editor/common/core/wordHelper.js'; @@ -20,7 +21,6 @@ import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../. import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../../common/contributions.js'; import { IEditorService } from '../../../../../services/editor/common/editorService.js'; import { IChatWidget, IChatWidgetService, showChatView } from '../../../../chat/browser/chat.js'; -import { ChatInputPart } from '../../../../chat/browser/chatInputPart.js'; import { ChatDynamicVariableModel } from '../../../../chat/browser/contrib/chatDynamicVariables.js'; import { computeCompletionRanges } from '../../../../chat/browser/contrib/chatInputCompletions.js'; import { IChatAgentService } from '../../../../chat/common/chatAgents.js'; @@ -70,7 +70,7 @@ class NotebookChatContribution extends Disposable implements IWorkbenchContribut updateNotebookAgentStatus(); this._register(chatAgentService.onDidChangeAgents(updateNotebookAgentStatus)); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'chatKernelDynamicCompletions', triggerCharacters: [chatVariableLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 531e14d8c8f..4c79dad2047 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -22,7 +22,7 @@ import { IEditorPane, IEditorPaneWithSelection } from '../../../common/editor.js import { CellViewModelStateChangeEvent, NotebookCellStateChangedEvent, NotebookLayoutInfo } from './notebookViewEvents.js'; import { NotebookCellTextModel } from '../common/model/notebookCellTextModel.js'; import { NotebookTextModel } from '../common/model/notebookTextModel.js'; -import { CellKind, ICellOutput, INotebookCellStatusBarItem, INotebookRendererInfo, INotebookFindOptions, IOrderedMimeType, NotebookCellInternalMetadata, NotebookCellMetadata, NOTEBOOK_EDITOR_ID } from '../common/notebookCommon.js'; +import { CellKind, ICellOutput, INotebookCellStatusBarItem, INotebookRendererInfo, INotebookFindOptions, IOrderedMimeType, NotebookCellInternalMetadata, NotebookCellMetadata, NOTEBOOK_EDITOR_ID, NOTEBOOK_DIFF_EDITOR_ID } from '../common/notebookCommon.js'; import { isCompositeNotebookEditorInput } from '../common/notebookEditorInput.js'; import { INotebookKernel } from '../common/notebookKernelService.js'; import { NotebookOptions } from './notebookOptions.js'; @@ -32,7 +32,6 @@ import { IEditorCommentsOptions, IEditorOptions } from '../../../../editor/commo import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { IObservable } from '../../../../base/common/observable.js'; -import { NotebookTextDiffEditor } from './diff/notebookDiffEditor.js'; import { INotebookTextDiffEditor } from './diff/notebookDiffEditorBrowser.js'; //#region Shared commands @@ -931,7 +930,7 @@ export function getNotebookEditorFromEditorPane(editorPane?: IEditorPane): INote return editorPane.getControl() as INotebookEditor | undefined; } - if (editorPane.getId() === NotebookTextDiffEditor.ID) { + if (editorPane.getId() === NOTEBOOK_DIFF_EDITOR_ID) { return (editorPane.getControl() as INotebookTextDiffEditor).inlineNotebookEditor; } diff --git a/src/vs/workbench/contrib/scm/browser/scmHistory.ts b/src/vs/workbench/contrib/scm/browser/scmHistory.ts index 7a6e840155a..71faef70db0 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistory.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistory.ts @@ -10,7 +10,6 @@ import { asCssVariable, ColorIdentifier, registerColor } from '../../../../platf import { ISCMHistoryItem, ISCMHistoryItemGraphNode, ISCMHistoryItemRef, ISCMHistoryItemViewModel } from '../common/history.js'; import { rot } from '../../../../base/common/numbers.js'; import { svgElem } from '../../../../base/browser/dom.js'; -import { compareHistoryItemRefs } from './util.js'; export const SWIMLANE_HEIGHT = 22; export const SWIMLANE_WIDTH = 11; @@ -365,3 +364,31 @@ export function getHistoryItemIndex(historyItemViewModel: ISCMHistoryItemViewMod // Circle index - use the input swimlane index if present, otherwise add it to the end return inputIndex !== -1 ? inputIndex : inputSwimlanes.length; } + +export function compareHistoryItemRefs( + ref1: ISCMHistoryItemRef, + ref2: ISCMHistoryItemRef, + currentHistoryItemRef?: ISCMHistoryItemRef, + currentHistoryItemRemoteRef?: ISCMHistoryItemRef, + currentHistoryItemBaseRef?: ISCMHistoryItemRef +): number { + const getHistoryItemRefOrder = (ref: ISCMHistoryItemRef) => { + if (ref.id === currentHistoryItemRef?.id) { + return 1; + } else if (ref.id === currentHistoryItemRemoteRef?.id) { + return 2; + } else if (ref.id === currentHistoryItemBaseRef?.id) { + return 3; + } else if (ref.color !== undefined) { + return 4; + } + + return 99; + }; + + // Assign order (current > remote > base > color) + const ref1Order = getHistoryItemRefOrder(ref1); + const ref2Order = getHistoryItemRefOrder(ref2); + + return ref1Order - ref2Order; +} diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts index 9de801b4541..7a2c3ab47f5 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts @@ -22,7 +22,7 @@ import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IChatWidget, showChatView } from '../../chat/browser/chat.js'; import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService, picksWithPromiseFn } from '../../chat/browser/chatContextPickService.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; -import { ISCMHistoryItemVariableEntry } from '../../chat/common/chatModel.js'; +import { ISCMHistoryItemVariableEntry } from '../../chat/common/chatVariableEntries.js'; import { ScmHistoryItemResolver } from '../../multiDiffEditor/browser/scmMultiDiffSourceResolver.js'; import { ISCMHistoryItem } from '../common/history.js'; import { ISCMProvider, ISCMService, ISCMViewService } from '../common/scm.js'; diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 2334f15157f..f838ba6ac9b 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -5,7 +5,7 @@ import { localize } from '../../../../nls.js'; import * as platform from '../../../../base/common/platform.js'; -import { ISCMHistoryItem, ISCMHistoryItemRef, SCMHistoryItemChangeViewModelTreeElement, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from '../common/history.js'; +import { ISCMHistoryItem, SCMHistoryItemChangeViewModelTreeElement, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from '../common/history.js'; import { ISCMResource, ISCMRepository, ISCMResourceGroup, ISCMInput, ISCMActionButton, ISCMViewService, ISCMProvider } from '../common/scm.js'; import { IMenu, MenuItemAction } from '../../../../platform/actions/common/actions.js'; import { IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; @@ -224,31 +224,3 @@ export function getHistoryItemHoverContent(themeService: IThemeService, historyI return { markdown, markdownNotSupportedFallback: historyItem.message }; } - -export function compareHistoryItemRefs( - ref1: ISCMHistoryItemRef, - ref2: ISCMHistoryItemRef, - currentHistoryItemRef?: ISCMHistoryItemRef, - currentHistoryItemRemoteRef?: ISCMHistoryItemRef, - currentHistoryItemBaseRef?: ISCMHistoryItemRef -): number { - const getHistoryItemRefOrder = (ref: ISCMHistoryItemRef) => { - if (ref.id === currentHistoryItemRef?.id) { - return 1; - } else if (ref.id === currentHistoryItemRemoteRef?.id) { - return 2; - } else if (ref.id === currentHistoryItemBaseRef?.id) { - return 3; - } else if (ref.color !== undefined) { - return 4; - } - - return 99; - }; - - // Assign order (current > remote > base > color) - const ref1Order = getHistoryItemRefOrder(ref1); - const ref2Order = getHistoryItemRefOrder(ref2); - - return ref1Order - ref2Order; -} diff --git a/src/vs/workbench/contrib/search/browser/chatContributions.ts b/src/vs/workbench/contrib/search/browser/chatContributions.ts index 3a039e6d339..2e8aa007859 100644 --- a/src/vs/workbench/contrib/search/browser/chatContributions.ts +++ b/src/vs/workbench/contrib/search/browser/chatContributions.ts @@ -14,7 +14,7 @@ import { IWorkbenchContribution } from '../../../common/contributions.js'; import { getExcludes, IFileQuery, ISearchComplete, ISearchConfiguration, ISearchService, QueryType, VIEW_ID } from '../../../services/search/common/search.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService, IChatContextValueItem, picksWithPromiseFn } from '../../chat/browser/chatContextPickService.js'; -import { IChatRequestVariableEntry, ISymbolVariableEntry } from '../../chat/common/chatModel.js'; +import { IChatRequestVariableEntry, ISymbolVariableEntry } from '../../chat/common/chatVariableEntries.js'; import { SearchContext } from '../common/constants.js'; import { SearchView } from './searchView.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index a9780480c6d..7c2219db3f2 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -20,7 +20,7 @@ import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatus import { IOutputChannelRegistry, Extensions as OutputExt } from '../../../services/output/common/output.js'; -import { ITaskEvent, TaskGroup, TaskSettingId, TASKS_CATEGORY, TASK_RUNNING_STATE, TASK_TERMINAL_ACTIVE, TaskEventKind } from '../common/tasks.js'; +import { ITaskEvent, TaskGroup, TaskSettingId, TASKS_CATEGORY, TASK_RUNNING_STATE, TASK_TERMINAL_ACTIVE, TaskEventKind, rerunTaskIcon, RerunForActiveTerminalCommandId } from '../common/tasks.js'; import { ITaskService, TaskCommandsRegistered, TaskExecutionSupportedContext } from '../common/taskService.js'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; @@ -40,8 +40,6 @@ import { TaskDefinitionRegistry } from '../common/taskDefinitionRegistry.js'; import { TerminalMenuBarGroup } from '../../terminal/browser/terminalMenus.js'; import { isString } from '../../../../base/common/types.js'; import { promiseWithResolvers } from '../../../../base/common/async.js'; -import { Codicon } from '../../../../base/common/codicons.js'; -import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { TerminalContextKeys } from '../../terminal/common/terminalContextKey.js'; import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; @@ -566,8 +564,6 @@ configurationRegistry.registerConfiguration({ } }); -export const rerunTaskIcon = registerIcon('rerun-task', Codicon.refresh, nls.localize('rerunTaskIcon', 'View icon of the rerun task.')); -export const RerunForActiveTerminalCommandId = 'workbench.action.tasks.rerunForActiveTerminal'; registerAction2(class extends Action2 { constructor() { super({ @@ -595,4 +591,3 @@ registerAction2(class extends Action2 { } } }); - diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 5b2f568a3c3..3fbca0976b9 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -40,7 +40,7 @@ import { TaskTerminalStatus } from './taskTerminalStatus.js'; import { ProblemCollectorEventKind, ProblemHandlingStrategy, StartStopProblemCollector, WatchingProblemCollector } from '../common/problemCollectors.js'; import { GroupKind } from '../common/taskConfiguration.js'; import { IResolveSet, IResolvedVariables, ITaskExecuteResult, ITaskResolver, ITaskSummary, ITaskSystem, ITaskSystemInfo, ITaskSystemInfoResolver, ITaskTerminateResponse, TaskError, TaskErrors, TaskExecuteKind, Triggers } from '../common/taskSystem.js'; -import { CommandOptions, CommandString, ContributedTask, CustomTask, DependsOrder, ICommandConfiguration, IConfigurationProperties, IExtensionTaskSource, IPresentationOptions, IShellConfiguration, IShellQuotingOptions, ITaskEvent, InMemoryTask, PanelKind, RevealKind, RevealProblemKind, RuntimeType, ShellQuoting, TASK_TERMINAL_ACTIVE, Task, TaskEvent, TaskEventKind, TaskScope, TaskSourceKind } from '../common/tasks.js'; +import { CommandOptions, CommandString, ContributedTask, CustomTask, DependsOrder, ICommandConfiguration, IConfigurationProperties, IExtensionTaskSource, IPresentationOptions, IShellConfiguration, IShellQuotingOptions, ITaskEvent, InMemoryTask, PanelKind, RerunForActiveTerminalCommandId, RevealKind, RevealProblemKind, RuntimeType, ShellQuoting, TASK_TERMINAL_ACTIVE, Task, TaskEvent, TaskEventKind, TaskScope, TaskSourceKind, rerunTaskIcon } from '../common/tasks.js'; import { ITerminalGroupService, ITerminalInstance, ITerminalService } from '../../terminal/browser/terminal.js'; import { VSCodeOscProperty, VSCodeOscPt, VSCodeSequence } from '../../terminal/browser/terminalEscapeSequences.js'; import { TerminalProcessExtHostProxy } from '../../terminal/browser/terminalProcessExtHostProxy.js'; @@ -50,7 +50,6 @@ import { IWorkbenchEnvironmentService } from '../../../services/environment/comm import { IOutputService } from '../../../services/output/common/output.js'; import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js'; import { IPathService } from '../../../services/path/common/pathService.js'; -import { RerunForActiveTerminalCommandId, rerunTaskIcon } from './task.contribution.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; interface ITerminalData { diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index f7acf37527e..8f40c65ffda 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -17,6 +17,8 @@ import { TaskDefinitionRegistry } from './taskDefinitionRegistry.js'; import { IExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; import { ConfigurationTarget } from '../../../../platform/configuration/common/configuration.js'; import { TerminalExitReason } from '../../../../platform/terminal/common/terminal.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; @@ -1389,3 +1391,5 @@ export namespace TaskDefinition { return KeyedTaskIdentifier.create(literal); } } + +export const rerunTaskIcon = registerIcon('rerun-task', Codicon.refresh, nls.localize('rerunTaskIcon', 'View icon of the rerun task.')); export const RerunForActiveTerminalCommandId = 'workbench.action.tasks.rerunForActiveTerminal';