mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
Merge pull request #29 from quant-eagle/fix/codelens_imports
fix: update VS source to more recent version
This commit is contained in:
commit
d3fd53974e
85 changed files with 1032 additions and 575 deletions
|
|
@ -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 getWCOBoundingRect(): DOMRect | undefined {
|
||||
return (navigator as any)?.windowControlsOverlay?.getTitlebarAreaRect();
|
||||
export function getWCOTitlebarAreaRect(targetWindow: Window): DOMRect | undefined {
|
||||
return (targetWindow.navigator as any)?.windowControlsOverlay?.getTitlebarAreaRect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
|
||||
import { createInstantHoverDelegate } 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 ?? getDefaultHoverDelegate('element');
|
||||
this.hoverDelegate = opts.hoverDelegate ?? this._register(createInstantHoverDelegate());
|
||||
|
||||
this.domNode = $('.monaco-custom-radio');
|
||||
this.domNode.setAttribute('role', 'radio');
|
||||
|
|
|
|||
|
|
@ -217,24 +217,6 @@ 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
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ export class HoverWidget extends Widget implements IHoverWidget {
|
|||
}
|
||||
|
||||
// Show the hover hint if needed
|
||||
if (hideOnHover && options.appearance?.showHoverHint) {
|
||||
if (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');
|
||||
|
|
|
|||
|
|
@ -2019,9 +2019,9 @@ export interface CommentThreadChangedEvent<T> {
|
|||
}
|
||||
|
||||
export interface CodeLens {
|
||||
range: IRange; // the range of code
|
||||
id?: string; // no idea what this is for
|
||||
command?: Command; // command to run when they click the codeLens
|
||||
range: IRange;
|
||||
id?: string;
|
||||
command?: Command;
|
||||
}
|
||||
|
||||
export interface CodeLensList {
|
||||
|
|
|
|||
|
|
@ -180,6 +180,12 @@ 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) {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,14 @@ 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;
|
||||
|
||||
|
|
@ -148,15 +156,8 @@ 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 || !lightbulbClasses.some(cls => e.target.element && e.target.element.classList.contains(cls))) {
|
||||
if (!e.target.element || !this.lightbulbClasses.some(cls => e.target.element && e.target.element.classList.contains(cls))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -247,7 +248,9 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
|
|||
let hasDecoration = false;
|
||||
if (currLineDecorations) {
|
||||
for (const decoration of currLineDecorations) {
|
||||
if (decoration.options.glyphMarginClassName) {
|
||||
const glyphClass = decoration.options.glyphMarginClassName;
|
||||
|
||||
if (glyphClass && !this.lightbulbClasses.some(className => glyphClass.includes(className))) {
|
||||
hasDecoration = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -271,7 +274,6 @@ 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, {
|
||||
|
|
@ -280,7 +282,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
|
|||
});
|
||||
this.renderGutterLightbub();
|
||||
return this.hide();
|
||||
} else if (prevLineEmptyOrIndented || endLine || (notEmpty && !currLineEmptyOrIndented)) {
|
||||
} else if (prevLineEmptyOrIndented || endLine || (prevLineEmptyOrIndented && !currLineEmptyOrIndented)) {
|
||||
effectiveLineNumber -= 1;
|
||||
} else if (nextLineEmptyOrIndented || (notEmpty && currLineEmptyOrIndented)) {
|
||||
effectiveLineNumber += 1;
|
||||
|
|
|
|||
|
|
@ -3,17 +3,17 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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';
|
||||
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';
|
||||
|
||||
export const ICodeLensCache = createDecorator<ICodeLensCache>('ICodeLensCache');
|
||||
|
||||
|
|
|
|||
|
|
@ -3,17 +3,17 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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';
|
||||
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';
|
||||
|
||||
export interface CodeLensItem {
|
||||
symbol: CodeLens;
|
||||
|
|
|
|||
|
|
@ -4,26 +4,26 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
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';
|
||||
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';
|
||||
|
||||
export class CodeLensContribution implements IEditorContribution {
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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';
|
||||
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';
|
||||
|
||||
class CodeLensViewZone implements IViewZone {
|
||||
|
||||
|
|
|
|||
|
|
@ -318,7 +318,8 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable
|
|||
}
|
||||
|
||||
afterRender(position: ContentWidgetPositionPreference | null): void {
|
||||
this._trace('invoking afterRender, position: ', position ? 'not null' : 'null');
|
||||
// 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');
|
||||
if (position === null) {
|
||||
// cancel rename when input widget isn't rendered anymore
|
||||
this.cancelInput(true, 'afterRender (because position is null)');
|
||||
|
|
@ -363,7 +364,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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,8 +166,8 @@ export class ActionList<T> extends Disposable {
|
|||
|
||||
private readonly _list: List<IActionListItem<T>>;
|
||||
|
||||
private readonly _actionLineHeight = 28;
|
||||
private readonly _headerLineHeight = 28;
|
||||
private readonly _actionLineHeight = 24;
|
||||
private readonly _headerLineHeight = 26;
|
||||
|
||||
private readonly _allMenuItems: readonly IActionListItem<T>[];
|
||||
|
||||
|
|
|
|||
|
|
@ -132,8 +132,9 @@
|
|||
/* Action bar */
|
||||
|
||||
.action-widget .action-widget-action-bar {
|
||||
background-color: var(--vscode-editorHoverWidget-statusBarBackground);
|
||||
background-color: var(--vscode-editorActionList-background);
|
||||
border-top: 1px solid var(--vscode-editorHoverWidget-border);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.action-widget .action-widget-action-bar::before {
|
||||
|
|
@ -143,7 +144,7 @@
|
|||
}
|
||||
|
||||
.action-widget .action-widget-action-bar .actions-container {
|
||||
padding: 0 8px;
|
||||
padding: 3px 8px 0;
|
||||
}
|
||||
|
||||
.action-widget-action-bar .action-label {
|
||||
|
|
|
|||
|
|
@ -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 storageManger: StorageManager;
|
||||
private readonly storageManager: StorageManager;
|
||||
|
||||
constructor(
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
|
||||
) {
|
||||
super();
|
||||
this.storageManger = this._register(new StorageManager(storageService));
|
||||
this._register(this.storageManger.onDidChange(extensions => this._onDidChangeEnablement.fire({ extensions, source: 'storage' })));
|
||||
this.storageManager = this._register(new StorageManager(storageService));
|
||||
this._register(this.storageManager.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.storageManger.get(storageId, StorageScope.PROFILE);
|
||||
return this.storageManager.get(storageId, StorageScope.PROFILE);
|
||||
}
|
||||
|
||||
private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void {
|
||||
this.storageManger.set(storageId, extensions, StorageScope.PROFILE);
|
||||
this.storageManager.set(storageId, extensions, StorageScope.PROFILE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, OpenDevToolsOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'vs/base/parts/sandbox/common/electronTypes';
|
||||
import { MessageBoxOptions, MessageBoxReturnValue, 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<void>;
|
||||
|
||||
// Development
|
||||
openDevTools(options?: Partial<OpenDevToolsOptions> & INativeHostOptions): Promise<void>;
|
||||
openDevTools(options?: INativeHostOptions): Promise<void>;
|
||||
toggleDevTools(options?: INativeHostOptions): Promise<void>;
|
||||
|
||||
// Perf Introspection
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import * as fs from 'fs';
|
||||
import { exec } from 'child_process';
|
||||
import { app, BrowserWindow, clipboard, Display, Menu, MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, OpenDialogOptions, OpenDialogReturnValue, powerMonitor, SaveDialogOptions, SaveDialogReturnValue, screen, shell, webContents } from 'electron';
|
||||
import { app, BrowserWindow, clipboard, Display, Menu, MessageBoxOptions, MessageBoxReturnValue, 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 } from 'vs/platform/window/common/window';
|
||||
import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable, useWindowControlsOverlay } 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,14 +855,28 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
|||
|
||||
//#region Development
|
||||
|
||||
async openDevTools(windowId: number | undefined, options?: Partial<OpenDevToolsOptions> & INativeHostOptions): Promise<void> {
|
||||
async openDevTools(windowId: number | undefined, options?: INativeHostOptions): Promise<void> {
|
||||
const window = this.windowById(options?.targetWindowId, windowId);
|
||||
window?.win?.webContents.openDevTools(options?.mode ? { mode: options.mode, activate: options.activate } : undefined);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async toggleDevTools(windowId: number | undefined, options?: INativeHostOptions): Promise<void> {
|
||||
const window = this.windowById(options?.targetWindowId, windowId);
|
||||
window?.win?.webContents.toggleDevTools();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
|
|
|||
|
|
@ -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', description, format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] };
|
||||
const propertySchema: IJSONSchemaWithSnippets = { type: 'string', format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] };
|
||||
if (deprecationMessage) {
|
||||
propertySchema.deprecationMessage = deprecationMessage;
|
||||
}
|
||||
|
|
@ -168,6 +168,7 @@ 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.') }
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ 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';
|
||||
|
|
@ -240,8 +239,6 @@ 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.
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ 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 {
|
||||
|
|
@ -146,7 +147,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
|||
|
||||
const query = this._queryBuilder.file(
|
||||
includeFolder ? [includeFolder] : workspace.folders,
|
||||
options
|
||||
revive(options)
|
||||
);
|
||||
|
||||
return this._searchService.fileSearch(query, token).then(result => {
|
||||
|
|
@ -164,7 +165,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, options);
|
||||
const query = this._queryBuilder.text(pattern, folders, revive(options));
|
||||
query._reason = 'startTextSearch';
|
||||
|
||||
const onProgress = (p: ISearchProgressItem) => {
|
||||
|
|
|
|||
|
|
@ -550,14 +550,6 @@ 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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -304,17 +304,6 @@ 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<languages.CodeLens[], vscode.CodeLens[] | undefined>('A promise that resolves to an array of VoidCodeLens-instances.', (value, _args, converter) => {
|
||||
// return tryMapWith<languages.CodeLens, vscode.CodeLens>(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.',
|
||||
|
|
|
|||
|
|
@ -2312,15 +2312,15 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
|||
}
|
||||
|
||||
$provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise<extHostProtocol.ICodeLensListDto | undefined> {
|
||||
return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined, token);
|
||||
return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined, token, resource.scheme === 'output');
|
||||
}
|
||||
|
||||
$resolveCodeLens(handle: number, symbol: extHostProtocol.ICodeLensDto, token: CancellationToken): Promise<extHostProtocol.ICodeLensDto | undefined> {
|
||||
return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined, undefined);
|
||||
return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined, undefined, true);
|
||||
}
|
||||
|
||||
$releaseCodeLenses(handle: number, cacheId: number): void {
|
||||
this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined, undefined);
|
||||
this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined, undefined, true);
|
||||
}
|
||||
|
||||
// --- declaration
|
||||
|
|
|
|||
|
|
@ -584,7 +584,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
|||
}
|
||||
const parsedInclude = include ? parseSearchExcludeInclude(GlobPattern.from(include)) : undefined;
|
||||
|
||||
const excludePatterns = include ? globsToISearchPatternBuilder(options.exclude) : undefined;
|
||||
const excludePatterns = globsToISearchPatternBuilder(options.exclude);
|
||||
|
||||
return {
|
||||
options: {
|
||||
|
|
@ -664,7 +664,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
|||
async findTextInFilesBase(query: vscode.TextSearchQuery, queryOptions: QueryOptions<ITextQueryBuilderOptions>[] | undefined, callback: (result: ITextSearchResult<URI>, uri: URI) => void, token: vscode.CancellationToken = CancellationToken.None): Promise<vscode.TextSearchComplete> {
|
||||
const requestId = this._requestIdProvider.getNext();
|
||||
|
||||
const isCanceled = false;
|
||||
let isCanceled = false;
|
||||
token.onCancellationRequested(_ => {
|
||||
isCanceled = true;
|
||||
});
|
||||
|
||||
this._activeSearchCallbacks[requestId] = p => {
|
||||
if (isCanceled) {
|
||||
|
|
|
|||
|
|
@ -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 (Minimize, Max/Restore, Close) */
|
||||
/* Window Controls Container */
|
||||
.monaco-workbench .part.titlebar .window-controls-container {
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
|
|
@ -292,7 +292,12 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
/* Web WCO Sizing/Ordering */
|
||||
.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) */
|
||||
.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);
|
||||
|
|
@ -311,29 +316,31 @@
|
|||
order: 1;
|
||||
}
|
||||
|
||||
/* Desktop Windows/Linux Window Controls*/
|
||||
.monaco-workbench:not(.web):not(.mac) .part.titlebar .window-controls-container.primary {
|
||||
/* Window Controls Container Desktop: apply zoom friendly size */
|
||||
.monaco-workbench:not(.web):not(.mac) .part.titlebar .window-controls-container {
|
||||
width: calc(138px / var(--zoom-factor, 1));
|
||||
}
|
||||
|
||||
.monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container.counter-zoom .window-controls-container.primary {
|
||||
.monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container.counter-zoom .window-controls-container {
|
||||
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));
|
||||
}
|
||||
|
||||
/* Desktop macOS Window Controls */
|
||||
.monaco-workbench:not(.web).mac .part.titlebar .window-controls-container.primary {
|
||||
.monaco-workbench:not(.web).mac .part.titlebar .window-controls-container {
|
||||
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;
|
||||
|
|
@ -342,6 +349,11 @@
|
|||
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 {
|
||||
|
|
@ -376,7 +388,6 @@
|
|||
z-index: 2500;
|
||||
-webkit-app-region: no-drag;
|
||||
height: 100%;
|
||||
min-width: 28px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container {
|
||||
|
|
@ -455,11 +466,3 @@
|
|||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 { getWCOBoundingRect, getZoomFactor, isWCOEnabled } from 'vs/base/browser/browser';
|
||||
import { getWCOTitlebarAreaRect, getZoomFactor, isWCOEnabled, onDidChangeZoomLevel } 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, getWCOBoundingRect()?.height ?? 0);
|
||||
value = Math.max(value, getWCOTitlebarAreaRect(getWindow(this.element))?.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 primaryWindowControls: HTMLElement | undefined;
|
||||
protected windowControlsContainer: HTMLElement | undefined;
|
||||
protected dragRegion: HTMLElement | undefined;
|
||||
private title!: HTMLElement;
|
||||
|
||||
|
|
@ -476,21 +476,49 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart {
|
|||
this.createActionToolBarMenus();
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
// Window Controls Container
|
||||
if (!hasNativeTitlebar(this.configurationService, this.titleBarStyle)) {
|
||||
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'));
|
||||
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 :-/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Context menu over title bar: depending on the OS and the location of the click this will either be
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
const timeAgoStr = fromNowByDay(i.lastMessageDate, true, true);
|
||||
const separator: IQuickPickSeparator | undefined = timeAgoStr !== lastDate ? {
|
||||
type: 'separator', label: timeAgoStr,
|
||||
} : undefined;
|
||||
|
|
|
|||
|
|
@ -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 { ChatExtensionPointHandler } from 'vs/workbench/contrib/chat/browser/chatParticipantContributions';
|
||||
import { ChatCompatibilityNotifier, 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,9 +261,7 @@ workbenchContributionsRegistry.registerWorkbenchContribution(ChatSlashStaticSlas
|
|||
Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer);
|
||||
registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup);
|
||||
registerWorkbenchContribution2(LanguageModelToolsExtensionPointHandler.ID, LanguageModelToolsExtensionPointHandler, WorkbenchPhase.BlockRestore);
|
||||
|
||||
// Disabled until https://github.com/microsoft/vscode/issues/218646 is fixed
|
||||
// registerWorkbenchContribution2(ChatCompatibilityNotifier.ID, ChatCompatibilityNotifier, WorkbenchPhase.Eventually);
|
||||
registerWorkbenchContribution2(ChatCompatibilityNotifier.ID, ChatCompatibilityNotifier, WorkbenchPhase.Eventually);
|
||||
|
||||
registerChatActions();
|
||||
registerChatCopyActions();
|
||||
|
|
|
|||
|
|
@ -516,7 +516,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
|||
onDidChangeCursorPosition();
|
||||
}
|
||||
|
||||
private initAttachedContext(container: HTMLElement) {
|
||||
private initAttachedContext(container: HTMLElement, isLayout = false) {
|
||||
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) {
|
||||
if (oldHeight !== container.offsetHeight && !isLayout) {
|
||||
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);
|
||||
this.initAttachedContext(this.attachedContextContainer, true);
|
||||
|
||||
const data = this.getLayoutData();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,17 +3,16 @@
|
|||
* 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 { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { localize, localize2 } from 'vs/nls';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
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 { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { 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';
|
||||
|
|
@ -21,7 +20,9 @@ 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';
|
||||
|
|
@ -160,35 +161,6 @@ 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';
|
||||
|
|
@ -198,9 +170,10 @@ 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();
|
||||
}
|
||||
|
||||
|
|
@ -239,11 +212,6 @@ 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;
|
||||
|
|
@ -260,6 +228,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
|
|||
}
|
||||
}
|
||||
|
||||
const store = new DisposableStore();
|
||||
store.add(this._chatAgentService.registerAgent(
|
||||
providerDescriptor.id,
|
||||
{
|
||||
|
|
@ -318,15 +287,9 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
|
|||
return viewContainer;
|
||||
}
|
||||
|
||||
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;
|
||||
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';
|
||||
const viewDescriptor: IViewDescriptor[] = [{
|
||||
id: CHAT_VIEW_ID,
|
||||
containerIcon: this._viewContainer.icon,
|
||||
|
|
@ -336,12 +299,11 @@ 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<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer);
|
||||
|
||||
return toDisposable(() => {
|
||||
this.hasRegisteredDefaultParticipantView = false;
|
||||
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer);
|
||||
});
|
||||
}
|
||||
|
|
@ -350,3 +312,39 @@ 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<IViewsRegistry>(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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,8 +88,9 @@ 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();
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -114,6 +115,10 @@ 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -770,15 +770,11 @@ export class CodeCompareBlockPart extends Disposable {
|
|||
});
|
||||
|
||||
dom.reset(this.messageElement, message);
|
||||
|
||||
}
|
||||
|
||||
const diffData = await data.diffData;
|
||||
if (!diffData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isEditApplied) {
|
||||
if (!isEditApplied && diffData) {
|
||||
const viewModel = this.diffEditor.createViewModel({
|
||||
original: diffData.original,
|
||||
modified: diffData.modified
|
||||
|
|
@ -801,6 +797,7 @@ export class CodeCompareBlockPart extends Disposable {
|
|||
} else {
|
||||
this.diffEditor.setModel(null);
|
||||
this._lastDiffEditorViewModel.value = undefined;
|
||||
this._onDidChangeContentHeight.fire();
|
||||
}
|
||||
|
||||
this.toolbar.context = {
|
||||
|
|
|
|||
|
|
@ -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 } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } 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,11 +233,13 @@ export class ChatAgentService implements IChatAgentService {
|
|||
readonly onDidChangeAgents: Event<IChatAgent | undefined> = this._onDidChangeAgents.event;
|
||||
|
||||
private readonly _hasDefaultAgent: IContextKey<boolean>;
|
||||
private readonly _defaultAgentRegistered: IContextKey<boolean>;
|
||||
|
||||
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 {
|
||||
|
|
@ -246,6 +248,10 @@ 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 = {
|
||||
|
|
@ -256,8 +262,13 @@ 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);
|
||||
});
|
||||
}
|
||||
|
|
@ -445,7 +456,7 @@ export class ChatAgentService implements IChatAgentService {
|
|||
const participants = this.getAgents().reduce<IChatParticipantMetadata[]>((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: [] });
|
||||
acc.push({ participant: a.id, command: command.name, disambiguation: command.disambiguation ?? [] });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@ export const CONTEXT_CHAT_INPUT_HAS_FOCUS = new RawContextKey<boolean>('chatInpu
|
|||
export const CONTEXT_IN_CHAT_INPUT = new RawContextKey<boolean>('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<boolean>('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") });
|
||||
|
||||
export const CONTEXT_CHAT_ENABLED = new RawContextKey<boolean>('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is registered.") });
|
||||
export const CONTEXT_CHAT_ENABLED = new RawContextKey<boolean>('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<boolean>('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<boolean>('chatExtensionInvalid', false, { type: 'boolean', description: localize('chatExtensionInvalid', "True when the installed chat extension is invalid and needs to be updated.") });
|
||||
export const CONTEXT_CHAT_INPUT_CURSOR_AT_TOP = new RawContextKey<boolean>('chatCursorAtTop', false);
|
||||
export const CONTEXT_CHAT_INPUT_HAS_AGENT = new RawContextKey<boolean>('chatInputHasAgent', false);
|
||||
export const CONTEXT_CHAT_LOCATION = new RawContextKey<ChatAgentLocation>('chatLocation', undefined);
|
||||
|
|
|
|||
|
|
@ -616,6 +616,8 @@ 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,
|
||||
|
|
@ -636,6 +638,30 @@ 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' &&
|
||||
|
|
|
|||
|
|
@ -481,9 +481,8 @@ export class ChatService extends Disposable implements IChatService {
|
|||
}
|
||||
|
||||
async sendRequest(sessionId: string, request: string, options?: IChatSendRequestOptions): Promise<IChatSendRequestData | undefined> {
|
||||
|
||||
this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`);
|
||||
if (!request.trim()) {
|
||||
if (!request.trim() && !options?.slashCommand && !options?.agentId) {
|
||||
this.trace('sendRequest', 'Rejected empty message');
|
||||
return;
|
||||
}
|
||||
|
|
@ -546,6 +545,7 @@ 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(model, location, defaultAgent.id);
|
||||
const defaultAgentHistory = this.getHistoryEntriesFromModel(requests, model.sessionId, 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(model, location, agent.id);
|
||||
const history = this.getHistoryEntriesFromModel(requests, model.sessionId, 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, location, agent.id), CancellationToken.None) : undefined;
|
||||
chatTitlePromise = model.getRequests().length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model.getRequests(), model.sessionId, 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(model: IChatModel, location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] {
|
||||
private getHistoryEntriesFromModel(requests: IChatRequestModel[], sessionId: string, location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] {
|
||||
const history: IChatAgentHistoryEntry[] = [];
|
||||
for (const request of model.getRequests()) {
|
||||
for (const request of requests) {
|
||||
if (!request.response) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -791,7 +791,7 @@ export class ChatService extends Disposable implements IChatService {
|
|||
|
||||
const promptTextResult = getPromptText(request.message);
|
||||
const historyRequest: IChatAgentRequest = {
|
||||
sessionId: model.sessionId,
|
||||
sessionId: sessionId,
|
||||
requestId: request.id,
|
||||
agentId: request.response.agent?.id ?? '',
|
||||
message: promptTextResult.message,
|
||||
|
|
|
|||
|
|
@ -941,12 +941,6 @@ export class StopReadAloud extends Action2 {
|
|||
when: ScopedChatSynthesisInProgress,
|
||||
group: 'navigation',
|
||||
order: -1
|
||||
},
|
||||
{
|
||||
id: MENU_INLINE_CHAT_WIDGET_SECONDARY,
|
||||
when: ScopedChatSynthesisInProgress,
|
||||
group: 'navigation',
|
||||
order: -1
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
@ -980,6 +974,15 @@ 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'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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, normalizeSerializableChatData, Response } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { ChatModel, ISerializableChatData1, ISerializableChatData2, ISerializableChatData3, 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,4 +230,53 @@ 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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
sortBy: this.viewState['sortBy'] ?? CommentsSortOrder.ResourceAscending,
|
||||
}, this.contextKeyService));
|
||||
this.filter = new Filter(new FilterOptions(this.filterWidget.getFilterText(), this.filters.showResolved, this.filters.showUnresolved));
|
||||
|
||||
|
|
|
|||
|
|
@ -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, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ContextKeyExpr, IContextKey, 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 = CONTEXT_KEY_SORT_BY.bindTo(this.contextKeyService);
|
||||
private _sortBy: IContextKey<CommentsSortOrder> = CONTEXT_KEY_SORT_BY.bindTo(this.contextKeyService);
|
||||
get sortBy(): CommentsSortOrder {
|
||||
return this._sortBy.get()!;
|
||||
return this._sortBy.get() ?? CommentsSortOrder.ResourceAscending;
|
||||
}
|
||||
set sortBy(sortBy: CommentsSortOrder) {
|
||||
if (this._sortBy.get() !== sortBy) {
|
||||
|
|
@ -208,7 +208,7 @@ registerAction2(class extends ViewAction<ICommentsView> {
|
|||
icon: Codicon.history,
|
||||
viewId: COMMENTS_VIEW_ID,
|
||||
toggled: {
|
||||
condition: ContextKeyExpr.equals('commentsView.sortBy', CommentsSortOrder.UpdatedAtDescending),
|
||||
condition: ContextKeyExpr.equals(CONTEXT_KEY_SORT_BY.key, CommentsSortOrder.UpdatedAtDescending),
|
||||
title: localize('sorting by updated at', "Updated Time"),
|
||||
},
|
||||
menu: {
|
||||
|
|
@ -229,13 +229,13 @@ registerAction2(class extends ViewAction<ICommentsView> {
|
|||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.${COMMENTS_VIEW_ID}.toggleSortByResource`,
|
||||
title: localize('toggle sorting by resource', "File"),
|
||||
title: localize('toggle sorting by resource', "Position in File"),
|
||||
category: localize('comments', "Comments"),
|
||||
icon: Codicon.history,
|
||||
viewId: COMMENTS_VIEW_ID,
|
||||
toggled: {
|
||||
condition: ContextKeyExpr.equals('commentsView.sortBy', CommentsSortOrder.ResourceAscending),
|
||||
title: localize('sorting by file', "File"),
|
||||
condition: ContextKeyExpr.equals(CONTEXT_KEY_SORT_BY.key, CommentsSortOrder.ResourceAscending),
|
||||
title: localize('sorting by position in file', "Position in File"),
|
||||
},
|
||||
menu: {
|
||||
id: commentSortSubmenu,
|
||||
|
|
|
|||
|
|
@ -12,20 +12,24 @@ 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 } from 'vs/base/common/observable';
|
||||
import { autorun, autorunWithStore, derived, IObservable, ISettableObservable, observableValue, transaction } 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 { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { EditorContributionCtor, EditorContributionInstantiation, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
|
||||
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';
|
||||
|
|
@ -38,7 +42,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 } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
|
||||
export class CallStackFrame {
|
||||
|
|
@ -97,6 +101,9 @@ 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';
|
||||
|
|
@ -137,6 +144,7 @@ export class CallStackWidget extends Disposable {
|
|||
multipleSelectionSupport: false,
|
||||
mouseSupport: false,
|
||||
keyboardSupport: false,
|
||||
setRowLineHeight: false,
|
||||
accessibilityProvider: instantiationService.createInstance(StackAccessibilityProvider),
|
||||
}
|
||||
) as WorkbenchList<ListItem>);
|
||||
|
|
@ -157,6 +165,17 @@ 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<void> {
|
||||
if (!this.cts) {
|
||||
return;
|
||||
|
|
@ -356,9 +375,9 @@ abstract class AbstractFrameRenderer<T extends IAbstractFrameRendererTemplateDat
|
|||
collapse.element.ariaExpanded = String(!collapsed);
|
||||
elements.root.classList.toggle('collapsed', collapsed);
|
||||
}));
|
||||
elementStore.add(collapse.onDidClick(() => {
|
||||
item.collapsed.set(!item.collapsed.get(), undefined);
|
||||
}));
|
||||
const toggleCollapse = () => item.collapsed.set(!item.collapsed.get(), undefined);
|
||||
elementStore.add(collapse.onDidClick(toggleCollapse));
|
||||
elementStore.add(dom.addDisposableListener(elements.title, 'click', toggleCollapse));
|
||||
}
|
||||
|
||||
disposeElement(element: ListItem, index: number, templateData: T, height: number | undefined): void {
|
||||
|
|
@ -382,26 +401,33 @@ class FrameCodeRenderer extends AbstractFrameRenderer<IStackTemplateData> {
|
|||
private readonly containingEditor: ICodeEditor | undefined,
|
||||
private readonly onLayout: Event<void>,
|
||||
@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 },
|
||||
{ isSimpleWidget: true, contributions },
|
||||
this.containingEditor,
|
||||
)
|
||||
: this.instantiationService.createInstance(
|
||||
CodeEditorWidget,
|
||||
data.elements.editor,
|
||||
editorOptions,
|
||||
{ isSimpleWidget: true },
|
||||
{ isSimpleWidget: true, contributions },
|
||||
);
|
||||
|
||||
data.templateStore.add(editor);
|
||||
|
|
@ -423,20 +449,6 @@ class FrameCodeRenderer extends AbstractFrameRenderer<IStackTemplateData> {
|
|||
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 => {
|
||||
|
|
@ -632,6 +644,73 @@ class SkippedRenderer implements IListRenderer<ListItem, ISkippedTemplateData> {
|
|||
}
|
||||
}
|
||||
|
||||
/** 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({
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@
|
|||
&[role="link"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-icon-label::before {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
|
|
@ -39,6 +43,7 @@
|
|||
.collapse-button {
|
||||
width: 16px;
|
||||
min-height: 1px; /* show even if empty */
|
||||
line-height: 0;
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
|
|
@ -56,6 +61,11 @@
|
|||
.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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ class ExtensionsViewState extends Disposable implements IExtensionsViewState {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
export interface ExtensionsListViewOptions {
|
||||
server?: IExtensionManagementServer;
|
||||
flexibleHeight?: boolean;
|
||||
|
|
|
|||
|
|
@ -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 } 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, ACTION_DISCARD_CHANGES } 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: 'inlineChat.discardHunkChange',
|
||||
id: ACTION_DISCARD_CHANGES,
|
||||
title: localize('discard', 'Discard'),
|
||||
icon: Codicon.chromeClose,
|
||||
precondition: CTX_INLINE_CHAT_VISIBLE,
|
||||
|
|
|
|||
|
|
@ -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, Session, StashedSession } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
|
||||
import { HunkInformation, HunkState, 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,6 +622,7 @@ 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);
|
||||
|
|
@ -636,13 +637,13 @@ export class InlineChatController implements IEditorContribution {
|
|||
}
|
||||
|
||||
const newEditor = editorPane.getControl();
|
||||
if (!newEditor || !isCodeEditor(newEditor) || !newEditor.hasModel()) {
|
||||
if (!isCodeEditor(newEditor) || !newEditor.hasModel()) {
|
||||
log('new editor is either missing or not a code editor or does not have a model');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._session) {
|
||||
log('controller does not have a session');
|
||||
if (this._inlineChatSessionService.getSession(newEditor, e.target)) {
|
||||
log('new editor ALREADY has a session');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -741,7 +742,7 @@ export class InlineChatController implements IEditorContribution {
|
|||
await responsePromise.p;
|
||||
await progressiveEditsQueue.whenIdle();
|
||||
|
||||
if (response.isCanceled) {
|
||||
if (response.result?.errorDetails) {
|
||||
await this._session.undoChangesUntil(response.requestId);
|
||||
}
|
||||
|
||||
|
|
@ -758,7 +759,6 @@ 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,8 +990,21 @@ export class InlineChatController implements IEditorContribution {
|
|||
// ---- controller API
|
||||
|
||||
showSaveHint(): void {
|
||||
const status = localize('savehint', "Accept or discard changes to continue saving");
|
||||
if (!this._session) {
|
||||
return;
|
||||
}
|
||||
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -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))(() => {
|
||||
this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel, editor.onDidBlurEditorWidget))(() => {
|
||||
this._session = undefined;
|
||||
this._sessionService.releaseSession(session);
|
||||
this._ctxHasStashedSession.reset();
|
||||
|
|
@ -360,27 +360,30 @@ export class HunkData {
|
|||
// mirror textModelN changes to textModel0 execept for those that
|
||||
// overlap with a hunk
|
||||
|
||||
type HunkRangePair = { rangeN: Range; range0: Range };
|
||||
type HunkRangePair = { rangeN: Range; range0: Range; markAccepted: () => void };
|
||||
const hunkRanges: HunkRangePair[] = [];
|
||||
|
||||
const ranges0: Range[] = [];
|
||||
|
||||
for (const { textModelNDecorations, textModel0Decorations, state } of this._data.values()) {
|
||||
for (const entry of this._data.values()) {
|
||||
|
||||
if (state === HunkState.Pending) {
|
||||
if (entry.state === HunkState.Pending) {
|
||||
// pending means the hunk's changes aren't "sync'd" yet
|
||||
for (let i = 1; i < textModelNDecorations.length; i++) {
|
||||
const rangeN = this._textModelN.getDecorationRange(textModelNDecorations[i]);
|
||||
const range0 = this._textModel0.getDecorationRange(textModel0Decorations[i]);
|
||||
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]);
|
||||
if (rangeN && range0) {
|
||||
hunkRanges.push({ rangeN, range0 });
|
||||
hunkRanges.push({
|
||||
rangeN, range0,
|
||||
markAccepted: () => entry.state = HunkState.Accepted
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} else if (state === HunkState.Accepted) {
|
||||
} else if (entry.state === HunkState.Accepted) {
|
||||
// accepted means the hunk's changes are also in textModel0
|
||||
for (let i = 1; i < textModel0Decorations.length; i++) {
|
||||
const range = this._textModel0.getDecorationRange(textModel0Decorations[i]);
|
||||
for (let i = 1; i < entry.textModel0Decorations.length; i++) {
|
||||
const range = this._textModel0.getDecorationRange(entry.textModel0Decorations[i]);
|
||||
if (range) {
|
||||
ranges0.push(range);
|
||||
}
|
||||
|
|
@ -399,16 +402,20 @@ export class HunkData {
|
|||
|
||||
let pendingChangesLen = 0;
|
||||
|
||||
for (const { rangeN, range0 } of hunkRanges) {
|
||||
if (rangeN.getEndPosition().isBefore(Range.getStartPosition(change.range))) {
|
||||
for (const entry of hunkRanges) {
|
||||
if (entry.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(rangeN);
|
||||
pendingChangesLen -= this._textModel0.getValueLengthInRange(range0);
|
||||
pendingChangesLen += this._textModelN.getValueLengthInRange(entry.rangeN);
|
||||
pendingChangesLen -= this._textModel0.getValueLengthInRange(entry.range0);
|
||||
|
||||
} else if (Range.areIntersectingOrTouching(rangeN, change.range)) {
|
||||
} 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();
|
||||
isOverlapping = true;
|
||||
break;
|
||||
|
||||
|
|
@ -447,24 +454,23 @@ export class HunkData {
|
|||
|
||||
diff ??= await this._editorWorkerService.computeDiff(this._textModel0.uri, this._textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }, 'advanced');
|
||||
|
||||
if (!diff || diff.changes.length === 0) {
|
||||
// return new HunkData([], session);
|
||||
return;
|
||||
}
|
||||
let mergedChanges: DetailedLineRangeMapping[] = [];
|
||||
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 } from 'vs/base/common/themables';
|
||||
import { themeColorFromId, ThemeIcon } 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 { 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 { 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 { assertType } from 'vs/base/common/types';
|
||||
import { IModelService } from 'vs/editor/common/services/model';
|
||||
import { performAsyncTextEdit, asProgressiveEdit } from './utils';
|
||||
|
|
@ -43,6 +43,9 @@ 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;
|
||||
|
|
@ -216,8 +219,10 @@ type HunkDisplayData = {
|
|||
|
||||
decorationIds: string[];
|
||||
|
||||
viewZoneId: string | undefined;
|
||||
viewZone: IViewZone;
|
||||
diffViewZoneId: string | undefined;
|
||||
diffViewZone: IViewZone;
|
||||
|
||||
lensActionsViewZoneIds?: string[];
|
||||
|
||||
distance: number;
|
||||
position: Position;
|
||||
|
|
@ -257,6 +262,7 @@ export class LiveStrategy extends EditModeStrategy {
|
|||
private readonly _ctxCurrentChangeShowsDiff: IContextKey<boolean>;
|
||||
|
||||
private readonly _progressiveEditingDecorations: IEditorDecorationsCollection;
|
||||
private readonly _lensActionsFactory: ConflictActionsFactory;
|
||||
private _editCount: number = 0;
|
||||
|
||||
constructor(
|
||||
|
|
@ -268,6 +274,8 @@ 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
|
||||
) {
|
||||
|
|
@ -276,6 +284,7 @@ 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));
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -487,45 +496,95 @@ export class LiveStrategy extends EditModeStrategy {
|
|||
afterLineNumber: -1,
|
||||
heightInLines: result.heightInLines,
|
||||
domNode,
|
||||
ordinal: 50000 + 1 // more than https://github.com/microsoft/vscode/blob/bf52a5cfb2c75a7327c9adeaefbddc06d529dcad/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts#L42
|
||||
ordinal: 50000 + 2 // 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.viewZoneId) {
|
||||
if (!data.diffViewZoneId) {
|
||||
const [hunkRange] = hunkData.getRangesN();
|
||||
viewZoneData.afterLineNumber = hunkRange.startLineNumber - 1;
|
||||
data.viewZoneId = viewZoneAccessor.addZone(viewZoneData);
|
||||
data.diffViewZoneId = viewZoneAccessor.addZone(viewZoneData);
|
||||
overlay?.updateExtraTop(result.heightInLines);
|
||||
} else {
|
||||
viewZoneAccessor.removeZone(data.viewZoneId!);
|
||||
viewZoneAccessor.removeZone(data.diffViewZoneId!);
|
||||
overlay?.updateExtraTop(0);
|
||||
data.viewZoneId = undefined;
|
||||
data.diffViewZoneId = undefined;
|
||||
}
|
||||
});
|
||||
this._ctxCurrentChangeShowsDiff.set(typeof data?.viewZoneId === 'string');
|
||||
this._ctxCurrentChangeShowsDiff.set(typeof data?.diffViewZoneId === 'string');
|
||||
scrollState.restore(this._editor);
|
||||
};
|
||||
|
||||
const overlay = this._showOverlayToolbar
|
||||
const overlay = this._showOverlayToolbar && false
|
||||
? 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.viewZoneId) {
|
||||
viewZoneAccessor.removeZone(data.viewZoneId);
|
||||
if (data.diffViewZoneId) {
|
||||
viewZoneAccessor.removeZone(data.diffViewZoneId);
|
||||
}
|
||||
data.decorationIds = [];
|
||||
data.viewZoneId = undefined;
|
||||
data.diffViewZoneId = undefined;
|
||||
|
||||
data.lensActionsViewZoneIds?.forEach(viewZoneAccessor.removeZone);
|
||||
data.lensActionsViewZoneIds = undefined;
|
||||
});
|
||||
|
||||
lensActions?.dispose();
|
||||
overlay?.dispose();
|
||||
};
|
||||
|
||||
|
|
@ -548,8 +607,9 @@ export class LiveStrategy extends EditModeStrategy {
|
|||
data = {
|
||||
hunk: hunkData,
|
||||
decorationIds,
|
||||
viewZoneId: '',
|
||||
viewZone: viewZoneData,
|
||||
diffViewZoneId: '',
|
||||
diffViewZone: viewZoneData,
|
||||
lensActionsViewZoneIds,
|
||||
distance: myDistance,
|
||||
position: hunkRanges[0].getStartPosition().delta(-1),
|
||||
acceptHunk,
|
||||
|
|
|
|||
|
|
@ -96,9 +96,9 @@ export class InlineChatWidget {
|
|||
h('div.accessibleViewer@accessibleViewer'),
|
||||
h('div.status@status', [
|
||||
h('div.label.info.hidden@infoLabel'),
|
||||
h('div.actions.button-style.hidden@toolbar1'),
|
||||
h('div.actions.hidden@toolbar1'),
|
||||
h('div.label.status.hidden@statusLabel'),
|
||||
h('div.actions.button-style.hidden@toolbar2'),
|
||||
h('div.actions.secondary.hidden@toolbar2'),
|
||||
]),
|
||||
]
|
||||
);
|
||||
|
|
@ -385,7 +385,7 @@ export class InlineChatWidget {
|
|||
}
|
||||
|
||||
protected _getExtraHeight(): number {
|
||||
return 4 /* padding */ + 2 /*border*/ + 4 /*shadow*/;
|
||||
return 2 /*border*/ + 4 /*shadow*/;
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
import { addDisposableListener, Dimension } from 'vs/base/browser/dom';
|
||||
import * as aria from 'vs/base/browser/ui/aria/aria';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { MutableDisposable, 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,6 +29,7 @@ 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;
|
||||
|
||||
|
|
@ -165,6 +166,7 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
|||
this.widget.focus();
|
||||
|
||||
revealZone();
|
||||
this._scrollUp.enable();
|
||||
}
|
||||
|
||||
override updatePositionAndHeight(position: Position): void {
|
||||
|
|
@ -186,14 +188,15 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
|||
return isResponseVM(candidate) && candidate.response.value.length > 0;
|
||||
});
|
||||
|
||||
if (hasResponse && zoneTop < scrollTop) {
|
||||
if (hasResponse && zoneTop < scrollTop || this._scrollUp.didScrollUp) {
|
||||
// don't reveal the zone if it is already out of view (unless we are still getting ready)
|
||||
return () => {
|
||||
// or if an outside scroll-up happened (e.g the user scrolled up to see the new content)
|
||||
return this._scrollUp.runIgnored(() => {
|
||||
scrollState.restore(this.editor);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
return this._scrollUp.runIgnored(() => {
|
||||
scrollState.restore(this.editor);
|
||||
|
||||
const scrollTop = this.editor.getScrollTop();
|
||||
|
|
@ -216,7 +219,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 {
|
||||
|
|
@ -229,6 +232,7 @@ 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);
|
||||
|
|
@ -237,3 +241,54 @@ 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
}
|
||||
|
||||
.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part {
|
||||
padding: 4px 6px 0 6px;
|
||||
padding: 2px 6px 0 6px;
|
||||
}
|
||||
|
||||
.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part .interactive-execute-toolbar {
|
||||
|
|
@ -32,6 +32,12 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -70,7 +76,15 @@
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6px 6px 0 6px
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.monaco-workbench .inline-chat > .status {
|
||||
.label,
|
||||
.actions {
|
||||
padding-top: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.monaco-workbench .inline-chat .status .actions.hidden {
|
||||
|
|
@ -92,7 +106,8 @@
|
|||
|
||||
.monaco-workbench .inline-chat .status .label.status {
|
||||
margin-left: auto;
|
||||
padding: 0 6px;
|
||||
padding-right: 6px;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.monaco-workbench .inline-chat .status .label.hidden,
|
||||
|
|
@ -156,6 +171,16 @@
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ export const CTX_INLINE_CHAT_RESPONSE_TYPE = new RawContextKey<InlineChatRespons
|
|||
// --- (selected) action identifier
|
||||
|
||||
export const ACTION_ACCEPT_CHANGES = 'inlineChat.acceptChanges';
|
||||
export const ACTION_DISCARD_CHANGES = 'inlineChat.discardHunkChange';
|
||||
export const ACTION_REGENERATE_RESPONSE = 'inlineChat.regenerate';
|
||||
export const ACTION_VIEW_IN_CHAT = 'inlineChat.viewInChat';
|
||||
export const ACTION_TOGGLE_DIFF = 'inlineChat.toggleDiff';
|
||||
|
|
|
|||
|
|
@ -779,7 +779,7 @@ suite('InteractiveChatController', function () {
|
|||
|
||||
});
|
||||
|
||||
test('Stopping/cancelling a request should undo its changes', async function () {
|
||||
test('Stopping/cancelling a request should NOT undo its changes', async function () {
|
||||
|
||||
model.setValue('World');
|
||||
|
||||
|
|
@ -819,7 +819,7 @@ suite('InteractiveChatController', function () {
|
|||
chatService.cancelCurrentRequestForSession(ctrl.chatWidget.viewModel!.model.sessionId);
|
||||
assert.strictEqual(await p2, undefined);
|
||||
|
||||
assert.strictEqual(model.getValue(), 'World');
|
||||
assert.strictEqual(model.getValue(), 'HelloWorld'); // CANCEL just stops the request and progressive typing but doesn't undo
|
||||
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/co
|
|||
import { NullWorkbenchAssignmentService } from 'vs/workbench/services/assignment/test/common/nullAssignmentService';
|
||||
import { ILanguageModelToolsService } from 'vs/workbench/contrib/chat/common/languageModelToolsService';
|
||||
import { MockLanguageModelToolsService } from 'vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService';
|
||||
import { IChatRequestModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { assertSnapshot } from 'vs/base/test/common/snapshot';
|
||||
|
||||
suite('InlineChatSession', function () {
|
||||
|
||||
|
|
@ -487,4 +489,90 @@ suite('InlineChatSession', function () {
|
|||
|
||||
inlineChatSessionService.releaseSession(session);
|
||||
});
|
||||
|
||||
test('Pressing Escape after inline chat errored with "response filtered" leaves document dirty #7764', async function () {
|
||||
|
||||
const origValue = `class Foo {
|
||||
private onError(error: string): void {
|
||||
if (/The request timed out|The network connection was lost/i.test(error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: error,
|
||||
source: nls.localize('update service', "Update Service"),
|
||||
});
|
||||
}
|
||||
}`;
|
||||
model.setValue(origValue);
|
||||
|
||||
const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None);
|
||||
assertType(session);
|
||||
|
||||
const fakeRequest = new class extends mock<IChatRequestModel>() {
|
||||
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' });
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ 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`;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1074,7 +1074,7 @@ export class DeletedElement extends SingleSideDiffElement {
|
|||
|
||||
layout(state: IDiffElementLayoutState) {
|
||||
DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._diffEditorContainer), () => {
|
||||
if (state.editorHeight || state.outerWidth) {
|
||||
if ((state.editorHeight || state.outerWidth) && this._editor) {
|
||||
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) {
|
||||
if ((state.editorHeight || state.outerWidth) && this._editor) {
|
||||
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.getValue() !== cell.original?.textModel.getValue() ? { reason: undefined } : false;
|
||||
return cell.modified?.textModel.getTextBufferHash() !== cell.original?.textModel.getTextBufferHash() ? { 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.getValue() !== this.cell.original.textModel.getValue());
|
||||
inputChanged.set(this.cell.modified.textModel.getTextBufferHash() !== this.cell.original.textModel.getTextBufferHash());
|
||||
|
||||
const ignoreWhitespace = NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE.bindTo(scopedContextKeyService);
|
||||
const ignore = this.textConfigurationService.getValue<boolean>(this.cell.modified.uri, 'diffEditor.ignoreTrimWhitespace');
|
||||
|
|
@ -1675,7 +1675,7 @@ export class ModifiedElement extends AbstractElementRenderer {
|
|||
const refreshToolbar = () => {
|
||||
const ignore = this.textConfigurationService.getValue<boolean>(this.cell.modified.uri, 'diffEditor.ignoreTrimWhitespace');
|
||||
ignoreWhitespace.set(ignore);
|
||||
const hasChanges = this.cell.modified.textModel.getValue() !== this.cell.original.textModel.getValue();
|
||||
const hasChanges = this.cell.modified.textModel.getTextBufferHash() !== this.cell.original.textModel.getTextBufferHash();
|
||||
inputChanged.set(hasChanges);
|
||||
|
||||
if (hasChanges) {
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ export abstract class DiffElementCellViewModelBase extends DiffElementViewModelB
|
|||
layoutState: CellLayoutState.Uninitialized
|
||||
};
|
||||
|
||||
this.cellFoldingState = modified?.textModel?.getValue() !== original?.textModel?.getValue() ? PropertyFoldingState.Expanded : PropertyFoldingState.Collapsed;
|
||||
this.cellFoldingState = modified?.getTextBufferHash() !== original?.getTextBufferHash() ? PropertyFoldingState.Expanded : PropertyFoldingState.Collapsed;
|
||||
this.metadataFoldingState = PropertyFoldingState.Collapsed;
|
||||
this.outputFoldingState = PropertyFoldingState.Collapsed;
|
||||
|
||||
|
|
|
|||
|
|
@ -441,16 +441,12 @@ 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,
|
||||
originalCell,
|
||||
modifiedCell,
|
||||
type,
|
||||
eventDispatcher,
|
||||
originalModel.cells[diff.originalCellIndex],
|
||||
modifiedModel.cells[diff.modifiedCellIndex],
|
||||
'unchanged', eventDispatcher,
|
||||
initData,
|
||||
notebookService
|
||||
);
|
||||
|
|
|
|||
|
|
@ -419,6 +419,10 @@ 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -314,7 +314,7 @@ class HistoryItemRenderer implements ITreeRenderer<SCMHistoryItemViewModelTreeEl
|
|||
const historyItemLabels = (historyItem.labels ?? [])
|
||||
.filter(l => labels.includes(l.title));
|
||||
|
||||
if (historyItemLabels) {
|
||||
if (historyItemLabels.length > 0) {
|
||||
const historyItemGroupLocalColor = colorTheme.getColor(historyItemGroupLocal);
|
||||
const historyItemGroupRemoteColor = colorTheme.getColor(historyItemGroupRemote);
|
||||
const historyItemGroupBaseColor = colorTheme.getColor(historyItemGroupBase);
|
||||
|
|
|
|||
|
|
@ -117,22 +117,21 @@ export class NotebookSearchService implements INotebookSearchService {
|
|||
}
|
||||
|
||||
private async doesFileExist(includes: string[], folderQueries: IFolderQuery<URI>[], token: CancellationToken): Promise<boolean> {
|
||||
const promises: Promise<void>[] = includes.map(async includePattern => {
|
||||
const promises: Promise<boolean>[] = 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
|
||||
exists: true,
|
||||
onlyFileScheme: true,
|
||||
});
|
||||
return this.searchService.fileSearch(
|
||||
query,
|
||||
token
|
||||
).then((ret) => {
|
||||
if (!ret.limitHit) {
|
||||
throw Error('File not found');
|
||||
}
|
||||
return !!ret.limitHit;
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.any(promises).then(() => true).catch(() => false);
|
||||
return Promise.any(promises);
|
||||
}
|
||||
|
||||
private async getClosedNotebookResults(textQuery: ITextQuery, scannedFiles: ResourceSet, token: CancellationToken): Promise<IClosedNotebookSearchResults> {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -172,9 +172,9 @@ function Set-MappedKeyHandler {
|
|||
|
||||
function Get-KeywordCompletionResult(
|
||||
$Keyword,
|
||||
$Description = $null
|
||||
$Description = $Keyword
|
||||
) {
|
||||
[System.Management.Automation.CompletionResult]::new($Keyword, $Keyword, [System.Management.Automation.CompletionResultType]::Keyword, $null -ne $Description ? $Description : $Keyword)
|
||||
[System.Management.Automation.CompletionResult]::new($Keyword, $Keyword, [System.Management.Automation.CompletionResultType]::Keyword, $Description)
|
||||
}
|
||||
|
||||
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 && $lastSlashIndex -lt $cursorIndex) {
|
||||
if ($lastSlashIndex -ne -1 -and $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('../')) {
|
||||
if ($completionPrefix -match '(\.\.\/)+') {
|
||||
$parentDir = "$($matches[0])../"
|
||||
if ($firstCompletion.CompletionText.StartsWith("..$([System.IO.Path]::DirectorySeparatorChar)")) {
|
||||
if ($completionPrefix -match "(\.\.\$([System.IO.Path]::DirectorySeparatorChar))+") {
|
||||
$parentDir = "$($matches[0])..$([System.IO.Path]::DirectorySeparatorChar)"
|
||||
$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 && $lastSlashIndex -lt $cursorIndex) {
|
||||
if ($lastSlashIndex -ne -1 -and $lastSlashIndex -lt $cursorIndex) {
|
||||
$newCursorIndex = $lastSlashIndex + 1
|
||||
$completionPrefix = $completionPrefix.Substring(0, $newCursorIndex)
|
||||
$prefixCursorDelta = $cursorIndex - $newCursorIndex
|
||||
|
|
|
|||
|
|
@ -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')) {
|
||||
if (dom.isHTMLElement(event.target) && (event.target.classList.contains('scrollbar') || event.target.classList.contains('slider'))) {
|
||||
return { cancelContextMenu: true };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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';
|
||||
|
|
@ -170,16 +171,20 @@ 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.
|
||||
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. 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;
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ 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,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ 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;
|
||||
|
|
|
|||
|
|
@ -838,22 +838,21 @@ class TreeActionsProvider {
|
|||
}
|
||||
|
||||
if (element instanceof TestMessageElement) {
|
||||
id = MenuId.TestMessageContext;
|
||||
contextKeys.push([TestingContextKeys.testMessageContext.key, element.contextValue]);
|
||||
|
||||
primary.push(new Action(
|
||||
'testing.outputPeek.goToFile',
|
||||
localize('testing.goToFile', "Go to Source"),
|
||||
'testing.outputPeek.goToTest',
|
||||
localize('testing.goToTest', "Go to Test"),
|
||||
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 Source"),
|
||||
localize('testing.goToError', "Go to Error"),
|
||||
ThemeIcon.asClassName(Codicon.goToFile),
|
||||
undefined,
|
||||
() => this.editorService.openEditor({
|
||||
|
|
|
|||
|
|
@ -322,6 +322,13 @@ 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;
|
||||
|
|
|
|||
|
|
@ -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, GoToNextMessageAction, GoToPreviousMessageAction, OpenMessageInEditorAction, TestResultsView, TestingOutputPeekController, TestingPeekOpener, ToggleTestingPeekHistory } from 'vs/workbench/contrib/testing/browser/testingOutputPeek';
|
||||
import { CloseTestPeek, CollapsePeekStack, 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,6 +136,7 @@ registerAction2(GoToPreviousMessageAction);
|
|||
registerAction2(GoToNextMessageAction);
|
||||
registerAction2(CloseTestPeek);
|
||||
registerAction2(ToggleTestingPeekHistory);
|
||||
registerAction2(CollapsePeekStack);
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingContentProvider, LifecyclePhase.Restored);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingPeekOpener, LifecyclePhase.Eventually);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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';
|
||||
|
|
@ -456,11 +457,24 @@ export class TestingDecorations extends Disposable implements IEditorContributio
|
|||
decorations.syncDecorations(this._currentUri);
|
||||
}
|
||||
}));
|
||||
this._register(this.editor.onKeyDown(e => {
|
||||
if (e.keyCode === KeyCode.Alt && this._currentUri) {
|
||||
decorations.updateDecorationsAlternateAction(this._currentUri!, true);
|
||||
|
||||
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(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);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ 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';
|
||||
|
|
@ -43,6 +44,7 @@ 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';
|
||||
|
|
@ -52,7 +54,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, mapFindTestMessage } from 'vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject';
|
||||
import { InspectSubject, MessageSubject, TaskSubject, TestOutputSubject, inspectSubjectHasStack, 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';
|
||||
|
|
@ -498,6 +500,13 @@ 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.
|
||||
*/
|
||||
|
|
@ -645,10 +654,14 @@ class TestResultsPeek extends PeekViewWidget {
|
|||
private static lastHeightInLines?: number;
|
||||
|
||||
private readonly visibilityChange = this._disposables.add(new Emitter<boolean>());
|
||||
private readonly _current = observableValue<InspectSubject | undefined>('testPeekCurrent', undefined);
|
||||
private content!: TestResultsViewContent;
|
||||
private scopedContextKeyService!: IContextKeyService;
|
||||
private dimension?: dom.Dimension;
|
||||
public current?: InspectSubject;
|
||||
|
||||
public get current() {
|
||||
return this._current.get();
|
||||
}
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
|
|
@ -702,7 +715,14 @@ class TestResultsPeek extends PeekViewWidget {
|
|||
protected override _fillHead(container: HTMLElement): void {
|
||||
super._fillHead(container);
|
||||
|
||||
const menu = this.menuService.createMenu(MenuId.TestPeekTitle, this.contextKeyService);
|
||||
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 actionBar = this._actionbarWidget!;
|
||||
this._disposables.add(menu.onDidChange(() => {
|
||||
actions.length = 0;
|
||||
|
|
@ -732,7 +752,7 @@ class TestResultsPeek extends PeekViewWidget {
|
|||
*/
|
||||
public setModel(subject: InspectSubject): Promise<void> {
|
||||
if (subject instanceof TaskSubject || subject instanceof TestOutputSubject) {
|
||||
this.current = subject;
|
||||
this._current.set(subject, undefined);
|
||||
return this.showInPlace(subject);
|
||||
}
|
||||
|
||||
|
|
@ -743,14 +763,14 @@ class TestResultsPeek extends PeekViewWidget {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.current = subject;
|
||||
this._current.set(subject, undefined);
|
||||
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(
|
||||
subject instanceof MessageSubject && subject.stack?.length ? Math.ceil(this.getVisibleEditorLines() / 2) : 0,
|
||||
inspectSubjectHasStack(subject) ? Math.ceil(this.getVisibleEditorLines() / 2) : 0,
|
||||
hintMessagePeekHeight(message)
|
||||
);
|
||||
|
||||
|
|
@ -760,6 +780,13 @@ 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.
|
||||
|
|
@ -1025,6 +1052,31 @@ 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() {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ 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<boolean> } = {
|
||||
[TestRunProfileBitset.Run]: hasRunnableTests,
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi
|
|||
private profileWidget: ProfileWidget | undefined;
|
||||
|
||||
private model: UserDataProfilesEditorModel | undefined;
|
||||
private templates: readonly IProfileTemplateInfo[] = [];
|
||||
|
||||
constructor(
|
||||
group: IEditorGroup,
|
||||
|
|
@ -207,7 +208,7 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi
|
|||
actions: {
|
||||
getActions: () => {
|
||||
const actions: IAction[] = [];
|
||||
if (this.model?.templates.length) {
|
||||
if (this.templates.length) {
|
||||
actions.push(new SubmenuAction('from.template', localize('from template', "From Template"), this.getCreateFromTemplateActions()));
|
||||
actions.push(new Separator());
|
||||
}
|
||||
|
|
@ -225,15 +226,13 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi
|
|||
}
|
||||
|
||||
private getCreateFromTemplateActions(): IAction[] {
|
||||
return this.model
|
||||
? this.model.templates.map(template =>
|
||||
new Action(
|
||||
`template:${template.url}`,
|
||||
template.name,
|
||||
undefined,
|
||||
true,
|
||||
() => this.createNewProfile(URI.parse(template.url))))
|
||||
: [];
|
||||
return this.templates.map(template =>
|
||||
new Action(
|
||||
`template:${template.url}`,
|
||||
template.name,
|
||||
undefined,
|
||||
true,
|
||||
() => this.createNewProfile(URI.parse(template.url))));
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
|
@ -343,9 +342,12 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi
|
|||
override async setInput(input: UserDataProfilesEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
|
||||
await super.setInput(input, options, context, token);
|
||||
this.model = await input.resolve();
|
||||
if (this.profileWidget) {
|
||||
this.profileWidget.templates = this.model.templates;
|
||||
}
|
||||
this.model.getTemplates().then(templates => {
|
||||
this.templates = templates;
|
||||
if (this.profileWidget) {
|
||||
this.profileWidget.templates = templates;
|
||||
}
|
||||
});
|
||||
this.updateProfilesList();
|
||||
this._register(this.model.onDidChange(element =>
|
||||
this.updateProfilesList(element)));
|
||||
|
|
@ -710,7 +712,6 @@ class ProfileTreeDataSource implements IAsyncDataSource<AbstractUserDataProfileE
|
|||
children.push({ element: 'name', root: element });
|
||||
children.push({ element: 'icon', root: element });
|
||||
}
|
||||
children.push({ element: 'useForCurrent', root: element });
|
||||
children.push({ element: 'useAsDefault', root: element });
|
||||
children.push({ element: 'contents', root: element });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -713,8 +713,7 @@ export class UserDataProfilesEditorModel extends EditorModel {
|
|||
private _onDidChange = this._register(new Emitter<AbstractUserDataProfileElement | undefined>());
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
private _templates: IProfileTemplateInfo[] | undefined;
|
||||
get templates(): readonly IProfileTemplateInfo[] { return this._templates ?? []; }
|
||||
private templates: Promise<readonly IProfileTemplateInfo[]> | undefined;
|
||||
|
||||
constructor(
|
||||
@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
|
||||
|
|
@ -761,9 +760,11 @@ export class UserDataProfilesEditorModel extends EditorModel {
|
|||
}
|
||||
}
|
||||
|
||||
override async resolve(): Promise<void> {
|
||||
await super.resolve();
|
||||
this._templates = await this.userDataProfileManagementService.getBuiltinProfileTemplates();
|
||||
getTemplates(): Promise<readonly IProfileTemplateInfo[]> {
|
||||
if (!this.templates) {
|
||||
this.templates = this.userDataProfileManagementService.getBuiltinProfileTemplates();
|
||||
}
|
||||
return this.templates;
|
||||
}
|
||||
|
||||
private createProfileElement(profile: IUserDataProfile): [UserDataProfileElement, DisposableStore] {
|
||||
|
|
@ -771,7 +772,7 @@ export class UserDataProfilesEditorModel extends EditorModel {
|
|||
|
||||
const activateAction = disposables.add(new Action(
|
||||
'userDataProfile.activate',
|
||||
localize('active', "Use for Current Window"),
|
||||
localize('active', "Use this Profile for Current Window"),
|
||||
ThemeIcon.asClassName(Codicon.check),
|
||||
true,
|
||||
() => this.userDataProfileManagementService.switchProfile(profileElement.profile)
|
||||
|
|
@ -808,25 +809,16 @@ 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,
|
||||
|
|
@ -834,16 +826,9 @@ export class UserDataProfilesEditorModel extends EditorModel {
|
|||
[primaryActions, secondaryActions]
|
||||
));
|
||||
|
||||
activateAction.checked = this.userDataProfileService.currentProfile.id === profileElement.profile.id;
|
||||
activateAction.enabled = this.userDataProfileService.currentProfile.id !== profileElement.profile.id;
|
||||
disposables.add(this.userDataProfileService.onDidChangeCurrentProfile(() =>
|
||||
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;
|
||||
}
|
||||
}));
|
||||
activateAction.enabled = this.userDataProfileService.currentProfile.id !== profileElement.profile.id));
|
||||
|
||||
return [profileElement, disposables];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
|
|||
'onSettingChanged:workbench.colorTheme',
|
||||
'onCommand:workbench.action.selectTheme'
|
||||
],
|
||||
when: '!accessibilityModeEnabled',
|
||||
media: { type: 'markdown', path: 'theme_picker', }
|
||||
},
|
||||
{
|
||||
|
|
@ -399,7 +400,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
|
|||
isFeatured: true,
|
||||
icon: setupIcon,
|
||||
when: CONTEXT_ACCESSIBILITY_MODE_ENABLED.key,
|
||||
next: 'SetupScreenReaderExtended',
|
||||
next: 'Setup',
|
||||
content: {
|
||||
type: 'steps',
|
||||
steps: [
|
||||
|
|
@ -470,90 +471,6 @@ 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,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ 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 {
|
||||
|
|
@ -240,7 +239,7 @@ import product from 'vs/platform/product/common/product';
|
|||
'type': 'boolean',
|
||||
'included': isLinux,
|
||||
'markdownDescription': localize('window.experimentalControlOverlay', "Show the native window controls when {0} is set to `custom` (Linux only).", '`#window.titleBarStyle#`'),
|
||||
'default': product.quality !== 'stable', // TODO@bpasero disable by default in stable for now (TODO@bpasero TODO@benibenj flip when custom title is default)
|
||||
'default': true
|
||||
},
|
||||
'window.customTitleBarVisibility': {
|
||||
'type': 'string',
|
||||
|
|
|
|||
|
|
@ -156,17 +156,17 @@ export class NativeTitlebarPart extends BrowserTitlebarPart {
|
|||
})));
|
||||
}
|
||||
|
||||
// Window Controls (Native Windows/Linux)
|
||||
if (!isMacintosh && !hasNativeTitlebar(this.configurationService) && !isWCOEnabled() && this.primaryWindowControls) {
|
||||
// Window Controls (Native Linux when WCO is disabled)
|
||||
if (isLinux && !hasNativeTitlebar(this.configurationService) && !isWCOEnabled() && this.windowControlsContainer) {
|
||||
|
||||
// Minimize
|
||||
const minimizeIcon = append(this.primaryWindowControls, $('div.window-icon.window-minimize' + ThemeIcon.asCSSSelector(Codicon.chromeMinimize)));
|
||||
const minimizeIcon = append(this.windowControlsContainer, $('div.window-icon.window-minimize' + ThemeIcon.asCSSSelector(Codicon.chromeMinimize)));
|
||||
this._register(addDisposableListener(minimizeIcon, EventType.CLICK, () => {
|
||||
this.nativeHostService.minimizeWindow({ targetWindowId });
|
||||
}));
|
||||
|
||||
// Restore
|
||||
this.maxRestoreControl = append(this.primaryWindowControls, $('div.window-icon.window-max-restore'));
|
||||
this.maxRestoreControl = append(this.windowControlsContainer, $('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.primaryWindowControls, $('div.window-icon.window-close' + ThemeIcon.asCSSSelector(Codicon.chromeClose)));
|
||||
const closeIcon = append(this.windowControlsContainer, $('div.window-icon.window-close' + ThemeIcon.asCSSSelector(Codicon.chromeClose)));
|
||||
this._register(addDisposableListener(closeIcon, EventType.CLICK, () => {
|
||||
this.nativeHostService.closeWindow({ targetWindowId });
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
|||
public readonly onEnablementChanged: Event<readonly IExtension[]> = this._onEnablementChanged.event;
|
||||
|
||||
protected readonly extensionsManager: ExtensionsManager;
|
||||
private readonly storageManger: StorageManager;
|
||||
private readonly storageManager: StorageManager;
|
||||
|
||||
constructor(
|
||||
@IStorageService storageService: IStorageService,
|
||||
|
|
@ -63,7 +63,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
|||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
this.storageManger = this._register(new StorageManager(storageService));
|
||||
this.storageManager = 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.storageManger.get(storageId, StorageScope.WORKSPACE);
|
||||
return this.storageManager.get(storageId, StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void {
|
||||
this.storageManger.set(storageId, extensions, StorageScope.WORKSPACE);
|
||||
this.storageManager.set(storageId, extensions, StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
private async _onDidChangeGloballyDisabledExtensions(extensionIdentifiers: ReadonlyArray<IExtensionIdentifier>, source?: string): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ 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;
|
||||
|
|
@ -53,7 +55,7 @@ class FileSearchEngine {
|
|||
|
||||
private globalExcludePattern?: glob.ParsedExpression;
|
||||
|
||||
constructor(private config: IFileQuery, private provider: FileSearchProviderNew, private sessionToken?: unknown) {
|
||||
constructor(private config: IFileQuery, private provider: FileSearchProviderNew, private sessionLifecycle?: SessionLifecycle) {
|
||||
this.filePattern = config.filePattern;
|
||||
this.includePattern = config.includePattern && glob.parse(config.includePattern);
|
||||
this.maxResults = config.maxResults || undefined;
|
||||
|
|
@ -116,10 +118,11 @@ class FileSearchEngine {
|
|||
private async doSearch(fqs: IFolderQuery<URI>[], onResult: (match: IInternalFileMatch) => void): Promise<IFileSearchProviderStats | null> {
|
||||
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: this.sessionToken
|
||||
session
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -301,11 +304,30 @@ 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<string, unknown>();
|
||||
private readonly sessions = new Map<string, SessionLifecycle>();
|
||||
|
||||
fileSearch(config: IFileQuery, provider: FileSearchProviderNew, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): Promise<ISearchCompleteStats> {
|
||||
const sessionTokenSource = this.getSessionTokenSource(config.cacheKey);
|
||||
|
|
@ -333,17 +355,19 @@ 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): unknown {
|
||||
private getSessionTokenSource(cacheKey: string | undefined): SessionLifecycle | undefined {
|
||||
if (!cacheKey) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!this.sessions.has(cacheKey)) {
|
||||
this.sessions.set(cacheKey, new Object());
|
||||
this.sessions.set(cacheKey, new SessionLifecycle());
|
||||
}
|
||||
|
||||
return this.sessions.get(cacheKey);
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ interface ICommonQueryBuilderOptions {
|
|||
disregardSearchExcludeSettings?: boolean;
|
||||
ignoreSymlinks?: boolean;
|
||||
onlyOpenEditors?: boolean;
|
||||
onlyFileScheme?: boolean;
|
||||
}
|
||||
|
||||
export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions {
|
||||
|
|
@ -260,7 +261,8 @@ export class QueryBuilder {
|
|||
excludePattern: excludeSearchPathsInfo.pattern,
|
||||
includePattern: includeSearchPathsInfo.pattern,
|
||||
onlyOpenEditors: options.onlyOpenEditors,
|
||||
maxResults: options.maxResults
|
||||
maxResults: options.maxResults,
|
||||
onlyFileScheme: options.onlyFileScheme
|
||||
};
|
||||
|
||||
if (options.onlyOpenEditors) {
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ export interface ICommonQueryProps<U extends UriComponents> {
|
|||
|
||||
maxResults?: number;
|
||||
usingSearchPaths?: boolean;
|
||||
onlyFileScheme?: boolean;
|
||||
}
|
||||
|
||||
export interface IFileQueryProps<U extends UriComponents> extends ICommonQueryProps<U> {
|
||||
|
|
|
|||
|
|
@ -271,6 +271,9 @@ 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ export class TestNativeHostService implements INativeHostService {
|
|||
async closeWindow(): Promise<void> { }
|
||||
async quit(): Promise<void> { }
|
||||
async exit(code: number): Promise<void> { }
|
||||
async openDevTools(options?: Partial<Electron.OpenDevToolsOptions> & INativeHostOptions | undefined): Promise<void> { }
|
||||
async openDevTools(): Promise<void> { }
|
||||
async toggleDevTools(): Promise<void> { }
|
||||
async resolveProxy(url: string): Promise<string | undefined> { return undefined; }
|
||||
async lookupAuthorization(authInfo: AuthInfo): Promise<Credentials | undefined> { return undefined; }
|
||||
|
|
|
|||
|
|
@ -3,11 +3,6 @@
|
|||
* 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';
|
||||
|
|
@ -15,7 +10,6 @@ import 'vs/editor/editor.all';
|
|||
import 'vs/workbench/api/browser/extensionHost.contribution';
|
||||
import 'vs/workbench/browser/workbench.contribution';
|
||||
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue