diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index f3e6b43c..87db1c57 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -136,6 +136,6 @@ export function isWCOEnabled(): boolean { // Returns the bounding rect of the titlebar area if it is supported and defined // See docs at https://developer.mozilla.org/en-US/docs/Web/API/WindowControlsOverlay/getTitlebarAreaRect -export function getWCOTitlebarAreaRect(targetWindow: Window): DOMRect | undefined { - return (targetWindow.navigator as any)?.windowControlsOverlay?.getTitlebarAreaRect(); +export function getWCOBoundingRect(): DOMRect | undefined { + return (navigator as any)?.windowControlsOverlay?.getTitlebarAreaRect(); } diff --git a/src/vs/base/browser/ui/radio/radio.ts b/src/vs/base/browser/ui/radio/radio.ts index 5ab1edc4..2f57e4ac 100644 --- a/src/vs/base/browser/ui/radio/radio.ts +++ b/src/vs/base/browser/ui/radio/radio.ts @@ -11,7 +11,7 @@ import { $ } from 'vs/base/browser/dom'; import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { Button } from 'vs/base/browser/ui/button/button'; import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; -import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IRadioStyles { readonly activeForeground?: string; @@ -53,7 +53,7 @@ export class Radio extends Widget { constructor(opts: IRadioOptions) { super(); - this.hoverDelegate = opts.hoverDelegate ?? this._register(createInstantHoverDelegate()); + this.hoverDelegate = opts.hoverDelegate ?? getDefaultHoverDelegate('element'); this.domNode = $('.monaco-custom-radio'); this.domNode.setAttribute('role', 'radio'); diff --git a/src/vs/base/parts/sandbox/common/electronTypes.ts b/src/vs/base/parts/sandbox/common/electronTypes.ts index ef8c1026..43fa7507 100644 --- a/src/vs/base/parts/sandbox/common/electronTypes.ts +++ b/src/vs/base/parts/sandbox/common/electronTypes.ts @@ -217,6 +217,24 @@ export interface FileFilter { name: string; } +export interface OpenDevToolsOptions { + /** + * Opens the devtools with specified dock state, can be `left`, `right`, `bottom`, + * `undocked`, `detach`. Defaults to last used dock state. In `undocked` mode it's + * possible to dock back. In `detach` mode it's not. + */ + mode: ('left' | 'right' | 'bottom' | 'undocked' | 'detach'); + /** + * Whether to bring the opened devtools window to the foreground. The default is + * `true`. + */ + activate?: boolean; + /** + * A title for the DevTools window (only in `undocked` or `detach` mode). + */ + title?: string; +} + interface InputEvent { // Docs: https://electronjs.org/docs/api/structures/input-event diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 2d8e9ae9..ed929bc9 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -222,7 +222,7 @@ export class HoverWidget extends Widget implements IHoverWidget { } // Show the hover hint if needed - if (options.appearance?.showHoverHint) { + if (hideOnHover && options.appearance?.showHoverHint) { const statusBarElement = $('div.hover-row.status-bar'); const infoElement = $('div.info'); infoElement.textContent = localize('hoverhint', 'Hold {0} key to mouse over', isMacintosh ? 'Option' : 'Alt'); diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index fbb24ec6..32b937b6 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -2019,9 +2019,9 @@ export interface CommentThreadChangedEvent { } export interface CodeLens { - range: IRange; - id?: string; - command?: Command; + range: IRange; // the range of code + id?: string; // no idea what this is for + command?: Command; // command to run when they click the codeLens } export interface CodeLensList { diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index 862459df..dbbe9a91 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -180,12 +180,6 @@ export class CodeActionController extends Disposable implements IEditorContribut return; } - - const selection = this._editor.getSelection(); - if (selection?.startLineNumber !== newState.position.lineNumber) { - return; - } - this._lightBulbWidget.value?.update(actions, newState.trigger, newState.position); if (newState.trigger.type === CodeActionTriggerType.Invoke) { diff --git a/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts index bc71291b..0395f421 100644 --- a/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts @@ -75,14 +75,6 @@ export class LightBulbWidget extends Disposable implements IContentWidget { private _gutterState: LightBulbState.State = LightBulbState.Hidden; private _iconClasses: string[] = []; - private readonly lightbulbClasses = [ - 'codicon-' + GUTTER_LIGHTBULB_ICON.id, - 'codicon-' + GUTTER_LIGHTBULB_AIFIX_AUTO_FIX_ICON.id, - 'codicon-' + GUTTER_LIGHTBULB_AUTO_FIX_ICON.id, - 'codicon-' + GUTTER_LIGHTBULB_AIFIX_ICON.id, - 'codicon-' + GUTTER_SPARKLE_FILLED_ICON.id - ]; - private _preferredKbLabel?: string; private _quickFixKbLabel?: string; @@ -156,8 +148,15 @@ export class LightBulbWidget extends Disposable implements IContentWidget { })); this._register(this._editor.onMouseDown(async (e: IEditorMouseEvent) => { + const lightbulbClasses = [ + 'codicon-' + GUTTER_LIGHTBULB_ICON.id, + 'codicon-' + GUTTER_LIGHTBULB_AIFIX_AUTO_FIX_ICON.id, + 'codicon-' + GUTTER_LIGHTBULB_AUTO_FIX_ICON.id, + 'codicon-' + GUTTER_LIGHTBULB_AIFIX_ICON.id, + 'codicon-' + GUTTER_SPARKLE_FILLED_ICON.id + ]; - if (!e.target.element || !this.lightbulbClasses.some(cls => e.target.element && e.target.element.classList.contains(cls))) { + if (!e.target.element || !lightbulbClasses.some(cls => e.target.element && e.target.element.classList.contains(cls))) { return; } @@ -248,9 +247,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { let hasDecoration = false; if (currLineDecorations) { for (const decoration of currLineDecorations) { - const glyphClass = decoration.options.glyphMarginClassName; - - if (glyphClass && !this.lightbulbClasses.some(className => glyphClass.includes(className))) { + if (decoration.options.glyphMarginClassName) { hasDecoration = true; break; } @@ -274,6 +271,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { const currLineEmptyOrIndented = isLineEmptyOrIndented(lineNumber); const notEmpty = !nextLineEmptyOrIndented && !prevLineEmptyOrIndented; + // check above and below. if both are blocked, display lightbulb in the gutter. if (!nextLineEmptyOrIndented && !prevLineEmptyOrIndented && !hasDecoration) { this.gutterState = new LightBulbState.Showing(actions, trigger, atPosition, { @@ -282,7 +280,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { }); this.renderGutterLightbub(); return this.hide(); - } else if (prevLineEmptyOrIndented || endLine || (prevLineEmptyOrIndented && !currLineEmptyOrIndented)) { + } else if (prevLineEmptyOrIndented || endLine || (notEmpty && !currLineEmptyOrIndented)) { effectiveLineNumber -= 1; } else if (nextLineEmptyOrIndented || (notEmpty && currLineEmptyOrIndented)) { effectiveLineNumber += 1; diff --git a/src/vs/editor/contrib/codelens/browser/codeLensCache.ts b/src/vs/editor/contrib/codelens/browser/codeLensCache.ts index f7666b8c..5f479695 100644 --- a/src/vs/editor/contrib/codelens/browser/codeLensCache.ts +++ b/src/vs/editor/contrib/codelens/browser/codeLensCache.ts @@ -3,17 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; -import { LRUCache } from 'vs/base/common/map'; -import { Range } from 'vs/editor/common/core/range'; -import { ITextModel } from 'vs/editor/common/model'; -import { CodeLens, CodeLensList, CodeLensProvider } from 'vs/editor/common/languages'; -import { CodeLensModel } from 'vs/editor/contrib/codelens/browser/codelens'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; -import { mainWindow } from 'vs/base/browser/window'; -import { runWhenWindowIdle } from 'vs/base/browser/dom'; +import { Event } from '../../../../base/common/event.js'; +import { LRUCache } from '../../../../base/common/map.js'; +import { Range } from '../../../common/core/range.js'; +import { ITextModel } from '../../../common/model.js'; +import { CodeLens, CodeLensList, CodeLensProvider } from '../../../common/languages.js'; +import { CodeLensModel } from './codelens.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from '../../../../platform/storage/common/storage.js'; +import { mainWindow } from '../../../../base/browser/window.js'; +import { runWhenWindowIdle } from '../../../../base/browser/dom.js'; export const ICodeLensCache = createDecorator('ICodeLensCache'); diff --git a/src/vs/editor/contrib/codelens/browser/codelens.ts b/src/vs/editor/contrib/codelens/browser/codelens.ts index 68253150..0a777339 100644 --- a/src/vs/editor/contrib/codelens/browser/codelens.ts +++ b/src/vs/editor/contrib/codelens/browser/codelens.ts @@ -3,17 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from 'vs/base/common/cancellation'; -import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { assertType } from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; -import { ITextModel } from 'vs/editor/common/model'; -import { CodeLens, CodeLensList, CodeLensProvider } from 'vs/editor/common/languages'; -import { IModelService } from 'vs/editor/common/services/model'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { illegalArgument, onUnexpectedExternalError } from '../../../../base/common/errors.js'; +import { DisposableStore } from '../../../../base/common/lifecycle.js'; +import { assertType } from '../../../../base/common/types.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ITextModel } from '../../../common/model.js'; +import { CodeLens, CodeLensList, CodeLensProvider } from '../../../common/languages.js'; +import { IModelService } from '../../../common/services/model.js'; +import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; +import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js'; +import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; export interface CodeLensItem { symbol: CodeLens; diff --git a/src/vs/editor/contrib/codelens/browser/codelensController.ts b/src/vs/editor/contrib/codelens/browser/codelensController.ts index 49433647..6eca903f 100644 --- a/src/vs/editor/contrib/codelens/browser/codelensController.ts +++ b/src/vs/editor/contrib/codelens/browser/codelensController.ts @@ -4,26 +4,26 @@ *--------------------------------------------------------------------------------------------*/ -import { CancelablePromise, createCancelablePromise, disposableTimeout, RunOnceScheduler } from 'vs/base/common/async'; -import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; -import { IActiveCodeEditor, ICodeEditor, IViewZoneChangeAccessor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, EditorContributionInstantiation, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; -import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; -import { CodeLens, Command } from 'vs/editor/common/languages'; -import { CodeLensItem, CodeLensModel, getCodeLensModel } from 'vs/editor/contrib/codelens/browser/codelens'; -import { ICodeLensCache } from 'vs/editor/contrib/codelens/browser/codeLensCache'; -import { CodeLensHelper, CodeLensWidget } from 'vs/editor/contrib/codelens/browser/codelensWidget'; -import { localize } from 'vs/nls'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { CancelablePromise, createCancelablePromise, disposableTimeout, RunOnceScheduler } from '../../../../base/common/async.js'; +import { onUnexpectedError, onUnexpectedExternalError } from '../../../../base/common/errors.js'; +import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; +import { StableEditorScrollState } from '../../../browser/stableEditorScroll.js'; +import { IActiveCodeEditor, ICodeEditor, IViewZoneChangeAccessor, MouseTargetType } from '../../../browser/editorBrowser.js'; +import { EditorAction, EditorContributionInstantiation, registerEditorAction, registerEditorContribution, ServicesAccessor } from '../../../browser/editorExtensions.js'; +import { EditorOption, EDITOR_FONT_DEFAULTS } from '../../../common/config/editorOptions.js'; +import { IEditorContribution } from '../../../common/editorCommon.js'; +import { EditorContextKeys } from '../../../common/editorContextKeys.js'; +import { IModelDecorationsChangeAccessor } from '../../../common/model.js'; +import { CodeLens, Command } from '../../../common/languages.js'; +import { CodeLensItem, CodeLensModel, getCodeLensModel } from './codelens.js'; +import { ICodeLensCache } from './codeLensCache.js'; +import { CodeLensHelper, CodeLensWidget } from './codelensWidget.js'; +import { localize } from '../../../../nls.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; +import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from '../../../common/services/languageFeatureDebounce.js'; +import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; export class CodeLensContribution implements IEditorContribution { diff --git a/src/vs/editor/contrib/codelens/browser/codelensWidget.ts b/src/vs/editor/contrib/codelens/browser/codelensWidget.ts index c89fc7fd..394c333b 100644 --- a/src/vs/editor/contrib/codelens/browser/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/browser/codelensWidget.ts @@ -3,16 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from 'vs/base/browser/dom'; -import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { Constants } from 'vs/base/common/uint'; -import 'vs/css!./codelensWidget'; -import { ContentWidgetPositionPreference, IActiveCodeEditor, IContentWidget, IContentWidgetPosition, IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; -import { Range } from 'vs/editor/common/core/range'; -import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { CodeLens, Command } from 'vs/editor/common/languages'; -import { CodeLensItem } from 'vs/editor/contrib/codelens/browser/codelens'; +import * as dom from '../../../../base/browser/dom.js'; +import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { Constants } from '../../../../base/common/uint.js'; +import './codelensWidget.css'; +import { ContentWidgetPositionPreference, IActiveCodeEditor, IContentWidget, IContentWidgetPosition, IViewZone, IViewZoneChangeAccessor } from '../../../browser/editorBrowser.js'; +import { Range } from '../../../common/core/range.js'; +import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from '../../../common/model.js'; +import { ModelDecorationOptions } from '../../../common/model/textModel.js'; +import { CodeLens, Command } from '../../../common/languages.js'; +import { CodeLensItem } from './codelens.js'; class CodeLensViewZone implements IViewZone { diff --git a/src/vs/editor/contrib/rename/browser/renameWidget.ts b/src/vs/editor/contrib/rename/browser/renameWidget.ts index 1f6fcc14..b284cd51 100644 --- a/src/vs/editor/contrib/rename/browser/renameWidget.ts +++ b/src/vs/editor/contrib/rename/browser/renameWidget.ts @@ -318,8 +318,7 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable } afterRender(position: ContentWidgetPositionPreference | null): void { - // FIXME@ulugbekna: commenting trace log out until we start unmounting the widget from editor properly - https://github.com/microsoft/vscode/issues/226975 - // this._trace('invoking afterRender, position: ', position ? 'not null' : 'null'); + this._trace('invoking afterRender, position: ', position ? 'not null' : 'null'); if (position === null) { // cancel rename when input widget isn't rendered anymore this.cancelInput(true, 'afterRender (because position is null)'); @@ -364,7 +363,7 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable } cancelInput(focusEditor: boolean, caller: string): void { - // this._trace(`invoking cancelInput, caller: ${caller}, _currentCancelInput: ${this._currentAcceptInput ? 'not undefined' : 'undefined'}`); + this._trace(`invoking cancelInput, caller: ${caller}, _currentCancelInput: ${this._currentAcceptInput ? 'not undefined' : 'undefined'}`); this._currentCancelInput?.(focusEditor); } diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index 8eeeee2d..98403a2a 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -166,8 +166,8 @@ export class ActionList extends Disposable { private readonly _list: List>; - private readonly _actionLineHeight = 24; - private readonly _headerLineHeight = 26; + private readonly _actionLineHeight = 28; + private readonly _headerLineHeight = 28; private readonly _allMenuItems: readonly IActionListItem[]; diff --git a/src/vs/platform/actionWidget/browser/actionWidget.css b/src/vs/platform/actionWidget/browser/actionWidget.css index d205c7ab..b9ebc031 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.css +++ b/src/vs/platform/actionWidget/browser/actionWidget.css @@ -132,9 +132,8 @@ /* Action bar */ .action-widget .action-widget-action-bar { - background-color: var(--vscode-editorActionList-background); + background-color: var(--vscode-editorHoverWidget-statusBarBackground); border-top: 1px solid var(--vscode-editorHoverWidget-border); - margin-top: 2px; } .action-widget .action-widget-action-bar::before { @@ -144,7 +143,7 @@ } .action-widget .action-widget-action-bar .actions-container { - padding: 3px 8px 0; + padding: 0 8px; } .action-widget-action-bar .action-label { diff --git a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts index 7637899a..61c8816e 100644 --- a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts @@ -16,15 +16,15 @@ export class GlobalExtensionEnablementService extends Disposable implements IGlo private _onDidChangeEnablement = new Emitter<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }>(); readonly onDidChangeEnablement: Event<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }> = this._onDidChangeEnablement.event; - private readonly storageManager: StorageManager; + private readonly storageManger: StorageManager; constructor( @IStorageService storageService: IStorageService, @IExtensionManagementService extensionManagementService: IExtensionManagementService, ) { super(); - this.storageManager = this._register(new StorageManager(storageService)); - this._register(this.storageManager.onDidChange(extensions => this._onDidChangeEnablement.fire({ extensions, source: 'storage' }))); + this.storageManger = this._register(new StorageManager(storageService)); + this._register(this.storageManger.onDidChange(extensions => this._onDidChangeEnablement.fire({ extensions, source: 'storage' }))); this._register(extensionManagementService.onDidInstallExtensions(e => e.forEach(({ local, operation }) => { if (local && operation === InstallOperation.Migrate) { this._removeFromDisabledExtensions(local.identifier); /* Reset migrated extensions */ @@ -84,11 +84,11 @@ export class GlobalExtensionEnablementService extends Disposable implements IGlo } private _getExtensions(storageId: string): IExtensionIdentifier[] { - return this.storageManager.get(storageId, StorageScope.PROFILE); + return this.storageManger.get(storageId, StorageScope.PROFILE); } private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void { - this.storageManager.set(storageId, extensions, StorageScope.PROFILE); + this.storageManger.set(storageId, extensions, StorageScope.PROFILE); } } diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts index a27a17ad..344d4539 100644 --- a/src/vs/platform/native/common/native.ts +++ b/src/vs/platform/native/common/native.ts @@ -6,7 +6,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { MessageBoxOptions, MessageBoxReturnValue, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'vs/base/parts/sandbox/common/electronTypes'; +import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'vs/base/parts/sandbox/common/electronTypes'; import { ISerializableCommandAction } from 'vs/platform/action/common/action'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -178,7 +178,7 @@ export interface ICommonNativeHostService { exit(code: number): Promise; // Development - openDevTools(options?: INativeHostOptions): Promise; + openDevTools(options?: Partial & INativeHostOptions): Promise; toggleDevTools(options?: INativeHostOptions): Promise; // Perf Introspection diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 4ccaa6b1..6c49504e 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import { exec } from 'child_process'; -import { app, BrowserWindow, clipboard, Display, Menu, MessageBoxOptions, MessageBoxReturnValue, OpenDialogOptions, OpenDialogReturnValue, powerMonitor, SaveDialogOptions, SaveDialogReturnValue, screen, shell, webContents } from 'electron'; +import { app, BrowserWindow, clipboard, Display, Menu, MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, OpenDialogOptions, OpenDialogReturnValue, powerMonitor, SaveDialogOptions, SaveDialogReturnValue, screen, shell, webContents } from 'electron'; import { arch, cpus, freemem, loadavg, platform, release, totalmem, type } from 'os'; import { promisify } from 'util'; import { memoize } from 'vs/base/common/decorators'; @@ -33,7 +33,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { ICodeWindow } from 'vs/platform/window/electron-main/window'; -import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable, useWindowControlsOverlay } from 'vs/platform/window/common/window'; +import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable } from 'vs/platform/window/common/window'; import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; @@ -855,28 +855,14 @@ export class NativeHostMainService extends Disposable implements INativeHostMain //#region Development - async openDevTools(windowId: number | undefined, options?: INativeHostOptions): Promise { + async openDevTools(windowId: number | undefined, options?: Partial & INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); - - let mode: 'bottom' | undefined = undefined; - if (isLinux && useWindowControlsOverlay(this.configurationService)) { - mode = 'bottom'; // TODO@bpasero WCO and devtools collide with default option 'right' - } - window?.win?.webContents.openDevTools(mode ? { mode } : undefined); + window?.win?.webContents.openDevTools(options?.mode ? { mode: options.mode, activate: options.activate } : undefined); } async toggleDevTools(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); - const webContents = window?.win?.webContents; - if (!webContents) { - return; - } - - if (isLinux && useWindowControlsOverlay(this.configurationService) && !webContents.isDevToolsOpened()) { - webContents.openDevTools({ mode: 'bottom' }); // TODO@bpasero WCO and devtools collide with default option 'right' - } else { - webContents.toggleDevTools(); - } + window?.win?.webContents.toggleDevTools(); } //#endregion diff --git a/src/vs/platform/theme/common/colorUtils.ts b/src/vs/platform/theme/common/colorUtils.ts index a237166f..14ceea88 100644 --- a/src/vs/platform/theme/common/colorUtils.ts +++ b/src/vs/platform/theme/common/colorUtils.ts @@ -159,7 +159,7 @@ class ColorRegistry implements IColorRegistry { public registerColor(id: string, defaults: ColorDefaults | ColorValue | null, description: string, needsTransparency = false, deprecationMessage?: string): ColorIdentifier { const colorContribution: ColorContribution = { id, description, defaults, needsTransparency, deprecationMessage }; this.colorsById[id] = colorContribution; - const propertySchema: IJSONSchemaWithSnippets = { type: 'string', format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] }; + const propertySchema: IJSONSchemaWithSnippets = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] }; if (deprecationMessage) { propertySchema.deprecationMessage = deprecationMessage; } @@ -168,7 +168,6 @@ class ColorRegistry implements IColorRegistry { propertySchema.patternErrorMessage = nls.localize('transparecyRequired', 'This color must be transparent or it will obscure content'); } this.colorSchema.properties[id] = { - description, oneOf: [ propertySchema, { type: 'string', const: DEFAULT_COLOR_CONFIG_VALUE, description: nls.localize('useDefault', 'Use the default color.') } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index dc5de9f2..40b1ebf0 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -14,6 +14,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { FileType } from 'vs/platform/files/common/files'; import { ILoggerResource, LogLevel } from 'vs/platform/log/common/log'; import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; +import product from 'vs/platform/product/common/product'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IAnyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; @@ -239,6 +240,8 @@ export function useWindowControlsOverlay(configurationService: IConfigurationSer if (typeof setting === 'boolean') { return setting; } + + return product.quality !== 'stable'; // disable by default in stable for now (TODO@bpasero TODO@benibenj flip when custom title is default) } // Default to true. diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 880a6026..12441286 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -28,7 +28,6 @@ import { IEditSessionIdentityService } from 'vs/platform/workspace/common/editSe import { EditorResourceAccessor, SaveReason, SideBySideEditor } from 'vs/workbench/common/editor'; import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; import { ICanonicalUriService } from 'vs/platform/workspace/common/canonicalUri'; -import { revive } from 'vs/base/common/marshalling'; @extHostNamedCustomer(MainContext.MainThreadWorkspace) export class MainThreadWorkspace implements MainThreadWorkspaceShape { @@ -147,7 +146,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { const query = this._queryBuilder.file( includeFolder ? [includeFolder] : workspace.folders, - revive(options) + options ); return this._searchService.fileSearch(query, token).then(result => { @@ -165,7 +164,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { const workspace = this._contextService.getWorkspace(); const folders = folder ? [folder] : workspace.folders.map(folder => folder.uri); - const query = this._queryBuilder.text(pattern, folders, revive(options)); + const query = this._queryBuilder.text(pattern, folders, options); query._reason = 'startTextSearch'; const onProgress = (p: ISearchProgressItem) => { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a9c8d66c..6da7aadc 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -550,6 +550,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider); }, + + + // VOID added this (I think will need to add this back when add ctrl+K) + // registerVoidCtrlKProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { + // return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider); + // }, + + registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable { return extHostLanguageFeatures.registerDefinitionProvider(extension, checkSelector(selector), provider); }, diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 6384178b..5e25c778 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -304,6 +304,17 @@ const newCommands: ApiCommand[] = [ })(value); }) ), + // // --- Void code lens + // new ApiCommand( + // 'vscode.executeVoidCodeLensProvider', '_executeVoidCodeLensProvider', 'Execute Void code lens provider.', + // [ApiCommandArgument.Uri, ApiCommandArgument.Number.with('itemResolveCount', 'Number of lenses that should be resolved and returned. Will only return resolved lenses, will impact performance)').optional()], + // new ApiCommandResult('A promise that resolves to an array of VoidCodeLens-instances.', (value, _args, converter) => { + // return tryMapWith(item => { + // return new types.CodeLens(typeConverters.Range.to(item.range), item.command && converter.fromInternal(item.command)); + // })(value); + // }) + // ), + // --- code actions new ApiCommand( 'vscode.executeCodeActionProvider', '_executeCodeActionProvider', 'Execute code action provider.', diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 5bb629a6..50ab3ea6 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -2312,15 +2312,15 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF } $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise { - return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined, token, resource.scheme === 'output'); + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined, token); } $resolveCodeLens(handle: number, symbol: extHostProtocol.ICodeLensDto, token: CancellationToken): Promise { - return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined, undefined, true); + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined, undefined); } $releaseCodeLenses(handle: number, cacheId: number): void { - this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined, undefined, true); + this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined, undefined); } // --- declaration diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 951c87d9..33e3bafe 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -584,7 +584,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac } const parsedInclude = include ? parseSearchExcludeInclude(GlobPattern.from(include)) : undefined; - const excludePatterns = globsToISearchPatternBuilder(options.exclude); + const excludePatterns = include ? globsToISearchPatternBuilder(options.exclude) : undefined; return { options: { @@ -664,10 +664,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac async findTextInFilesBase(query: vscode.TextSearchQuery, queryOptions: QueryOptions[] | undefined, callback: (result: ITextSearchResult, uri: URI) => void, token: vscode.CancellationToken = CancellationToken.None): Promise { const requestId = this._requestIdProvider.getNext(); - let isCanceled = false; - token.onCancellationRequested(_ => { - isCanceled = true; - }); + const isCanceled = false; this._activeSearchCallbacks[requestId] = p => { if (isCanceled) { diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 9a66a127..15afa9df 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -138,11 +138,11 @@ color: var(--vscode-titleBar-activeForeground); } -.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { +.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: var(--vscode-titleBar-inactiveForeground); } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: inherit; } @@ -182,7 +182,7 @@ text-overflow: ellipsis; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple { justify-content: flex-start; padding: 0 12px; } @@ -280,7 +280,7 @@ border-left: 1px solid transparent; } -/* Window Controls Container */ +/* Window Controls (Minimize, Max/Restore, Close) */ .monaco-workbench .part.titlebar .window-controls-container { display: flex; flex-grow: 0; @@ -292,12 +292,7 @@ height: 100%; } -.monaco-workbench.fullscreen .part.titlebar .window-controls-container { - display: none; - background-color: transparent; -} - -/* Window Controls Container Web: Apply WCO environment variables (https://developer.mozilla.org/en-US/docs/Web/CSS/env#titlebar-area-x) */ +/* Web WCO Sizing/Ordering */ .monaco-workbench.web .part.titlebar .titlebar-right .window-controls-container { width: calc(100vw - env(titlebar-area-width, 100vw) - env(titlebar-area-x, 0px)); height: env(titlebar-area-height, 35px); @@ -316,31 +311,29 @@ order: 1; } -/* Window Controls Container Desktop: apply zoom friendly size */ -.monaco-workbench:not(.web):not(.mac) .part.titlebar .window-controls-container { +/* Desktop Windows/Linux Window Controls*/ +.monaco-workbench:not(.web):not(.mac) .part.titlebar .window-controls-container.primary { width: calc(138px / var(--zoom-factor, 1)); } -.monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container.counter-zoom .window-controls-container { +.monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container.counter-zoom .window-controls-container.primary { width: 138px; } -.monaco-workbench.linux:not(.web) .part.titlebar .window-controls-container.wco-enabled { - width: calc(var(--title-wco-width, 138px)); -} - -.monaco-workbench.linux:not(.web) .part.titlebar .titlebar-container.counter-zoom .window-controls-container.wco-enabled { - width: var(--title-wco-width, 138px); -} - .monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container:not(.counter-zoom) .window-controls-container * { zoom: calc(1 / var(--zoom-factor, 1)); } -.monaco-workbench:not(.web).mac .part.titlebar .window-controls-container { +/* Desktop macOS Window Controls */ +.monaco-workbench:not(.web).mac .part.titlebar .window-controls-container.primary { width: 70px; } +.monaco-workbench.fullscreen .part.titlebar .window-controls-container { + display: none; + background-color: transparent; +} + /* Window Control Icons */ .monaco-workbench .part.titlebar .window-controls-container > .window-icon { display: flex; @@ -349,11 +342,6 @@ height: 100%; width: 46px; font-size: 16px; - color: var(--vscode-titleBar-activeForeground); -} - -.monaco-workbench .part.titlebar.inactive .window-controls-container > .window-icon { - color: var(--vscode-titleBar-inactiveForeground); } .monaco-workbench .part.titlebar .window-controls-container > .window-icon::before { @@ -388,6 +376,7 @@ z-index: 2500; -webkit-app-region: no-drag; height: 100%; + min-width: 28px; } .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container { @@ -466,3 +455,11 @@ border-radius: 16px; text-align: center; } + +.monaco-workbench .part.titlebar .window-controls-container .window-icon { + color: var(--vscode-titleBar-activeForeground); +} + +.monaco-workbench .part.titlebar.inactive .window-controls-container .window-icon { + color: var(--vscode-titleBar-inactiveForeground); +} diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index ff858e53..9dd7d555 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/titlebarpart'; import { localize, localize2 } from 'vs/nls'; import { MultiWindowParts, Part } from 'vs/workbench/browser/part'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; -import { getWCOTitlebarAreaRect, getZoomFactor, isWCOEnabled, onDidChangeZoomLevel } from 'vs/base/browser/browser'; +import { getWCOBoundingRect, getZoomFactor, isWCOEnabled } from 'vs/base/browser/browser'; import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, TitlebarStyle, hasCustomTitlebar, hasNativeTitlebar, DEFAULT_CUSTOM_TITLEBAR_HEIGHT } from 'vs/platform/window/common/window'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -228,7 +228,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { const wcoEnabled = isWeb && isWCOEnabled(); let value = this.isCommandCenterVisible || wcoEnabled ? DEFAULT_CUSTOM_TITLEBAR_HEIGHT : 30; if (wcoEnabled) { - value = Math.max(value, getWCOTitlebarAreaRect(getWindow(this.element))?.height ?? 0); + value = Math.max(value, getWCOBoundingRect()?.height ?? 0); } return value / (this.preventZoom ? getZoomFactor(getWindow(this.element)) : 1); @@ -249,7 +249,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { //#endregion protected rootContainer!: HTMLElement; - protected windowControlsContainer: HTMLElement | undefined; + protected primaryWindowControls: HTMLElement | undefined; protected dragRegion: HTMLElement | undefined; private title!: HTMLElement; @@ -476,49 +476,21 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.createActionToolBarMenus(); } - // Window Controls Container + let primaryControlLocation = isMacintosh ? 'left' : 'right'; + if (isMacintosh && isNative) { + + // Check if the locale is RTL, macOS will move traffic lights in RTL locales + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo + + const localeInfo = new Intl.Locale(platformLocale) as any; + if (localeInfo?.textInfo?.direction === 'rtl') { + primaryControlLocation = 'right'; + } + } + if (!hasNativeTitlebar(this.configurationService, this.titleBarStyle)) { - let primaryWindowControlsLocation = isMacintosh ? 'left' : 'right'; - if (isMacintosh && isNative) { - - // Check if the locale is RTL, macOS will move traffic lights in RTL locales - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo - - const localeInfo = new Intl.Locale(platformLocale) as any; - if (localeInfo?.textInfo?.direction === 'rtl') { - primaryWindowControlsLocation = 'right'; - } - } - - if (isMacintosh && isNative && primaryWindowControlsLocation === 'left') { - // macOS native: controls are on the left and the container is not needed to make room - // for something, except for web where a custom menu being supported). not putting the - // container helps with allowing to move the window when clicking very close to the - // window control buttons. - } else { - this.windowControlsContainer = append(primaryWindowControlsLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container')); - if (isWeb) { - // Web: its possible to have control overlays on both sides, for example on macOS - // with window controls on the left and PWA controls on the right. - append(primaryWindowControlsLocation === 'left' ? this.rightContent : this.leftContent, $('div.window-controls-container')); - } - - if (isWCOEnabled()) { - this.windowControlsContainer.classList.add('wco-enabled'); - - const updateWCOWidthVariable = () => { - const targetWindow = getWindow(this.element); - const wcoTitlebarAreaRect = getWCOTitlebarAreaRect(targetWindow); - if (wcoTitlebarAreaRect) { - const wcoWidth = targetWindow.innerWidth - wcoTitlebarAreaRect.width - wcoTitlebarAreaRect.x; - this.windowControlsContainer?.style.setProperty('--title-wco-width', `${wcoWidth}px`); - } - }; - updateWCOWidthVariable(); - - this._register(onDidChangeZoomLevel(() => setTimeout(() => updateWCOWidthVariable(), 5))); // Somehow it does not get the right size without this timeout :-/ - } - } + this.primaryWindowControls = append(primaryControlLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container.primary')); + append(primaryControlLocation === 'left' ? this.rightContent : this.leftContent, $('div.window-controls-container.secondary')); } // Context menu over title bar: depending on the OS and the location of the click this will either be diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index d2e60118..28e759fb 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -152,7 +152,7 @@ class ChatHistoryAction extends Action2 { let lastDate: string | undefined = undefined; const picks = items.flatMap((i): [IQuickPickSeparator | undefined, IChatPickerItem] => { - const timeAgoStr = fromNowByDay(i.lastMessageDate, true, true); + const timeAgoStr = fromNowByDay(i.lastMessageDate, true); const separator: IQuickPickSeparator | undefined = timeAgoStr !== lastDate ? { type: 'separator', label: timeAgoStr, } : undefined; diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index be6c4955..d5cf3c35 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -36,7 +36,7 @@ import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat import { ChatEditor, IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput, ChatEditorInputSerializer } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { agentSlashCommandToMarkdown, agentToMarkdown } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; -import { ChatCompatibilityNotifier, ChatExtensionPointHandler } from 'vs/workbench/contrib/chat/browser/chatParticipantContributions'; +import { ChatExtensionPointHandler } from 'vs/workbench/contrib/chat/browser/chatParticipantContributions'; import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick'; import { ChatResponseAccessibleView } from 'vs/workbench/contrib/chat/browser/chatResponseAccessibleView'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; @@ -261,7 +261,9 @@ workbenchContributionsRegistry.registerWorkbenchContribution(ChatSlashStaticSlas Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer); registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup); registerWorkbenchContribution2(LanguageModelToolsExtensionPointHandler.ID, LanguageModelToolsExtensionPointHandler, WorkbenchPhase.BlockRestore); -registerWorkbenchContribution2(ChatCompatibilityNotifier.ID, ChatCompatibilityNotifier, WorkbenchPhase.Eventually); + +// Disabled until https://github.com/microsoft/vscode/issues/218646 is fixed +// registerWorkbenchContribution2(ChatCompatibilityNotifier.ID, ChatCompatibilityNotifier, WorkbenchPhase.Eventually); registerChatActions(); registerChatCopyActions(); diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 52d7d73b..8f910bc2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -516,7 +516,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge onDidChangeCursorPosition(); } - private initAttachedContext(container: HTMLElement, isLayout = false) { + private initAttachedContext(container: HTMLElement) { const oldHeight = container.offsetHeight; dom.clearNode(container); this.attachedContextDisposables.clear(); @@ -578,7 +578,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.attachedContextDisposables.add(disp); }); - if (oldHeight !== container.offsetHeight && !isLayout) { + if (oldHeight !== container.offsetHeight) { this._onDidChangeHeight.fire(); } } @@ -609,7 +609,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private previousInputEditorDimension: IDimension | undefined; private _layout(height: number, width: number, allowRecurse = true): void { - this.initAttachedContext(this.attachedContextContainer, true); + this.initAttachedContext(this.attachedContextContainer); const data = this.getLayoutData(); diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts index 2bf50f4a..09a83daf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts @@ -3,16 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Action } from 'vs/base/common/actions'; import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; -import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { localize, localize2 } from 'vs/nls'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ILogService } from 'vs/platform/log/common/log'; -import { Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -20,9 +21,7 @@ import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer import { CHAT_VIEW_ID } from 'vs/workbench/contrib/chat/browser/chat'; import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { ChatAgentLocation, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { CONTEXT_CHAT_EXTENSION_INVALID, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IRawChatParticipantContribution } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; -import { showExtensionsWithIdsCommandId } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -161,6 +160,35 @@ const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.regi }, }); +export class ChatCompatibilityNotifier implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.chatCompatNotifier'; + + constructor( + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @INotificationService notificationService: INotificationService, + @ICommandService commandService: ICommandService + ) { + // It may be better to have some generic UI for this, for any extension that is incompatible, + // but this is only enabled for Copilot Chat now and it needs to be obvious. + extensionsWorkbenchService.queryLocal().then(exts => { + const chat = exts.find(ext => ext.identifier.id === 'github.copilot-chat'); + if (chat?.local?.validations.some(v => v[0] === Severity.Error)) { + notificationService.notify({ + severity: Severity.Error, + message: localize('chatFailErrorMessage', "Chat failed to load. Please ensure that the GitHub Copilot Chat extension is up to date."), + actions: { + primary: [ + new Action('showExtension', localize('action.showExtension', "Show Extension"), undefined, true, () => { + return commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', ['GitHub.copilot-chat']); + }) + ] + } + }); + } + }); + } +} + export class ChatExtensionPointHandler implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.chatExtensionPointHandler'; @@ -170,10 +198,9 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { constructor( @IChatAgentService private readonly _chatAgentService: IChatAgentService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, ) { this._viewContainer = this.registerViewContainer(); - this.registerDefaultParticipantView(); this.handleAndRegisterChatExtensions(); } @@ -212,6 +239,11 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { continue; } + const store = new DisposableStore(); + if (providerDescriptor.isDefault && (!providerDescriptor.locations || providerDescriptor.locations?.includes(ChatAgentLocation.Panel))) { + store.add(this.registerDefaultParticipantView(providerDescriptor)); + } + const participantsAndCommandsDisambiguation: { categoryName: string; description: string; @@ -228,7 +260,6 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { } } - const store = new DisposableStore(); store.add(this._chatAgentService.registerAgent( providerDescriptor.id, { @@ -287,9 +318,15 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { return viewContainer; } - private registerDefaultParticipantView(): IDisposable { - // Register View. Name must be hardcoded because we want to show it even when the extension fails to load due to an API version incompatibility. - const name = 'GitHub Copilot'; + private hasRegisteredDefaultParticipantView = false; + private registerDefaultParticipantView(defaultParticipantDescriptor: IRawChatParticipantContribution): IDisposable { + if (this.hasRegisteredDefaultParticipantView) { + this.logService.warn(`Tried to register a second default chat participant view for "${defaultParticipantDescriptor.id}"`); + return Disposable.None; + } + + // Register View + const name = defaultParticipantDescriptor.fullName ?? defaultParticipantDescriptor.name; const viewDescriptor: IViewDescriptor[] = [{ id: CHAT_VIEW_ID, containerIcon: this._viewContainer.icon, @@ -299,11 +336,12 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(ChatViewPane), - when: ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_EXTENSION_INVALID) }]; + this.hasRegisteredDefaultParticipantView = true; Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer); return toDisposable(() => { + this.hasRegisteredDefaultParticipantView = false; Registry.as(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer); }); } @@ -312,39 +350,3 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { function getParticipantKey(extensionId: ExtensionIdentifier, participantName: string): string { return `${extensionId.value}_${participantName}`; } - -export class ChatCompatibilityNotifier implements IWorkbenchContribution { - static readonly ID = 'workbench.contrib.chatCompatNotifier'; - - constructor( - @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @IContextKeyService contextKeyService: IContextKeyService, - @IChatAgentService chatAgentService: IChatAgentService, - ) { - // It may be better to have some generic UI for this, for any extension that is incompatible, - // but this is only enabled for Copilot Chat now and it needs to be obvious. - - const showExtensionLabel = localize('showExtension', "Show Extension"); - const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - viewsRegistry.registerViewWelcomeContent(CHAT_VIEW_ID, { - content: localize('chatFailErrorMessage', "Chat failed to load. Please ensure that the GitHub Copilot Chat extension is up to date.") + `\n\n[${showExtensionLabel}](command:${showExtensionsWithIdsCommandId}?${encodeURIComponent(JSON.stringify([['GitHub.copilot-chat']]))})`, - when: CONTEXT_CHAT_EXTENSION_INVALID, - }); - - const isInvalid = CONTEXT_CHAT_EXTENSION_INVALID.bindTo(contextKeyService); - extensionsWorkbenchService.queryLocal().then(exts => { - const chat = exts.find(ext => ext.identifier.id === 'github.copilot-chat'); - if (chat?.local?.validations.some(v => v[0] === Severity.Error)) { - // This catches vscode starting up with the invalid extension, but the extension may still get updated by vscode after this. - isInvalid.set(true); - } - }); - - const listener = chatAgentService.onDidChangeAgents(() => { - if (chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) { - isInvalid.set(false); - listener.dispose(); - } - }); - } -} diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 68fb88bd..0cb92990 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -88,9 +88,8 @@ export class ChatViewPane extends ViewPane { } else if (this._widget?.viewModel?.initState === ChatModelInitState.Initialized) { // Model is initialized, and the default agent disappeared, so show welcome view this.didUnregisterProvider = true; + this._onDidChangeViewWelcomeState.fire(); } - - this._onDidChangeViewWelcomeState.fire(); })); } @@ -115,10 +114,6 @@ export class ChatViewPane extends ViewPane { } override shouldShowWelcome(): boolean { - if (!this.chatAgentService.getContributedDefaultAgent(ChatAgentLocation.Panel)) { - return true; - } - const noPersistedSessions = !this.chatService.hasSessions(); return this.didUnregisterProvider || !this._widget?.viewModel && (noPersistedSessions || this.didProviderRegistrationFail); } diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 5db94d51..1b53c429 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -770,11 +770,15 @@ export class CodeCompareBlockPart extends Disposable { }); dom.reset(this.messageElement, message); + } const diffData = await data.diffData; + if (!diffData) { + return; + } - if (!isEditApplied && diffData) { + if (!isEditApplied) { const viewModel = this.diffEditor.createViewModel({ original: diffData.original, modified: diffData.modified @@ -797,7 +801,6 @@ export class CodeCompareBlockPart extends Disposable { } else { this.diffEditor.setModel(null); this._lastDiffEditorViewModel.value = undefined; - this._onDidChangeContentHeight.fire(); } this.toolbar.context = { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 5725b2b0..5cc12623 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -24,7 +24,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { asJson, IRequestService } from 'vs/platform/request/common/request'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_CHAT_ENABLED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressResponseContent, IChatRequestVariableData, ISerializableChatAgentData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IRawChatCommandContribution, RawChatParticipantLocation } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; import { IChatFollowup, IChatLocationData, IChatProgress, IChatResponseErrorDetails, IChatTaskDto } from 'vs/workbench/contrib/chat/common/chatService'; @@ -233,13 +233,11 @@ export class ChatAgentService implements IChatAgentService { readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; private readonly _hasDefaultAgent: IContextKey; - private readonly _defaultAgentRegistered: IContextKey; constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { this._hasDefaultAgent = CONTEXT_CHAT_ENABLED.bindTo(this.contextKeyService); - this._defaultAgentRegistered = CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED.bindTo(this.contextKeyService); } registerAgent(id: string, data: IChatAgentData): IDisposable { @@ -248,10 +246,6 @@ export class ChatAgentService implements IChatAgentService { throw new Error(`Agent already registered: ${JSON.stringify(id)}`); } - if (data.isDefault) { - this._defaultAgentRegistered.set(true); - } - const that = this; const commands = data.slashCommands; data = { @@ -262,13 +256,8 @@ export class ChatAgentService implements IChatAgentService { }; const entry = { data }; this._agents.set(id, entry); - this._onDidChangeAgents.fire(undefined); return toDisposable(() => { this._agents.delete(id); - if (data.isDefault) { - this._defaultAgentRegistered.set(false); - } - this._onDidChangeAgents.fire(undefined); }); } @@ -456,7 +445,7 @@ export class ChatAgentService implements IChatAgentService { const participants = this.getAgents().reduce((acc, a) => { acc.push({ participant: a.id, disambiguation: a.disambiguation ?? [] }); for (const command of a.slashCommands) { - acc.push({ participant: a.id, command: command.name, disambiguation: command.disambiguation ?? [] }); + acc.push({ participant: a.id, command: command.name, disambiguation: [] }); } return acc; }, []); diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index f651a114..5930a975 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -25,9 +25,7 @@ export const CONTEXT_CHAT_INPUT_HAS_FOCUS = new RawContextKey('chatInpu export const CONTEXT_IN_CHAT_INPUT = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); export const CONTEXT_IN_CHAT_SESSION = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); -export const CONTEXT_CHAT_ENABLED = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is activated with an implementation.") }); -export const CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED = new RawContextKey('chatPanelParticipantRegistered', false, { type: 'boolean', description: localize('chatParticipantRegistered', "True when a default chat participant is registered for the panel.") }); -export const CONTEXT_CHAT_EXTENSION_INVALID = new RawContextKey('chatExtensionInvalid', false, { type: 'boolean', description: localize('chatExtensionInvalid', "True when the installed chat extension is invalid and needs to be updated.") }); +export const CONTEXT_CHAT_ENABLED = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is registered.") }); export const CONTEXT_CHAT_INPUT_CURSOR_AT_TOP = new RawContextKey('chatCursorAtTop', false); export const CONTEXT_CHAT_INPUT_HAS_AGENT = new RawContextKey('chatInputHasAgent', false); export const CONTEXT_CHAT_LOCATION = new RawContextKey('chatLocation', undefined); diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index b9578bae..14a94c04 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -616,8 +616,6 @@ export type ISerializableChatDataIn = ISerializableChatData1 | ISerializableChat * TODO- ChatModel#_deserialize and reviveSerializedAgent also still do some normalization and maybe that should be done in here too. */ export function normalizeSerializableChatData(raw: ISerializableChatDataIn): ISerializableChatData { - normalizeOldFields(raw); - if (!('version' in raw)) { return { version: 3, @@ -638,30 +636,6 @@ export function normalizeSerializableChatData(raw: ISerializableChatDataIn): ISe return raw; } -function normalizeOldFields(raw: ISerializableChatDataIn): void { - // Fill in fields that very old chat data may be missing - if (!raw.sessionId) { - raw.sessionId = generateUuid(); - } - - if (!raw.creationDate) { - raw.creationDate = getLastYearDate(); - } - - if ('version' in raw && (raw.version === 2 || raw.version === 3)) { - if (!raw.lastMessageDate) { - // A bug led to not porting creationDate properly, and that was copied to lastMessageDate, so fix that up if missing. - raw.lastMessageDate = getLastYearDate(); - } - } -} - -function getLastYearDate(): number { - const lastYearDate = new Date(); - lastYearDate.setFullYear(lastYearDate.getFullYear() - 1); - return lastYearDate.getTime(); -} - export function isExportableSessionData(obj: unknown): obj is IExportableChatData { const data = obj as IExportableChatData; return typeof data === 'object' && diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 2b6cfac7..3ca4524e 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -481,8 +481,9 @@ export class ChatService extends Disposable implements IChatService { } async sendRequest(sessionId: string, request: string, options?: IChatSendRequestOptions): Promise { + this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`); - if (!request.trim() && !options?.slashCommand && !options?.agentId) { + if (!request.trim()) { this.trace('sendRequest', 'Rejected empty message'); return; } @@ -545,7 +546,6 @@ export class ChatService extends Disposable implements IChatService { const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); const agentSlashCommandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); const commandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestSlashCommandPart => r instanceof ChatRequestSlashCommandPart); - const requests = [...model.getRequests()]; let gotProgress = false; const requestType = commandPart ? 'slashCommand' : 'string'; @@ -639,7 +639,7 @@ export class ChatService extends Disposable implements IChatService { if (this.configurationService.getValue('chat.experimental.detectParticipant.enabled') !== false && this.chatAgentService.hasChatParticipantDetectionProviders() && !agentPart && !commandPart && enableCommandDetection) { // We have no agent or command to scope history with, pass the full history to the participant detection provider - const defaultAgentHistory = this.getHistoryEntriesFromModel(requests, model.sessionId, location, defaultAgent.id); + const defaultAgentHistory = this.getHistoryEntriesFromModel(model, location, defaultAgent.id); // Prepare the request object that we will send to the participant detection provider const chatAgentRequest = await prepareChatAgentRequest(defaultAgent, agentSlashCommandPart?.command, enableCommandDetection, undefined, false); @@ -658,7 +658,7 @@ export class ChatService extends Disposable implements IChatService { await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`); // Recompute history in case the agent or command changed - const history = this.getHistoryEntriesFromModel(requests, model.sessionId, location, agent.id); + const history = this.getHistoryEntriesFromModel(model, location, agent.id); const requestProps = await prepareChatAgentRequest(agent, command, enableCommandDetection, request /* Reuse the request object if we already created it for participant detection */, !!detectedAgent); const pendingRequest = this._pendingRequests.get(sessionId); if (pendingRequest && !pendingRequest.requestId) { @@ -668,7 +668,7 @@ export class ChatService extends Disposable implements IChatService { const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, history, followupsCancelToken); - chatTitlePromise = model.getRequests().length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model.getRequests(), model.sessionId, location, agent.id), CancellationToken.None) : undefined; + chatTitlePromise = model.getRequests().length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model, location, agent.id), CancellationToken.None) : undefined; } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { variables: [] }, attempt); completeResponseCreated(); @@ -775,9 +775,9 @@ export class ChatService extends Disposable implements IChatService { }; } - private getHistoryEntriesFromModel(requests: IChatRequestModel[], sessionId: string, location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] { + private getHistoryEntriesFromModel(model: IChatModel, location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] { const history: IChatAgentHistoryEntry[] = []; - for (const request of requests) { + for (const request of model.getRequests()) { if (!request.response) { continue; } @@ -791,7 +791,7 @@ export class ChatService extends Disposable implements IChatService { const promptTextResult = getPromptText(request.message); const historyRequest: IChatAgentRequest = { - sessionId: sessionId, + sessionId: model.sessionId, requestId: request.id, agentId: request.response.agent?.id ?? '', message: promptTextResult.message, diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index b0455f49..bffc94d7 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -941,6 +941,12 @@ export class StopReadAloud extends Action2 { when: ScopedChatSynthesisInProgress, group: 'navigation', order: -1 + }, + { + id: MENU_INLINE_CHAT_WIDGET_SECONDARY, + when: ScopedChatSynthesisInProgress, + group: 'navigation', + order: -1 } ] }); @@ -974,15 +980,6 @@ export class StopReadChatItemAloud extends Action2 { CONTEXT_RESPONSE_FILTERED.negate() // but not when response is filtered ), group: 'navigation' - }, - { - id: MENU_INLINE_CHAT_WIDGET_SECONDARY, - when: ContextKeyExpr.and( - ScopedChatSynthesisInProgress, // only when in progress - CONTEXT_RESPONSE, // only for responses - CONTEXT_RESPONSE_FILTERED.negate() // but not when response is filtered - ), - group: 'navigation' } ] }); diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index c37b05e8..4be9380f 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -17,7 +17,7 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatAgentLocation, ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatModel, ISerializableChatData1, ISerializableChatData2, ISerializableChatData3, normalizeSerializableChatData, Response } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, ISerializableChatData1, ISerializableChatData2, normalizeSerializableChatData, Response } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestTextPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -230,53 +230,4 @@ suite('normalizeSerializableChatData', () => { assert.strictEqual(newData.lastMessageDate, v2Data.lastMessageDate); assert.strictEqual(newData.customTitle, v2Data.computedTitle); }); - - test('old bad data', () => { - const v1Data: ISerializableChatData1 = { - // Testing the scenario where these are missing - sessionId: undefined!, - creationDate: undefined!, - - initialLocation: undefined, - isImported: false, - requesterAvatarIconUri: undefined, - requesterUsername: 'me', - requests: [], - responderAvatarIconUri: undefined, - responderUsername: 'bot', - welcomeMessage: [] - }; - - const newData = normalizeSerializableChatData(v1Data); - assert.strictEqual(newData.version, 3); - assert.ok(newData.creationDate > 0); - assert.ok(newData.lastMessageDate > 0); - assert.ok(newData.sessionId); - }); - - test('v3 with bug', () => { - const v3Data: ISerializableChatData3 = { - // Test case where old data was wrongly normalized and these fields were missing - creationDate: undefined!, - lastMessageDate: undefined!, - - version: 3, - initialLocation: undefined, - isImported: false, - requesterAvatarIconUri: undefined, - requesterUsername: 'me', - requests: [], - responderAvatarIconUri: undefined, - responderUsername: 'bot', - sessionId: 'session1', - welcomeMessage: [], - customTitle: 'computed title' - }; - - const newData = normalizeSerializableChatData(v3Data); - assert.strictEqual(newData.version, 3); - assert.ok(newData.creationDate > 0); - assert.ok(newData.lastMessageDate > 0); - assert.ok(newData.sessionId); - }); }); diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 75deafec..253f7023 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -172,7 +172,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { this.filters = this._register(new CommentsFilters({ showResolved: this.viewState['showResolved'] !== false, showUnresolved: this.viewState['showUnresolved'] !== false, - sortBy: this.viewState['sortBy'] ?? CommentsSortOrder.ResourceAscending, + sortBy: this.viewState['sortBy'], }, this.contextKeyService)); this.filter = new Filter(new FilterOptions(this.filterWidget.getFilterText(), this.filters.showResolved, this.filters.showUnresolved)); diff --git a/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts b/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts index e2494534..8fedca8e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts @@ -7,7 +7,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { CommentsViewFilterFocusContextKey, ICommentsView } from 'vs/workbench/contrib/comments/browser/comments'; import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -74,9 +74,9 @@ export class CommentsFilters extends Disposable { } } - private _sortBy: IContextKey = CONTEXT_KEY_SORT_BY.bindTo(this.contextKeyService); + private _sortBy = CONTEXT_KEY_SORT_BY.bindTo(this.contextKeyService); get sortBy(): CommentsSortOrder { - return this._sortBy.get() ?? CommentsSortOrder.ResourceAscending; + return this._sortBy.get()!; } set sortBy(sortBy: CommentsSortOrder) { if (this._sortBy.get() !== sortBy) { @@ -208,7 +208,7 @@ registerAction2(class extends ViewAction { icon: Codicon.history, viewId: COMMENTS_VIEW_ID, toggled: { - condition: ContextKeyExpr.equals(CONTEXT_KEY_SORT_BY.key, CommentsSortOrder.UpdatedAtDescending), + condition: ContextKeyExpr.equals('commentsView.sortBy', CommentsSortOrder.UpdatedAtDescending), title: localize('sorting by updated at', "Updated Time"), }, menu: { @@ -229,13 +229,13 @@ registerAction2(class extends ViewAction { constructor() { super({ id: `workbench.actions.${COMMENTS_VIEW_ID}.toggleSortByResource`, - title: localize('toggle sorting by resource', "Position in File"), + title: localize('toggle sorting by resource', "File"), category: localize('comments', "Comments"), icon: Codicon.history, viewId: COMMENTS_VIEW_ID, toggled: { - condition: ContextKeyExpr.equals(CONTEXT_KEY_SORT_BY.key, CommentsSortOrder.ResourceAscending), - title: localize('sorting by position in file', "Position in File"), + condition: ContextKeyExpr.equals('commentsView.sortBy', CommentsSortOrder.ResourceAscending), + title: localize('sorting by file', "File"), }, menu: { id: commentSortSubmenu, diff --git a/src/vs/workbench/contrib/debug/browser/callStackWidget.ts b/src/vs/workbench/contrib/debug/browser/callStackWidget.ts index d02de4fb..50cfbf0f 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackWidget.ts @@ -12,24 +12,20 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { autorun, autorunWithStore, derived, IObservable, ISettableObservable, observableValue, transaction } from 'vs/base/common/observable'; +import { autorun, autorunWithStore, derived, IObservable, ISettableObservable, observableValue } from 'vs/base/common/observable'; import { ThemeIcon } from 'vs/base/common/themables'; import { Constants } from 'vs/base/common/uint'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import 'vs/css!./media/callStackWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorContributionCtor, EditorContributionInstantiation, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IWordAtPosition } from 'vs/editor/common/core/wordHelper'; -import { IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { Location } from 'vs/editor/common/languages'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ClickLinkGesture, ClickLinkMouseEvent } from 'vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture'; import { localize, localize2 } from 'vs/nls'; import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; @@ -42,7 +38,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { ResourceLabel } from 'vs/workbench/browser/labels'; import { makeStackFrameColumnDecoration, TOP_STACK_FRAME_DECORATION } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; -import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class CallStackFrame { @@ -101,9 +97,6 @@ class WrappedCustomStackFrame implements IFrameLikeItem { constructor(public readonly original: CustomStackFrame) { } } -const isFrameLike = (item: unknown): item is IFrameLikeItem => - item instanceof WrappedCallStackFrame || item instanceof WrappedCustomStackFrame; - type ListItem = WrappedCallStackFrame | SkippedCallFrames | WrappedCustomStackFrame; const WIDGET_CLASS_NAME = 'multiCallStackWidget'; @@ -144,7 +137,6 @@ export class CallStackWidget extends Disposable { multipleSelectionSupport: false, mouseSupport: false, keyboardSupport: false, - setRowLineHeight: false, accessibilityProvider: instantiationService.createInstance(StackAccessibilityProvider), } ) as WorkbenchList); @@ -165,17 +157,6 @@ export class CallStackWidget extends Disposable { this.layoutEmitter.fire(); } - public collapseAll() { - transaction(tx => { - for (let i = 0; i < this.list.length; i++) { - const frame = this.list.element(i); - if (isFrameLike(frame)) { - frame.collapsed.set(true, tx); - } - } - }); - } - private async loadFrame(replacing: SkippedCallFrames): Promise { if (!this.cts) { return; @@ -375,9 +356,9 @@ abstract class AbstractFrameRenderer item.collapsed.set(!item.collapsed.get(), undefined); - elementStore.add(collapse.onDidClick(toggleCollapse)); - elementStore.add(dom.addDisposableListener(elements.title, 'click', toggleCollapse)); + elementStore.add(collapse.onDidClick(() => { + item.collapsed.set(!item.collapsed.get(), undefined); + })); } disposeElement(element: ListItem, index: number, templateData: T, height: number | undefined): void { @@ -401,33 +382,26 @@ class FrameCodeRenderer extends AbstractFrameRenderer { private readonly containingEditor: ICodeEditor | undefined, private readonly onLayout: Event, @ITextModelService private readonly modelService: ITextModelService, + @ICodeEditorService private readonly editorService: ICodeEditorService, @IInstantiationService instantiationService: IInstantiationService, ) { super(instantiationService); } protected override finishRenderTemplate(data: IAbstractFrameRendererTemplateData): IStackTemplateData { - // override default e.g. language contributions, only allow users to click - // on code in the call stack to go to its source location - const contributions: IEditorContributionDescription[] = [{ - id: ClickToLocationContribution.ID, - instantiation: EditorContributionInstantiation.BeforeFirstInteraction, - ctor: ClickToLocationContribution as EditorContributionCtor, - }]; - const editor = this.containingEditor ? this.instantiationService.createInstance( EmbeddedCodeEditorWidget, data.elements.editor, editorOptions, - { isSimpleWidget: true, contributions }, + { isSimpleWidget: true }, this.containingEditor, ) : this.instantiationService.createInstance( CodeEditorWidget, data.elements.editor, editorOptions, - { isSimpleWidget: true, contributions }, + { isSimpleWidget: true }, ); data.templateStore.add(editor); @@ -449,6 +423,20 @@ class FrameCodeRenderer extends AbstractFrameRenderer { const uri = item.source!; template.label.element.setFile(uri); + template.elements.title.role = 'link'; + elementStore.add(dom.addDisposableListener(template.elements.title, 'click', e => { + this.editorService.openCodeEditor({ + resource: uri, + options: { + selection: Range.fromPositions({ + column: item.column ?? 1, + lineNumber: item.line ?? 1, + }), + selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, + }, + }, this.containingEditor || null, e.ctrlKey || e.metaKey); + })); + const cts = new CancellationTokenSource(); elementStore.add(toDisposable(() => cts.dispose(true))); this.modelService.createModelReference(uri).then(reference => { @@ -644,73 +632,6 @@ class SkippedRenderer implements IListRenderer { } } -/** A simple contribution that makes all data in the editor clickable to go to the location */ -class ClickToLocationContribution extends Disposable implements IEditorContribution { - public static readonly ID = 'clickToLocation'; - private readonly linkDecorations: IEditorDecorationsCollection; - private current: { line: number; word: IWordAtPosition } | undefined; - - constructor( - private readonly editor: ICodeEditor, - @IEditorService editorService: IEditorService, - ) { - super(); - this.linkDecorations = editor.createDecorationsCollection(); - this._register(toDisposable(() => this.linkDecorations.clear())); - - const clickLinkGesture = this._register(new ClickLinkGesture(editor)); - - this._register(clickLinkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => { - this.onMove(mouseEvent); - })); - this._register(clickLinkGesture.onExecute((e) => { - const model = this.editor.getModel(); - if (!this.current || !model) { - return; - } - - editorService.openEditor({ - resource: model.uri, - options: { - selection: Range.fromPositions(new Position(this.current.line, this.current.word.startColumn)), - selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, - }, - }, e.hasSideBySideModifier ? SIDE_GROUP : undefined); - })); - } - - private onMove(mouseEvent: ClickLinkMouseEvent) { - if (!mouseEvent.hasTriggerModifier) { - return this.clear(); - } - - const position = mouseEvent.target.position; - const word = position && this.editor.getModel()?.getWordAtPosition(position); - if (!word) { - return this.clear(); - } - - const prev = this.current?.word; - if (prev && prev.startColumn === word.startColumn && prev.endColumn === word.endColumn && prev.word === word.word) { - return; - } - - this.current = { word, line: position.lineNumber }; - this.linkDecorations.set([{ - range: new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn), - options: { - description: 'call-stack-go-to-file-link', - inlineClassName: 'call-stack-go-to-file-link', - }, - }]); - } - - private clear() { - this.linkDecorations.clear(); - this.current = undefined; - } -} - registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css b/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css index e5e0d56e..60f54930 100644 --- a/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css @@ -24,10 +24,6 @@ &[role="link"] { cursor: pointer; } - - .monaco-icon-label::before { - height: auto; - } } &.collapsed { @@ -43,7 +39,6 @@ .collapse-button { width: 16px; min-height: 1px; /* show even if empty */ - line-height: 0; a { cursor: pointer; @@ -61,11 +56,6 @@ .multiCallStackWidget { .multiCallStackFrameContainer { background: none !important; + line-height: inherit !important; } } - -.monaco-editor .call-stack-go-to-file-link { - text-decoration: underline; - cursor: pointer; - color: var(--vscode-editorLink-activeForeground) !important; -} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index f2eea7c6..1e8ef29a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -79,6 +79,7 @@ class ExtensionsViewState extends Disposable implements IExtensionsViewState { } } + export interface ExtensionsListViewOptions { server?: IExtensionManagementServer; flexibleHeight?: boolean; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 22d6a83f..8119cdd0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -11,7 +11,7 @@ import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/em import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, MENU_INLINE_CHAT_CONTENT_STATUS, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE, ACTION_DISCARD_CHANGES } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, MENU_INLINE_CHAT_CONTENT_STATUS, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { localize, localize2 } from 'vs/nls'; import { Action2, IAction2Options } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -287,7 +287,7 @@ export class DiscardHunkAction extends AbstractInlineChatAction { constructor() { super({ - id: ACTION_DISCARD_CHANGES, + id: 'inlineChat.discardHunkChange', title: localize('discard', 'Discard'), icon: Codicon.chromeClose, precondition: CTX_INLINE_CHAT_VISIBLE, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index c6d9ae57..29b4dee9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -39,7 +39,7 @@ import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatContentWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget'; -import { HunkInformation, HunkState, Session, StashedSession } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { HunkInformation, Session, StashedSession } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatError } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; import { EditModeStrategy, HunkAction, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -622,7 +622,6 @@ export class InlineChatController implements IEditorContribution { return; } if (e.kind === 'move') { - assertType(this._session); const log: typeof this._log = (msg: string, ...args: any[]) => this._log('state=_showRequest) moving inline chat', msg, ...args); log('move was requested', e.target, e.range); @@ -637,13 +636,13 @@ export class InlineChatController implements IEditorContribution { } const newEditor = editorPane.getControl(); - if (!isCodeEditor(newEditor) || !newEditor.hasModel()) { + if (!newEditor || !isCodeEditor(newEditor) || !newEditor.hasModel()) { log('new editor is either missing or not a code editor or does not have a model'); return; } - if (this._inlineChatSessionService.getSession(newEditor, e.target)) { - log('new editor ALREADY has a session'); + if (!this._session) { + log('controller does not have a session'); return; } @@ -742,7 +741,7 @@ export class InlineChatController implements IEditorContribution { await responsePromise.p; await progressiveEditsQueue.whenIdle(); - if (response.result?.errorDetails) { + if (response.isCanceled) { await this._session.undoChangesUntil(response.requestId); } @@ -759,6 +758,7 @@ export class InlineChatController implements IEditorContribution { if (response.result?.errorDetails) { // + await this._session.undoChangesUntil(response.requestId); } else if (response.response.value.length === 0) { // empty -> show message @@ -990,21 +990,8 @@ export class InlineChatController implements IEditorContribution { // ---- controller API showSaveHint(): void { - if (!this._session) { - return; - } - - const status = localize('savehint', "Accept or discard changes to continue saving."); + const status = localize('savehint', "Accept or discard changes to continue saving"); this._ui.value.zone.widget.updateStatus(status, { classes: ['warn'] }); - - if (this._ui.value.zone.position) { - this._editor.revealLineInCenterIfOutsideViewport(this._ui.value.zone.position.lineNumber); - } else { - const hunk = this._session.hunkData.getInfo().find(info => info.getState() === HunkState.Pending); - if (hunk) { - this._editor.revealLineInCenterIfOutsideViewport(hunk.getRangesN()[0].startLineNumber); - } - } } acceptInput() { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index f5b111e5..a3a6a25a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -263,7 +263,7 @@ export class StashedSession { // keep session for a little bit, only release when user continues to work (type, move cursor, etc.) this._session = session; this._ctxHasStashedSession.set(true); - this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel, editor.onDidBlurEditorWidget))(() => { + this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel))(() => { this._session = undefined; this._sessionService.releaseSession(session); this._ctxHasStashedSession.reset(); @@ -360,30 +360,27 @@ export class HunkData { // mirror textModelN changes to textModel0 execept for those that // overlap with a hunk - type HunkRangePair = { rangeN: Range; range0: Range; markAccepted: () => void }; + type HunkRangePair = { rangeN: Range; range0: Range }; const hunkRanges: HunkRangePair[] = []; const ranges0: Range[] = []; - for (const entry of this._data.values()) { + for (const { textModelNDecorations, textModel0Decorations, state } of this._data.values()) { - if (entry.state === HunkState.Pending) { + if (state === HunkState.Pending) { // pending means the hunk's changes aren't "sync'd" yet - for (let i = 1; i < entry.textModelNDecorations.length; i++) { - const rangeN = this._textModelN.getDecorationRange(entry.textModelNDecorations[i]); - const range0 = this._textModel0.getDecorationRange(entry.textModel0Decorations[i]); + for (let i = 1; i < textModelNDecorations.length; i++) { + const rangeN = this._textModelN.getDecorationRange(textModelNDecorations[i]); + const range0 = this._textModel0.getDecorationRange(textModel0Decorations[i]); if (rangeN && range0) { - hunkRanges.push({ - rangeN, range0, - markAccepted: () => entry.state = HunkState.Accepted - }); + hunkRanges.push({ rangeN, range0 }); } } - } else if (entry.state === HunkState.Accepted) { + } else if (state === HunkState.Accepted) { // accepted means the hunk's changes are also in textModel0 - for (let i = 1; i < entry.textModel0Decorations.length; i++) { - const range = this._textModel0.getDecorationRange(entry.textModel0Decorations[i]); + for (let i = 1; i < textModel0Decorations.length; i++) { + const range = this._textModel0.getDecorationRange(textModel0Decorations[i]); if (range) { ranges0.push(range); } @@ -402,20 +399,16 @@ export class HunkData { let pendingChangesLen = 0; - for (const entry of hunkRanges) { - if (entry.rangeN.getEndPosition().isBefore(Range.getStartPosition(change.range))) { + for (const { rangeN, range0 } of hunkRanges) { + if (rangeN.getEndPosition().isBefore(Range.getStartPosition(change.range))) { // pending hunk _before_ this change. When projecting into textModel0 we need to // subtract that. Because diffing is relaxed it might include changes that are not // actual insertions/deletions. Therefore we need to take the length of the original // range into account. - pendingChangesLen += this._textModelN.getValueLengthInRange(entry.rangeN); - pendingChangesLen -= this._textModel0.getValueLengthInRange(entry.range0); + pendingChangesLen += this._textModelN.getValueLengthInRange(rangeN); + pendingChangesLen -= this._textModel0.getValueLengthInRange(range0); - } else if (Range.areIntersectingOrTouching(entry.rangeN, change.range)) { - // an edit overlaps with a (pending) hunk. We take this as a signal - // to mark the hunk as accepted and to ignore the edit. The range of the hunk - // will be up-to-date because of decorations created for them - entry.markAccepted(); + } else if (Range.areIntersectingOrTouching(rangeN, change.range)) { isOverlapping = true; break; @@ -454,23 +447,24 @@ export class HunkData { diff ??= await this._editorWorkerService.computeDiff(this._textModel0.uri, this._textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }, 'advanced'); - let mergedChanges: DetailedLineRangeMapping[] = []; + if (!diff || diff.changes.length === 0) { + // return new HunkData([], session); + return; + } - if (diff && diff.changes.length > 0) { - // merge changes neighboring changes - mergedChanges = [diff.changes[0]]; - for (let i = 1; i < diff.changes.length; i++) { - const lastChange = mergedChanges[mergedChanges.length - 1]; - const thisChange = diff.changes[i]; - if (thisChange.modified.startLineNumber - lastChange.modified.endLineNumberExclusive <= HunkData._HUNK_THRESHOLD) { - mergedChanges[mergedChanges.length - 1] = new DetailedLineRangeMapping( - lastChange.original.join(thisChange.original), - lastChange.modified.join(thisChange.modified), - (lastChange.innerChanges ?? []).concat(thisChange.innerChanges ?? []) - ); - } else { - mergedChanges.push(thisChange); - } + // merge changes neighboring changes + const mergedChanges = [diff.changes[0]]; + for (let i = 1; i < diff.changes.length; i++) { + const lastChange = mergedChanges[mergedChanges.length - 1]; + const thisChange = diff.changes[i]; + if (thisChange.modified.startLineNumber - lastChange.modified.endLineNumberExclusive <= HunkData._HUNK_THRESHOLD) { + mergedChanges[mergedChanges.length - 1] = new DetailedLineRangeMapping( + lastChange.original.join(thisChange.original), + lastChange.modified.join(thisChange.modified), + (lastChange.innerChanges ?? []).concat(thisChange.innerChanges ?? []) + ); + } else { + mergedChanges.push(thisChange); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 01df414e..c44f6e6e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -8,7 +8,7 @@ import { coalesceInPlace } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { themeColorFromId, ThemeIcon } from 'vs/base/common/themables'; +import { themeColorFromId } from 'vs/base/common/themables'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; import { LineSource, RenderOptions, renderLines } from 'vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines'; @@ -27,7 +27,7 @@ import { SaveReason } from 'vs/workbench/common/editor'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { HunkInformation, Session, HunkState } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatZoneWidget } from './inlineChatZoneWidget'; -import { ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, MENU_INLINE_CHAT_ZONE, minimapInlineChatDiffInserted, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, MENU_INLINE_CHAT_ZONE, minimapInlineChatDiffInserted, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { assertType } from 'vs/base/common/types'; import { IModelService } from 'vs/editor/common/services/model'; import { performAsyncTextEdit, asProgressiveEdit } from './utils'; @@ -43,9 +43,6 @@ import { generateUuid } from 'vs/base/common/uuid'; import { MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Iterable } from 'vs/base/common/iterator'; -import { ConflictActionsFactory, IContentWidgetAction } from 'vs/workbench/contrib/mergeEditor/browser/view/conflictActions'; -import { observableValue } from 'vs/base/common/observable'; -import { IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; export interface IEditObserver { start(): void; @@ -219,10 +216,8 @@ type HunkDisplayData = { decorationIds: string[]; - diffViewZoneId: string | undefined; - diffViewZone: IViewZone; - - lensActionsViewZoneIds?: string[]; + viewZoneId: string | undefined; + viewZone: IViewZone; distance: number; position: Position; @@ -262,7 +257,6 @@ export class LiveStrategy extends EditModeStrategy { private readonly _ctxCurrentChangeShowsDiff: IContextKey; private readonly _progressiveEditingDecorations: IEditorDecorationsCollection; - private readonly _lensActionsFactory: ConflictActionsFactory; private _editCount: number = 0; constructor( @@ -274,8 +268,6 @@ export class LiveStrategy extends EditModeStrategy { @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IConfigurationService private readonly _configService: IConfigurationService, - @IMenuService private readonly _menuService: IMenuService, - @IContextKeyService private readonly _contextService: IContextKeyService, @ITextFileService textFileService: ITextFileService, @IInstantiationService instaService: IInstantiationService ) { @@ -284,7 +276,6 @@ export class LiveStrategy extends EditModeStrategy { this._ctxCurrentChangeShowsDiff = CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF.bindTo(contextKeyService); this._progressiveEditingDecorations = this._editor.createDecorationsCollection(); - this._lensActionsFactory = this._store.add(new ConflictActionsFactory(this._editor)); } @@ -496,95 +487,45 @@ export class LiveStrategy extends EditModeStrategy { afterLineNumber: -1, heightInLines: result.heightInLines, domNode, - ordinal: 50000 + 2 // more than https://github.com/microsoft/vscode/blob/bf52a5cfb2c75a7327c9adeaefbddc06d529dcad/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts#L42 + ordinal: 50000 + 1 // more than https://github.com/microsoft/vscode/blob/bf52a5cfb2c75a7327c9adeaefbddc06d529dcad/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts#L42 }; const toggleDiff = () => { const scrollState = StableEditorScrollState.capture(this._editor); changeDecorationsAndViewZones(this._editor, (_decorationsAccessor, viewZoneAccessor) => { assertType(data); - if (!data.diffViewZoneId) { + if (!data.viewZoneId) { const [hunkRange] = hunkData.getRangesN(); viewZoneData.afterLineNumber = hunkRange.startLineNumber - 1; - data.diffViewZoneId = viewZoneAccessor.addZone(viewZoneData); + data.viewZoneId = viewZoneAccessor.addZone(viewZoneData); overlay?.updateExtraTop(result.heightInLines); } else { - viewZoneAccessor.removeZone(data.diffViewZoneId!); + viewZoneAccessor.removeZone(data.viewZoneId!); overlay?.updateExtraTop(0); - data.diffViewZoneId = undefined; + data.viewZoneId = undefined; } }); - this._ctxCurrentChangeShowsDiff.set(typeof data?.diffViewZoneId === 'string'); + this._ctxCurrentChangeShowsDiff.set(typeof data?.viewZoneId === 'string'); scrollState.restore(this._editor); }; - const overlay = this._showOverlayToolbar && false + const overlay = this._showOverlayToolbar ? this._instaService.createInstance(InlineChangeOverlay, this._editor, hunkData) : undefined; - - let lensActions: DisposableStore | undefined; - const lensActionsViewZoneIds: string[] = []; - - if (this._showOverlayToolbar && hunkData.getState() === HunkState.Pending) { - - lensActions = new DisposableStore(); - - const menu = this._menuService.createMenu(MENU_INLINE_CHAT_ZONE, this._contextService); - const makeActions = () => { - const actions: IContentWidgetAction[] = []; - const tuples = menu.getActions(); - for (const [, group] of tuples) { - for (const item of group) { - if (item instanceof MenuItemAction) { - - let text = item.label; - - if (item.id === ACTION_TOGGLE_DIFF) { - text = item.checked ? 'Hide Changes' : 'Show Changes'; - } else if (ThemeIcon.isThemeIcon(item.item.icon)) { - text = `$(${item.item.icon.id}) ${text}`; - } - - actions.push({ - text, - tooltip: item.tooltip, - action: async () => item.run(), - }); - } - } - } - return actions; - }; - - const obs = observableValue(this, makeActions()); - lensActions.add(menu.onDidChange(() => obs.set(makeActions(), undefined))); - lensActions.add(menu); - - lensActions.add(this._lensActionsFactory.createWidget(viewZoneAccessor, - hunkRanges[0].startLineNumber - 1, - obs, - lensActionsViewZoneIds - )); - } - const remove = () => { changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { assertType(data); for (const decorationId of data.decorationIds) { decorationsAccessor.removeDecoration(decorationId); } - if (data.diffViewZoneId) { - viewZoneAccessor.removeZone(data.diffViewZoneId); + if (data.viewZoneId) { + viewZoneAccessor.removeZone(data.viewZoneId); } data.decorationIds = []; - data.diffViewZoneId = undefined; - - data.lensActionsViewZoneIds?.forEach(viewZoneAccessor.removeZone); - data.lensActionsViewZoneIds = undefined; + data.viewZoneId = undefined; }); - lensActions?.dispose(); overlay?.dispose(); }; @@ -607,9 +548,8 @@ export class LiveStrategy extends EditModeStrategy { data = { hunk: hunkData, decorationIds, - diffViewZoneId: '', - diffViewZone: viewZoneData, - lensActionsViewZoneIds, + viewZoneId: '', + viewZone: viewZoneData, distance: myDistance, position: hunkRanges[0].getStartPosition().delta(-1), acceptHunk, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index e0423850..252534e6 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -96,9 +96,9 @@ export class InlineChatWidget { h('div.accessibleViewer@accessibleViewer'), h('div.status@status', [ h('div.label.info.hidden@infoLabel'), - h('div.actions.hidden@toolbar1'), + h('div.actions.button-style.hidden@toolbar1'), h('div.label.status.hidden@statusLabel'), - h('div.actions.secondary.hidden@toolbar2'), + h('div.actions.button-style.hidden@toolbar2'), ]), ] ); @@ -385,7 +385,7 @@ export class InlineChatWidget { } protected _getExtraHeight(): number { - return 2 /*border*/ + 4 /*shadow*/; + return 4 /* padding */ + 2 /*border*/ + 4 /*shadow*/; } get value(): string { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts index 42612f49..1ce7b490 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableListener, Dimension } from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -29,7 +29,6 @@ export class InlineChatZoneWidget extends ZoneWidget { readonly widget: EditorBasedInlineChatWidget; - private readonly _scrollUp = this._disposables.add(new ScrollUpState(this.editor)); private readonly _ctxCursorPosition: IContextKey<'above' | 'below' | ''>; private _dimension?: Dimension; @@ -166,7 +165,6 @@ export class InlineChatZoneWidget extends ZoneWidget { this.widget.focus(); revealZone(); - this._scrollUp.enable(); } override updatePositionAndHeight(position: Position): void { @@ -188,15 +186,14 @@ export class InlineChatZoneWidget extends ZoneWidget { return isResponseVM(candidate) && candidate.response.value.length > 0; }); - if (hasResponse && zoneTop < scrollTop || this._scrollUp.didScrollUp) { + if (hasResponse && zoneTop < scrollTop) { // don't reveal the zone if it is already out of view (unless we are still getting ready) - // or if an outside scroll-up happened (e.g the user scrolled up to see the new content) - return this._scrollUp.runIgnored(() => { + return () => { scrollState.restore(this.editor); - }); + }; } - return this._scrollUp.runIgnored(() => { + return () => { scrollState.restore(this.editor); const scrollTop = this.editor.getScrollTop(); @@ -219,7 +216,7 @@ export class InlineChatZoneWidget extends ZoneWidget { this._logService.trace('[IE] REVEAL zone', { zoneTop, lineTop, lineBottom, scrollTop, newScrollTop, forceScrollTop }); this.editor.setScrollTop(newScrollTop, ScrollType.Immediate); } - }); + }; } protected override revealRange(range: Range, isLastLine: boolean): void { @@ -232,7 +229,6 @@ export class InlineChatZoneWidget extends ZoneWidget { override hide(): void { const scrollState = StableEditorBottomScrollState.capture(this.editor); - this._scrollUp.disable(); this._ctxCursorPosition.reset(); this.widget.reset(); this.widget.chatWidget.setVisible(false); @@ -241,54 +237,3 @@ export class InlineChatZoneWidget extends ZoneWidget { scrollState.restore(this.editor); } } - -class ScrollUpState { - - private _lastScrollTop: number = this._editor.getScrollTop(); - private _didScrollUp?: boolean; - private _ignoreEvents = false; - - private readonly _listener = new MutableDisposable(); - - constructor(private readonly _editor: ICodeEditor) { } - - dispose(): void { - this._listener.dispose(); - } - - enable(): void { - this._didScrollUp = undefined; - this._listener.value = this._editor.onDidScrollChange(e => { - if (!e.scrollTopChanged || this._ignoreEvents) { - return; - } - const currentScrollTop = e.scrollTop; - if (currentScrollTop > this._lastScrollTop) { - this._listener.clear(); - this._didScrollUp = true; - } - this._lastScrollTop = currentScrollTop; - }); - } - - disable(): void { - this._listener.clear(); - this._didScrollUp = undefined; - } - - runIgnored(callback: () => void): () => void { - return () => { - this._ignoreEvents = true; - try { - return callback(); - } finally { - this._ignoreEvents = false; - } - }; - } - - get didScrollUp(): boolean | undefined { - return this._didScrollUp; - } - -} diff --git a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css index d8fac566..f209158f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css @@ -20,7 +20,7 @@ } .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part { - padding: 2px 6px 0 6px; + padding: 4px 6px 0 6px; } .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part .interactive-execute-toolbar { @@ -32,12 +32,6 @@ border-radius: 2px; } - -.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part .interactive-input-followups .interactive-session-followups { - margin: 2px 0 0 4px; -} - - .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-list { padding: 4px 0 0 0; } @@ -76,15 +70,7 @@ display: flex; justify-content: space-between; align-items: center; - padding-left: 6px; - padding-right: 6px; -} - -.monaco-workbench .inline-chat > .status { - .label, - .actions { - padding-top: 6px; - } + padding: 6px 6px 0 6px } .monaco-workbench .inline-chat .status .actions.hidden { @@ -106,8 +92,7 @@ .monaco-workbench .inline-chat .status .label.status { margin-left: auto; - padding-right: 6px; - padding-left: 6px; + padding: 0 6px; } .monaco-workbench .inline-chat .status .label.hidden, @@ -171,16 +156,6 @@ gap: 4px; } -.monaco-workbench .inline-chat .status .actions.secondary { - display: none; -} - -.monaco-workbench .inline-chat .status:hover .actions.secondary, -.monaco-workbench .inline-chat:focus .status .actions.secondary, -.monaco-workbench .inline-chat .status:focus-within .actions.secondary { - display: inherit; -} - .monaco-workbench .inline-chat-diff-overlay { .monaco-button { diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index f4c87af0..d656e6c3 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -116,7 +116,6 @@ export const CTX_INLINE_CHAT_RESPONSE_TYPE = new RawContextKey() { - override get id() { return 'one'; } - }; - session.markModelVersion(fakeRequest); - - assert.strictEqual(editor.getModel().getLineCount(), 15); - - await makeEditAsAi([EditOperation.replace(new Range(7, 1, 7, Number.MAX_SAFE_INTEGER), `error = error.replace( - /See https:\/\/github\.com\/Squirrel\/Squirrel\.Mac\/issues\/182 for more information/, - 'This might mean the application was put on quarantine by macOS. See [this link](https://github.com/microsoft/vscode/issues/7426#issuecomment-425093469) for more information' - );`)]); - - assert.strictEqual(editor.getModel().getLineCount(), 18); - - // called when a response errors out - await session.undoChangesUntil(fakeRequest.id); - await session.hunkData.recompute({ applied: 0, sha1: 'fakeSha1' }, undefined); - - assert.strictEqual(editor.getModel().getValue(), origValue); - - session.hunkData.discardAll(); // called when dimissing the session - assert.strictEqual(editor.getModel().getValue(), origValue); - }); - - test('Apply Code\'s preview should be easier to undo/esc #7537', async function () { - model.setValue(`export function fib(n) { - if (n <= 0) return 0; - if (n === 1) return 0; - if (n === 2) return 1; - return fib(n - 1) + fib(n - 2); -}`); - const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); - assertType(session); - - await makeEditAsAi([EditOperation.replace(new Range(5, 1, 6, Number.MAX_SAFE_INTEGER), ` - let a = 0, b = 1, c; - for (let i = 3; i <= n; i++) { - c = a + b; - a = b; - b = c; - } - return b; -}`)]); - - assert.strictEqual(session.hunkData.size, 1); - assert.strictEqual(session.hunkData.pending, 1); - assert.ok(session.hunkData.getInfo().every(d => d.getState() === HunkState.Pending)); - - await assertSnapshot(editor.getModel().getValue(), { name: '1' }); - - await model.undo(); - await assertSnapshot(editor.getModel().getValue(), { name: '2' }); - - // overlapping edits (even UNDO) mark edits as accepted - assert.strictEqual(session.hunkData.size, 1); - assert.strictEqual(session.hunkData.pending, 0); - assert.ok(session.hunkData.getInfo().every(d => d.getState() === HunkState.Accepted)); - - // no further change when discarding - session.hunkData.discardAll(); // CANCEL - await assertSnapshot(editor.getModel().getValue(), { name: '2' }); - }); - }); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts index 3290793d..8d88a891 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts @@ -54,8 +54,8 @@ export class ConflictActionsFactory extends Disposable { newStyle += `${this._styleClassName} { font-family: var(${fontFamilyVar}), ${EDITOR_FONT_DEFAULTS.fontFamily}}`; } this._styleElement.textContent = newStyle; - this._editor.getContainerDomNode().style?.setProperty(fontFamilyVar, fontFamily ?? 'inherit'); - this._editor.getContainerDomNode().style?.setProperty(fontFeaturesVar, editorFontInfo.fontFeatureSettings); + this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily ?? 'inherit'); + this._editor.getContainerDomNode().style.setProperty(fontFeaturesVar, editorFontInfo.fontFeatureSettings); } private _getLayoutInfo() { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/fixedZoneWidget.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/fixedZoneWidget.ts index 27a48ee2..3175a1ca 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/fixedZoneWidget.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/fixedZoneWidget.ts @@ -33,7 +33,6 @@ export abstract class FixedZoneWidget extends Disposable { domNode: document.createElement('div'), afterLineNumber: afterLineNumber, heightInPx: height, - ordinal: 50000 + 1, onComputedHeight: (height) => { this.widgetDomNode.style.height = `${height}px`; }, diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index a8f2c46c..a2adaf3b 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -1074,7 +1074,7 @@ export class DeletedElement extends SingleSideDiffElement { layout(state: IDiffElementLayoutState) { DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._diffEditorContainer), () => { - if ((state.editorHeight || state.outerWidth) && this._editor) { + if (state.editorHeight || state.outerWidth) { this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; this._editor.layout({ width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), @@ -1254,7 +1254,7 @@ export class InsertElement extends SingleSideDiffElement { layout(state: IDiffElementLayoutState) { DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._diffEditorContainer), () => { - if ((state.editorHeight || state.outerWidth) && this._editor) { + if (state.editorHeight || state.outerWidth) { this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; this._editor.layout({ width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), @@ -1644,7 +1644,7 @@ export class ModifiedElement extends AbstractElementRenderer { { updateInfoRendering: () => renderSourceEditor(), checkIfModified: (cell) => { - return cell.modified?.textModel.getTextBufferHash() !== cell.original?.textModel.getTextBufferHash() ? { reason: undefined } : false; + return cell.modified?.textModel.getValue() !== cell.original?.textModel.getValue() ? { reason: undefined } : false; }, getFoldingState: (cell) => cell.cellFoldingState, updateFoldingState: (cell, state) => cell.cellFoldingState = state, @@ -1660,7 +1660,7 @@ export class ModifiedElement extends AbstractElementRenderer { const scopedContextKeyService = this.contextKeyService.createScoped(this.templateData.inputToolbarContainer); this._register(scopedContextKeyService); const inputChanged = NOTEBOOK_DIFF_CELL_INPUT.bindTo(scopedContextKeyService); - inputChanged.set(this.cell.modified.textModel.getTextBufferHash() !== this.cell.original.textModel.getTextBufferHash()); + inputChanged.set(this.cell.modified.textModel.getValue() !== this.cell.original.textModel.getValue()); const ignoreWhitespace = NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE.bindTo(scopedContextKeyService); const ignore = this.textConfigurationService.getValue(this.cell.modified.uri, 'diffEditor.ignoreTrimWhitespace'); @@ -1675,7 +1675,7 @@ export class ModifiedElement extends AbstractElementRenderer { const refreshToolbar = () => { const ignore = this.textConfigurationService.getValue(this.cell.modified.uri, 'diffEditor.ignoreTrimWhitespace'); ignoreWhitespace.set(ignore); - const hasChanges = this.cell.modified.textModel.getTextBufferHash() !== this.cell.original.textModel.getTextBufferHash(); + const hasChanges = this.cell.modified.textModel.getValue() !== this.cell.original.textModel.getValue(); inputChanged.set(hasChanges); if (hasChanges) { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts index ad8e1461..9d0cf11b 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts @@ -222,7 +222,7 @@ export abstract class DiffElementCellViewModelBase extends DiffElementViewModelB layoutState: CellLayoutState.Uninitialized }; - this.cellFoldingState = modified?.getTextBufferHash() !== original?.getTextBufferHash() ? PropertyFoldingState.Expanded : PropertyFoldingState.Collapsed; + this.cellFoldingState = modified?.textModel?.getValue() !== original?.textModel?.getValue() ? PropertyFoldingState.Expanded : PropertyFoldingState.Collapsed; this.metadataFoldingState = PropertyFoldingState.Collapsed; this.outputFoldingState = PropertyFoldingState.Collapsed; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel.ts index dd2b3f12..0c82062b 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel.ts @@ -441,12 +441,16 @@ function createDiffViewModels(instantiationService: IInstantiationService, confi ); } case 'unchanged': { + const originalCell = originalModel.cells[diff.originalCellIndex]; + const modifiedCell = modifiedModel.cells[diff.modifiedCellIndex]; + const type = (originalCell.textModel?.getValue() !== modifiedCell.textModel?.getValue()) ? 'modified' : 'unchanged'; return new SideBySideDiffElementViewModel( model.modified.notebook, model.original.notebook, - originalModel.cells[diff.originalCellIndex], - modifiedModel.cells[diff.modifiedCellIndex], - 'unchanged', eventDispatcher, + originalCell, + modifiedCell, + type, + eventDispatcher, initData, notebookService ); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index e83261cf..3d7310c6 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -419,10 +419,6 @@ export class NotebookCellTextModel extends Disposable implements ICell { return false; } - if (this.outputs.length !== b.outputs.length) { - return false; - } - if (this.getTextLength() !== b.getTextLength()) { return false; } diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 69060d9d..ba24fdfa 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -314,7 +314,7 @@ class HistoryItemRenderer implements ITreeRenderer labels.includes(l.title)); - if (historyItemLabels.length > 0) { + if (historyItemLabels) { const historyItemGroupLocalColor = colorTheme.getColor(historyItemGroupLocal); const historyItemGroupRemoteColor = colorTheme.getColor(historyItemGroupRemote); const historyItemGroupBaseColor = colorTheme.getColor(historyItemGroupBase); diff --git a/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts b/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts index 5265cf2c..4ef1cdae 100644 --- a/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts +++ b/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts @@ -117,21 +117,22 @@ export class NotebookSearchService implements INotebookSearchService { } private async doesFileExist(includes: string[], folderQueries: IFolderQuery[], token: CancellationToken): Promise { - const promises: Promise[] = includes.map(async includePattern => { + const promises: Promise[] = includes.map(async includePattern => { const query = this.queryBuilder.file(folderQueries.map(e => e.folder), { includePattern: includePattern.startsWith('/') ? includePattern : '**/' + includePattern, // todo: find cleaner way to ensure that globs match all appropriate filetypes - exists: true, - onlyFileScheme: true, + exists: true }); return this.searchService.fileSearch( query, token ).then((ret) => { - return !!ret.limitHit; + if (!ret.limitHit) { + throw Error('File not found'); + } }); }); - return Promise.any(promises); + return Promise.any(promises).then(() => true).catch(() => false); } private async getClosedNotebookResults(textQuery: ITextQuery, scannedFiles: ResourceSet, token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index fbeaa22f..92c0e07f 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -328,10 +328,10 @@ __vsc_restore_exit_code() { __vsc_prompt_cmd_original() { __vsc_status="$?" - builtin local cmd __vsc_restore_exit_code "${__vsc_status}" # Evaluate the original PROMPT_COMMAND similarly to how bash would normally # See https://unix.stackexchange.com/a/672843 for technique + builtin local cmd for cmd in "${__vsc_original_prompt_command[@]}"; do eval "${cmd:-}" done diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 index 444df384..c624a5f0 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 @@ -172,9 +172,9 @@ function Set-MappedKeyHandler { function Get-KeywordCompletionResult( $Keyword, - $Description = $Keyword + $Description = $null ) { - [System.Management.Automation.CompletionResult]::new($Keyword, $Keyword, [System.Management.Automation.CompletionResultType]::Keyword, $Description) + [System.Management.Automation.CompletionResult]::new($Keyword, $Keyword, [System.Management.Automation.CompletionResultType]::Keyword, $null -ne $Description ? $Description : $Keyword) } function Set-MappedKeyHandlers { @@ -360,7 +360,7 @@ function Send-Completions { # completions are consistent regardless of where it was requested elseif ($lastWord -match '[/\\]') { $lastSlashIndex = $completionPrefix.LastIndexOfAny(@('/', '\')) - if ($lastSlashIndex -ne -1 -and $lastSlashIndex -lt $cursorIndex) { + if ($lastSlashIndex -ne -1 && $lastSlashIndex -lt $cursorIndex) { $newCursorIndex = $lastSlashIndex + 1 $completionPrefix = $completionPrefix.Substring(0, $newCursorIndex) $prefixCursorDelta = $cursorIndex - $newCursorIndex @@ -388,9 +388,9 @@ function Send-Completions { if ($completions.CompletionMatches.Count -gt 0 -and $completions.CompletionMatches.Where({ $_.ResultType -eq 3 -or $_.ResultType -eq 4 })) { # Add `../ relative to the top completion $firstCompletion = $completions.CompletionMatches[0] - if ($firstCompletion.CompletionText.StartsWith("..$([System.IO.Path]::DirectorySeparatorChar)")) { - if ($completionPrefix -match "(\.\.\$([System.IO.Path]::DirectorySeparatorChar))+") { - $parentDir = "$($matches[0])..$([System.IO.Path]::DirectorySeparatorChar)" + if ($firstCompletion.CompletionText.StartsWith('../')) { + if ($completionPrefix -match '(\.\.\/)+') { + $parentDir = "$($matches[0])../" $currentPath = Split-Path -Parent $firstCompletion.ToolTip try { $parentDirPath = Split-Path -Parent $currentPath @@ -430,7 +430,7 @@ function Send-Completions { # completions are consistent regardless of where it was requested if ($completionPrefix -match '[/\\]') { $lastSlashIndex = $completionPrefix.LastIndexOfAny(@('/', '\')) - if ($lastSlashIndex -ne -1 -and $lastSlashIndex -lt $cursorIndex) { + if ($lastSlashIndex -ne -1 && $lastSlashIndex -lt $cursorIndex) { $newCursorIndex = $lastSlashIndex + 1 $completionPrefix = $completionPrefix.Substring(0, $newCursorIndex) $prefixCursorDelta = $cursorIndex - $newCursorIndex diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index ff8607f6..f38b9bf8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -2305,7 +2305,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { async handleMouseEvent(event: MouseEvent, contextMenu: IMenu): Promise<{ cancelContextMenu: boolean } | void> { // Don't handle mouse event if it was on the scroll bar - if (dom.isHTMLElement(event.target) && (event.target.classList.contains('scrollbar') || event.target.classList.contains('slider'))) { + if (dom.isHTMLElement(event.target) && event.target.classList.contains('scrollbar')) { return { cancelContextMenu: true }; } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index 433de4ee..9ef44f9b 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -9,7 +9,6 @@ import { AutoOpenBarrier } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { isWindows } from 'vs/base/common/platform'; import { localize2 } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey'; @@ -171,20 +170,16 @@ class TerminalSuggestContribution extends DisposableStore implements ITerminalCo // If completions are requested, pause and queue input events until completions are // received. This fixing some problems in PowerShell, particularly enter not executing - // when typing quickly and some characters being printed twice. On Windows this isn't - // needed because inputs are _not_ echoed when not handled immediately. - // TODO: This should be based on the OS of the pty host, not the client - if (!isWindows) { - let barrier: AutoOpenBarrier | undefined; - this.add(addon.onDidRequestCompletions(() => { - barrier = new AutoOpenBarrier(2000); - this._instance.pauseInputEvents(barrier); - })); - this.add(addon.onDidReceiveCompletions(() => { - barrier?.open(); - barrier = undefined; - })); - } + // when typing quickly and some characters being printed twice. + let barrier: AutoOpenBarrier | undefined; + this.add(addon.onDidRequestCompletions(() => { + barrier = new AutoOpenBarrier(2000); + this._instance.pauseInputEvents(barrier); + })); + this.add(addon.onDidReceiveCompletions(() => { + barrier?.open(); + barrier = undefined; + })); } } } diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts index c6c396e5..cb133b4b 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts @@ -30,10 +30,6 @@ export class TestResultStackWidget extends Disposable { )); } - public collapseAll() { - this.widget.collapseAll(); - } - public update(messageFrame: AnyStackFrame, stack: ITestMessageStackFrame[]) { this.widget.setFrames([messageFrame, ...stack.map(frame => new CallStackFrame( frame.label, diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts index e61463bc..9fa86330 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts @@ -22,9 +22,6 @@ interface ISubjectCommon { controllerId: string; } -export const inspectSubjectHasStack = (subject: InspectSubject | undefined) => - subject instanceof MessageSubject && !!subject.stack?.length; - export class MessageSubject implements ISubjectCommon { public readonly test: ITestItem; public readonly message: ITestMessage; diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts index 6be1cd3c..a65a1057 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts @@ -838,21 +838,22 @@ class TreeActionsProvider { } if (element instanceof TestMessageElement) { - id = MenuId.TestMessageContext; - contextKeys.push([TestingContextKeys.testMessageContext.key, element.contextValue]); - primary.push(new Action( - 'testing.outputPeek.goToTest', - localize('testing.goToTest', "Go to Test"), + 'testing.outputPeek.goToFile', + localize('testing.goToFile', "Go to Source"), ThemeIcon.asClassName(Codicon.goToFile), undefined, () => this.commandService.executeCommand('vscode.revealTest', element.test.item.extId), )); + } + if (element instanceof TestMessageElement) { + id = MenuId.TestMessageContext; + contextKeys.push([TestingContextKeys.testMessageContext.key, element.contextValue]); if (this.showRevealLocationOnMessages && element.location) { primary.push(new Action( 'testing.outputPeek.goToError', - localize('testing.goToError', "Go to Error"), + localize('testing.goToError', "Go to Source"), ThemeIcon.asClassName(Codicon.goToFile), undefined, () => this.editorService.openEditor({ diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts index 67f9f05b..db80e2c6 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts @@ -322,13 +322,6 @@ export class TestResultsViewContent extends Disposable { }); } - /** - * Collapses all displayed stack frames. - */ - public collapseStack() { - this.callStackWidget.collapseAll(); - } - private getCallFrames(subject: InspectSubject) { if (!(subject instanceof MessageSubject)) { return undefined; diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index 8caac4a2..e422256e 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -25,7 +25,7 @@ import { testingResultsIcon, testingViewIcon } from 'vs/workbench/contrib/testin import { TestCoverageView } from 'vs/workbench/contrib/testing/browser/testCoverageView'; import { TestingDecorationService, TestingDecorations } from 'vs/workbench/contrib/testing/browser/testingDecorations'; import { TestingExplorerView } from 'vs/workbench/contrib/testing/browser/testingExplorerView'; -import { CloseTestPeek, CollapsePeekStack, GoToNextMessageAction, GoToPreviousMessageAction, OpenMessageInEditorAction, TestResultsView, TestingOutputPeekController, TestingPeekOpener, ToggleTestingPeekHistory } from 'vs/workbench/contrib/testing/browser/testingOutputPeek'; +import { CloseTestPeek, GoToNextMessageAction, GoToPreviousMessageAction, OpenMessageInEditorAction, TestResultsView, TestingOutputPeekController, TestingPeekOpener, ToggleTestingPeekHistory } from 'vs/workbench/contrib/testing/browser/testingOutputPeek'; import { TestingProgressTrigger } from 'vs/workbench/contrib/testing/browser/testingProgressUiService'; import { TestingViewPaneContainer } from 'vs/workbench/contrib/testing/browser/testingViewPaneContainer'; import { testingConfiguration } from 'vs/workbench/contrib/testing/common/configuration'; @@ -136,7 +136,6 @@ registerAction2(GoToPreviousMessageAction); registerAction2(GoToNextMessageAction); registerAction2(CloseTestPeek); registerAction2(ToggleTestingPeekHistory); -registerAction2(CollapsePeekStack); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingContentProvider, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingPeekOpener, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index 0ff1cc43..7884f77a 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; import { equals } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -457,24 +456,11 @@ export class TestingDecorations extends Disposable implements IEditorContributio decorations.syncDecorations(this._currentUri); } })); - - const win = dom.getWindow(editor.getDomNode()); - this._register(dom.addDisposableListener(win, 'keydown', e => { - if (new StandardKeyboardEvent(e).keyCode === KeyCode.Alt && this._currentUri) { - decorations.updateDecorationsAlternateAction(this._currentUri, true); + this._register(this.editor.onKeyDown(e => { + if (e.keyCode === KeyCode.Alt && this._currentUri) { + decorations.updateDecorationsAlternateAction(this._currentUri!, true); } })); - this._register(dom.addDisposableListener(win, 'keyup', e => { - if (new StandardKeyboardEvent(e).keyCode === KeyCode.Alt && this._currentUri) { - decorations.updateDecorationsAlternateAction(this._currentUri, false); - } - })); - this._register(dom.addDisposableListener(win, 'blur', () => { - if (this._currentUri) { - decorations.updateDecorationsAlternateAction(this._currentUri, false); - } - })); - this._register(this.editor.onKeyUp(e => { if (e.keyCode === KeyCode.Alt && this._currentUri) { decorations.updateDecorationsAlternateAction(this._currentUri!, false); diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 3acce2ec..2bac0cf0 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -14,7 +14,6 @@ import { Iterable } from 'vs/base/common/iterator'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { observableValue } from 'vs/base/common/observable'; import { count } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -44,7 +43,6 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { bindContextKey } from 'vs/platform/observable/common/platformObservableUtils'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -54,7 +52,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { renderTestMessageAsText } from 'vs/workbench/contrib/testing/browser/testMessageColorizer'; -import { InspectSubject, MessageSubject, TaskSubject, TestOutputSubject, inspectSubjectHasStack, mapFindTestMessage } from 'vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject'; +import { InspectSubject, MessageSubject, TaskSubject, TestOutputSubject, mapFindTestMessage } from 'vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject'; import { TestResultsViewContent } from 'vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent'; import { testingMessagePeekBorder, testingPeekBorder, testingPeekHeaderBackground, testingPeekMessageHeaderBackground } from 'vs/workbench/contrib/testing/browser/theme'; import { AutoOpenPeekViewWhen, TestingConfigKeys, getTestingConfiguration } from 'vs/workbench/contrib/testing/common/configuration'; @@ -500,13 +498,6 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo this.peek.clear(); } - /** - * Collapses all displayed stack frames. - */ - public collapseStack() { - this.peek.value?.collapseStack(); - } - /** * Shows the next message in the peek, if possible. */ @@ -654,14 +645,10 @@ class TestResultsPeek extends PeekViewWidget { private static lastHeightInLines?: number; private readonly visibilityChange = this._disposables.add(new Emitter()); - private readonly _current = observableValue('testPeekCurrent', undefined); private content!: TestResultsViewContent; private scopedContextKeyService!: IContextKeyService; private dimension?: dom.Dimension; - - public get current() { - return this._current.get(); - } + public current?: InspectSubject; constructor( editor: ICodeEditor, @@ -715,14 +702,7 @@ class TestResultsPeek extends PeekViewWidget { protected override _fillHead(container: HTMLElement): void { super._fillHead(container); - const menuContextKeyService = this._disposables.add(this.contextKeyService.createScoped(container)); - this._disposables.add(bindContextKey( - TestingContextKeys.peekHasStack, - menuContextKeyService, - reader => inspectSubjectHasStack(this._current.read(reader)), - )); - - const menu = this.menuService.createMenu(MenuId.TestPeekTitle, menuContextKeyService); + const menu = this.menuService.createMenu(MenuId.TestPeekTitle, this.contextKeyService); const actionBar = this._actionbarWidget!; this._disposables.add(menu.onDidChange(() => { actions.length = 0; @@ -752,7 +732,7 @@ class TestResultsPeek extends PeekViewWidget { */ public setModel(subject: InspectSubject): Promise { if (subject instanceof TaskSubject || subject instanceof TestOutputSubject) { - this._current.set(subject, undefined); + this.current = subject; return this.showInPlace(subject); } @@ -763,14 +743,14 @@ class TestResultsPeek extends PeekViewWidget { return Promise.resolve(); } - this._current.set(subject, undefined); + this.current = subject; if (!revealLocation) { return this.showInPlace(subject); } // If there is a stack we want to display, ensure the default size is large-ish const peekLines = TestResultsPeek.lastHeightInLines || Math.max( - inspectSubjectHasStack(subject) ? Math.ceil(this.getVisibleEditorLines() / 2) : 0, + subject instanceof MessageSubject && subject.stack?.length ? Math.ceil(this.getVisibleEditorLines() / 2) : 0, hintMessagePeekHeight(message) ); @@ -780,13 +760,6 @@ class TestResultsPeek extends PeekViewWidget { return this.showInPlace(subject); } - /** - * Collapses all displayed stack frames. - */ - public collapseStack() { - this.content.collapseStack(); - } - private getVisibleEditorLines() { // note that we don't use the view ranges because we don't want to get // thrown off by large wrapping lines. Being approximate here is okay. @@ -1052,31 +1025,6 @@ export class GoToPreviousMessageAction extends Action2 { } } -export class CollapsePeekStack extends Action2 { - public static readonly ID = 'testing.collapsePeekStack'; - constructor() { - super({ - id: CollapsePeekStack.ID, - title: localize2('testing.collapsePeekStack', 'Collapse Stack Frames'), - icon: Codicon.collapseAll, - category: Categories.Test, - menu: [{ - id: MenuId.TestPeekTitle, - when: TestingContextKeys.peekHasStack, - group: 'navigation', - order: 4, - }], - }); - } - - public override run(accessor: ServicesAccessor) { - const editor = getPeekedEditorFromFocus(accessor.get(ICodeEditorService)); - if (editor) { - TestingOutputPeekController.get(editor)?.collapseStack(); - } - } -} - export class OpenMessageInEditorAction extends Action2 { public static readonly ID = 'testing.openMessageInEditor'; constructor() { diff --git a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts index 1cfd764d..ac303b48 100644 --- a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts +++ b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts @@ -29,7 +29,6 @@ export namespace TestingContextKeys { export const inlineCoverageEnabled = new RawContextKey('testing.inlineCoverageEnabled', false, { type: 'boolean', description: localize('testing.inlineCoverageEnabled', 'Indicates whether inline coverage is shown') }); export const canGoToRelatedCode = new RawContextKey('testing.canGoToRelatedCode', false, { type: 'boolean', description: localize('testing.canGoToRelatedCode', 'Whether a controller implements a capability to find code related to a test') }); export const canGoToRelatedTest = new RawContextKey('testing.canGoToRelatedTest', false, { type: 'boolean', description: localize('testing.canGoToRelatedTest', 'Whether a controller implements a capability to find tests related to code') }); - export const peekHasStack = new RawContextKey('testing.peekHasStack', false, { type: 'boolean', description: localize('testing.peekHasStack', 'Whether the message shown in a peek view has a stack trace') }); export const capabilityToContextKey: { [K in TestRunProfileBitset]: RawContextKey } = { [TestRunProfileBitset.Run]: hasRunnableTests, diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts index 17ddb9cb..c471d251 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts @@ -92,7 +92,6 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi private profileWidget: ProfileWidget | undefined; private model: UserDataProfilesEditorModel | undefined; - private templates: readonly IProfileTemplateInfo[] = []; constructor( group: IEditorGroup, @@ -208,7 +207,7 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi actions: { getActions: () => { const actions: IAction[] = []; - if (this.templates.length) { + if (this.model?.templates.length) { actions.push(new SubmenuAction('from.template', localize('from template', "From Template"), this.getCreateFromTemplateActions())); actions.push(new Separator()); } @@ -226,13 +225,15 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi } private getCreateFromTemplateActions(): IAction[] { - return this.templates.map(template => - new Action( - `template:${template.url}`, - template.name, - undefined, - true, - () => this.createNewProfile(URI.parse(template.url)))); + return this.model + ? this.model.templates.map(template => + new Action( + `template:${template.url}`, + template.name, + undefined, + true, + () => this.createNewProfile(URI.parse(template.url)))) + : []; } private registerListeners(): void { @@ -342,12 +343,9 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi override async setInput(input: UserDataProfilesEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); this.model = await input.resolve(); - this.model.getTemplates().then(templates => { - this.templates = templates; - if (this.profileWidget) { - this.profileWidget.templates = templates; - } - }); + if (this.profileWidget) { + this.profileWidget.templates = this.model.templates; + } this.updateProfilesList(); this._register(this.model.onDidChange(element => this.updateProfilesList(element))); @@ -712,6 +710,7 @@ class ProfileTreeDataSource implements IAsyncDataSource()); readonly onDidChange = this._onDidChange.event; - private templates: Promise | undefined; + private _templates: IProfileTemplateInfo[] | undefined; + get templates(): readonly IProfileTemplateInfo[] { return this._templates ?? []; } constructor( @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @@ -760,11 +761,9 @@ export class UserDataProfilesEditorModel extends EditorModel { } } - getTemplates(): Promise { - if (!this.templates) { - this.templates = this.userDataProfileManagementService.getBuiltinProfileTemplates(); - } - return this.templates; + override async resolve(): Promise { + await super.resolve(); + this._templates = await this.userDataProfileManagementService.getBuiltinProfileTemplates(); } private createProfileElement(profile: IUserDataProfile): [UserDataProfileElement, DisposableStore] { @@ -772,7 +771,7 @@ export class UserDataProfilesEditorModel extends EditorModel { const activateAction = disposables.add(new Action( 'userDataProfile.activate', - localize('active', "Use this Profile for Current Window"), + localize('active', "Use for Current Window"), ThemeIcon.asClassName(Codicon.check), true, () => this.userDataProfileManagementService.switchProfile(profileElement.profile) @@ -809,16 +808,25 @@ export class UserDataProfilesEditorModel extends EditorModel { () => this.openWindow(profileElement.profile) )); + const useAsNewWindowProfileAction = disposables.add(new Action( + 'userDataProfile.useAsNewWindowProfile', + localize('use as new window', "Use for New Windows"), + undefined, + true, + () => profileElement.toggleNewWindowProfile() + )); + const primaryActions: IAction[] = []; - primaryActions.push(activateAction); primaryActions.push(newWindowAction); + if (!profile.isDefault) { + primaryActions.push(deleteAction); + } const secondaryActions: IAction[] = []; + secondaryActions.push(activateAction); + secondaryActions.push(useAsNewWindowProfileAction); + secondaryActions.push(new Separator()); secondaryActions.push(copyFromProfileAction); secondaryActions.push(exportAction); - if (!profile.isDefault) { - secondaryActions.push(new Separator()); - secondaryActions.push(deleteAction); - } const profileElement = disposables.add(this.instantiationService.createInstance(UserDataProfileElement, profile, @@ -826,9 +834,16 @@ export class UserDataProfilesEditorModel extends EditorModel { [primaryActions, secondaryActions] )); - activateAction.enabled = this.userDataProfileService.currentProfile.id !== profileElement.profile.id; + activateAction.checked = this.userDataProfileService.currentProfile.id === profileElement.profile.id; disposables.add(this.userDataProfileService.onDidChangeCurrentProfile(() => - activateAction.enabled = this.userDataProfileService.currentProfile.id !== profileElement.profile.id)); + activateAction.checked = this.userDataProfileService.currentProfile.id === profileElement.profile.id)); + + useAsNewWindowProfileAction.checked = profileElement.isNewWindowProfile; + disposables.add(profileElement.onDidChange(e => { + if (e.newWindowProfile) { + useAsNewWindowProfileAction.checked = profileElement.isNewWindowProfile; + } + })); return [profileElement, disposables]; } diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index 3ca45752..0b826034 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -226,7 +226,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ 'onSettingChanged:workbench.colorTheme', 'onCommand:workbench.action.selectTheme' ], - when: '!accessibilityModeEnabled', media: { type: 'markdown', path: 'theme_picker', } }, { @@ -400,7 +399,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ isFeatured: true, icon: setupIcon, when: CONTEXT_ACCESSIBILITY_MODE_ENABLED.key, - next: 'Setup', + next: 'SetupScreenReaderExtended', content: { type: 'steps', steps: [ @@ -471,6 +470,90 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ ] } }, + { + id: 'SetupScreenReaderExtended', + title: localize('gettingStarted.setupScreenReaderExtended.title', "Learn more about using VS Code with a Screen Reader"), + description: localize('gettingStarted.setupScreenReaderExtended.description', "Customize your editor, learn the basics, and start coding"), + isFeatured: true, + icon: setupIcon, + when: `!isWeb && ${CONTEXT_ACCESSIBILITY_MODE_ENABLED.key}`, + content: { + type: 'steps', + steps: [ + { + id: 'extensionsWeb', + title: localize('gettingStarted.extensions.title', "Code with extensions"), + description: localize('gettingStarted.extensionsWeb.description.interpolated', "Extensions are VS Code's power-ups. A growing number are becoming available in the web.\n{0}", Button(localize('browsePopularWeb', "Browse Popular Web Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), + when: 'workspacePlatform == \'webworker\'', + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'findLanguageExtensions', + title: localize('gettingStarted.findLanguageExts.title', "Rich support for all your languages"), + description: localize('gettingStarted.findLanguageExts.description.interpolated', "Code smarter with syntax highlighting, code completion, linting and debugging. While many languages are built-in, many more can be added as extensions.\n{0}", Button(localize('browseLangExts', "Browse Language Extensions"), 'command:workbench.extensions.action.showLanguageExtensions')), + when: 'workspacePlatform != \'webworker\'', + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'settings', + title: localize('gettingStarted.settings.title', "Tune your settings"), + description: localize('gettingStarted.settings.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'settingsSync', + title: localize('gettingStarted.settingsSync.title', "Sync settings across devices"), + description: localize('gettingStarted.settingsSync.description.interpolated', "Keep your essential customizations backed up and updated across all your devices.\n{0}", Button(localize('enableSync', "Backup and Sync Settings"), 'command:workbench.userDataSync.actions.turnOn')), + when: 'syncStatus != uninitialized', + completionEvents: ['onEvent:sync-enabled'], + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'commandPaletteTask', + title: localize('gettingStarted.commandPalette.title', "Unlock productivity with the Command Palette "), + description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in VS Code.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'pickAFolderTask-Mac', + title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), + description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFileFolder')), + when: 'isMac && workspaceFolderCount == 0', + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'pickAFolderTask-Other', + title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), + description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFolder')), + when: '!isMac && workspaceFolderCount == 0', + media: { + type: 'markdown', path: 'empty' + } + }, + { + id: 'quickOpen', + title: localize('gettingStarted.quickOpen.title', "Quickly navigate between your files"), + description: localize('gettingStarted.quickOpen.description.interpolated', "Navigate between files in an instant with one keystroke. Tip: Open multiple files by pressing the right arrow key.\n{0}", Button(localize('quickOpen', "Quick Open a File"), 'command:toSide:workbench.action.quickOpen')), + when: 'workspaceFolderCount != 0', + media: { + type: 'markdown', path: 'empty' + } + }, + ] + } + }, { id: 'Beginner', isFeatured: false, diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 69b98abf..c51e033d 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -28,6 +28,7 @@ import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; import { ModifierKeyEmitter } from 'vs/base/browser/dom'; import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sandbox/window'; +import product from 'vs/platform/product/common/product'; // Actions (function registerActions(): void { @@ -239,7 +240,7 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand 'type': 'boolean', 'included': isLinux, 'markdownDescription': localize('window.experimentalControlOverlay', "Show the native window controls when {0} is set to `custom` (Linux only).", '`#window.titleBarStyle#`'), - 'default': true + 'default': product.quality !== 'stable', // TODO@bpasero disable by default in stable for now (TODO@bpasero TODO@benibenj flip when custom title is default) }, 'window.customTitleBarVisibility': { 'type': 'string', diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 2fa6f984..12609c51 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -156,17 +156,17 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { }))); } - // Window Controls (Native Linux when WCO is disabled) - if (isLinux && !hasNativeTitlebar(this.configurationService) && !isWCOEnabled() && this.windowControlsContainer) { + // Window Controls (Native Windows/Linux) + if (!isMacintosh && !hasNativeTitlebar(this.configurationService) && !isWCOEnabled() && this.primaryWindowControls) { // Minimize - const minimizeIcon = append(this.windowControlsContainer, $('div.window-icon.window-minimize' + ThemeIcon.asCSSSelector(Codicon.chromeMinimize))); + const minimizeIcon = append(this.primaryWindowControls, $('div.window-icon.window-minimize' + ThemeIcon.asCSSSelector(Codicon.chromeMinimize))); this._register(addDisposableListener(minimizeIcon, EventType.CLICK, () => { this.nativeHostService.minimizeWindow({ targetWindowId }); })); // Restore - this.maxRestoreControl = append(this.windowControlsContainer, $('div.window-icon.window-max-restore')); + this.maxRestoreControl = append(this.primaryWindowControls, $('div.window-icon.window-max-restore')); this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async () => { const maximized = await this.nativeHostService.isMaximized({ targetWindowId }); if (maximized) { @@ -177,7 +177,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { })); // Close - const closeIcon = append(this.windowControlsContainer, $('div.window-icon.window-close' + ThemeIcon.asCSSSelector(Codicon.chromeClose))); + const closeIcon = append(this.primaryWindowControls, $('div.window-icon.window-close' + ThemeIcon.asCSSSelector(Codicon.chromeClose))); this._register(addDisposableListener(closeIcon, EventType.CLICK, () => { this.nativeHostService.closeWindow({ targetWindowId }); })); diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index ec37cfcc..bbbd1d0b 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -41,7 +41,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench public readonly onEnablementChanged: Event = this._onEnablementChanged.event; protected readonly extensionsManager: ExtensionsManager; - private readonly storageManager: StorageManager; + private readonly storageManger: StorageManager; constructor( @IStorageService storageService: IStorageService, @@ -63,7 +63,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench @IInstantiationService instantiationService: IInstantiationService, ) { super(); - this.storageManager = this._register(new StorageManager(storageService)); + this.storageManger = this._register(new StorageManager(storageService)); const uninstallDisposable = this._register(Event.filter(extensionManagementService.onDidUninstallExtension, e => !e.error)(({ identifier }) => this._reset(identifier))); let isDisposed = false; @@ -610,11 +610,11 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench if (!this.hasWorkspace) { return []; } - return this.storageManager.get(storageId, StorageScope.WORKSPACE); + return this.storageManger.get(storageId, StorageScope.WORKSPACE); } private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void { - this.storageManager.set(storageId, extensions, StorageScope.WORKSPACE); + this.storageManger.set(storageId, extensions, StorageScope.WORKSPACE); } private async _onDidChangeGloballyDisabledExtensions(extensionIdentifiers: ReadonlyArray, source?: string): Promise { diff --git a/src/vs/workbench/services/search/common/fileSearchManager.ts b/src/vs/workbench/services/search/common/fileSearchManager.ts index 73505b7a..659217ef 100644 --- a/src/vs/workbench/services/search/common/fileSearchManager.ts +++ b/src/vs/workbench/services/search/common/fileSearchManager.ts @@ -13,8 +13,6 @@ import { URI } from 'vs/base/common/uri'; import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery, QueryGlobTester, resolvePatternsForProvider, hasSiblingFn, excludeToGlobPattern, DEFAULT_MAX_SEARCH_RESULTS } from 'vs/workbench/services/search/common/search'; import { FileSearchProviderFolderOptions, FileSearchProviderNew, FileSearchProviderOptions } from 'vs/workbench/services/search/common/searchExtTypes'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { OldFileSearchProviderConverter } from 'vs/workbench/services/search/common/searchExtConversionTypes'; interface IInternalFileMatch { base: URI; @@ -55,7 +53,7 @@ class FileSearchEngine { private globalExcludePattern?: glob.ParsedExpression; - constructor(private config: IFileQuery, private provider: FileSearchProviderNew, private sessionLifecycle?: SessionLifecycle) { + constructor(private config: IFileQuery, private provider: FileSearchProviderNew, private sessionToken?: unknown) { this.filePattern = config.filePattern; this.includePattern = config.includePattern && glob.parse(config.includePattern); this.maxResults = config.maxResults || undefined; @@ -118,11 +116,10 @@ class FileSearchEngine { private async doSearch(fqs: IFolderQuery[], onResult: (match: IInternalFileMatch) => void): Promise { const cancellation = new CancellationTokenSource(); const folderOptions = fqs.map(fq => this.getSearchOptionsForFolder(fq)); - const session = this.provider instanceof OldFileSearchProviderConverter ? this.sessionLifecycle?.tokenSource.token : this.sessionLifecycle?.obj; const options: FileSearchProviderOptions = { folderOptions, maxResults: this.config.maxResults ?? DEFAULT_MAX_SEARCH_RESULTS, - session + session: this.sessionToken }; @@ -304,30 +301,11 @@ interface IInternalSearchComplete { stats?: IFileSearchProviderStats; } -/** - * For backwards compatibility, store both a cancellation token and a session object. The session object is the new implementation, where - */ -class SessionLifecycle extends Disposable { - public readonly obj: object; - public readonly tokenSource: CancellationTokenSource; - - constructor() { - super(); - this.obj = new Object(); - this.tokenSource = new CancellationTokenSource(); - } - - public override dispose(): void { - this.tokenSource.cancel(); - super.dispose(); - } -} - export class FileSearchManager { private static readonly BATCH_SIZE = 512; - private readonly sessions = new Map(); + private readonly sessions = new Map(); fileSearch(config: IFileQuery, provider: FileSearchProviderNew, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { const sessionTokenSource = this.getSessionTokenSource(config.cacheKey); @@ -355,19 +333,17 @@ export class FileSearchManager { } clearCache(cacheKey: string): void { - // cancel the token - this.sessions.get(cacheKey)?.dispose(); // with no reference to this, it will be removed from WeakMaps this.sessions.delete(cacheKey); } - private getSessionTokenSource(cacheKey: string | undefined): SessionLifecycle | undefined { + private getSessionTokenSource(cacheKey: string | undefined): unknown { if (!cacheKey) { return undefined; } if (!this.sessions.has(cacheKey)) { - this.sessions.set(cacheKey, new SessionLifecycle()); + this.sessions.set(cacheKey, new Object()); } return this.sessions.get(cacheKey); diff --git a/src/vs/workbench/services/search/common/queryBuilder.ts b/src/vs/workbench/services/search/common/queryBuilder.ts index 53b37e5f..ed1ab661 100644 --- a/src/vs/workbench/services/search/common/queryBuilder.ts +++ b/src/vs/workbench/services/search/common/queryBuilder.ts @@ -92,7 +92,6 @@ interface ICommonQueryBuilderOptions { disregardSearchExcludeSettings?: boolean; ignoreSymlinks?: boolean; onlyOpenEditors?: boolean; - onlyFileScheme?: boolean; } export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions { @@ -261,8 +260,7 @@ export class QueryBuilder { excludePattern: excludeSearchPathsInfo.pattern, includePattern: includeSearchPathsInfo.pattern, onlyOpenEditors: options.onlyOpenEditors, - maxResults: options.maxResults, - onlyFileScheme: options.onlyFileScheme + maxResults: options.maxResults }; if (options.onlyOpenEditors) { diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 79b0e06e..5f6283c3 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -100,7 +100,6 @@ export interface ICommonQueryProps { maxResults?: number; usingSearchPaths?: boolean; - onlyFileScheme?: boolean; } export interface IFileQueryProps extends ICommonQueryProps { diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index b58dc567..f4e35c52 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -271,9 +271,6 @@ export class SearchService extends Disposable implements ISearchService { return []; } await Promise.all([...fqs.keys()].map(async scheme => { - if (query.onlyFileScheme && scheme !== Schemas.file) { - return; - } const schemeFQs = fqs.get(scheme)!; let provider = this.getSearchProvider(query.type).get(scheme); diff --git a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts index c19ad7c8..7bcb272f 100644 --- a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts @@ -143,7 +143,7 @@ export class TestNativeHostService implements INativeHostService { async closeWindow(): Promise { } async quit(): Promise { } async exit(code: number): Promise { } - async openDevTools(): Promise { } + async openDevTools(options?: Partial & INativeHostOptions | undefined): Promise { } async toggleDevTools(): Promise { } async resolveProxy(url: string): Promise { return undefined; } async lookupAuthorization(authInfo: AuthInfo): Promise { return undefined; } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 9e01f175..461caaf8 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -3,6 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +//#region --- void +import 'vs/workbench/contrib/void/browser/void.contribution' +//#endregion + + //#region --- editor/workbench core import 'vs/editor/editor.all'; @@ -10,6 +15,7 @@ import 'vs/editor/editor.all'; import 'vs/workbench/api/browser/extensionHost.contribution'; import 'vs/workbench/browser/workbench.contribution'; + //#endregion