mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-14 03:58:33 -05:00
Merge from vscode 777931080477e28b7c27e8f7d4b0d69897945946 (#9220)
This commit is contained in:
@@ -37,7 +37,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape {
|
||||
canSelectFiles: options.canSelectFiles || (!options.canSelectFiles && !options.canSelectFolders),
|
||||
canSelectFolders: options.canSelectFolders,
|
||||
canSelectMany: options.canSelectMany,
|
||||
defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined
|
||||
defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined,
|
||||
title: options.title || undefined
|
||||
};
|
||||
if (options.filters) {
|
||||
result.filters = [];
|
||||
@@ -49,7 +50,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape {
|
||||
private static _convertSaveOptions(options: MainThreadDialogSaveOptions): ISaveDialogOptions {
|
||||
const result: ISaveDialogOptions = {
|
||||
defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined,
|
||||
saveLabel: options.saveLabel || undefined
|
||||
saveLabel: options.saveLabel || undefined,
|
||||
title: options.title || undefined
|
||||
};
|
||||
if (options.filters) {
|
||||
result.filters = [];
|
||||
|
||||
@@ -10,7 +10,7 @@ import { RenderLineNumbersType, TextEditorCursorStyle, cursorStyleToString, Edit
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
||||
import { IDecorationOptions, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { IIdentifiedSingleEditOperation, ISingleEditOperation, ITextModel, ITextModelUpdateOptions } from 'vs/editor/common/model';
|
||||
import { ISingleEditOperation, ITextModel, ITextModelUpdateOptions, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
|
||||
import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol';
|
||||
|
||||
@@ -50,6 +50,7 @@ import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtens
|
||||
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views';
|
||||
@@ -177,12 +178,14 @@ export interface MainThreadDialogOpenOptions {
|
||||
canSelectFolders?: boolean;
|
||||
canSelectMany?: boolean;
|
||||
filters?: { [name: string]: string[]; };
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface MainThreadDialogSaveOptions {
|
||||
defaultUri?: UriComponents;
|
||||
saveLabel?: string;
|
||||
filters?: { [name: string]: string[]; };
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface MainThreadDiaglogsShape extends IDisposable {
|
||||
@@ -1098,18 +1101,25 @@ export interface IWorkspaceSymbolsDto extends IdObject {
|
||||
symbols: IWorkspaceSymbolDto[];
|
||||
}
|
||||
|
||||
export interface IWorkspaceEditEntryMetadataDto {
|
||||
needsConfirmation: boolean;
|
||||
label: string;
|
||||
description?: string;
|
||||
iconPath?: { id: string } | UriComponents | { light: UriComponents, dark: UriComponents };
|
||||
}
|
||||
|
||||
export interface IWorkspaceFileEditDto {
|
||||
oldUri?: UriComponents;
|
||||
newUri?: UriComponents;
|
||||
options?: modes.WorkspaceFileEditOptions
|
||||
metadata?: modes.WorkspaceEditMetadata;
|
||||
metadata?: IWorkspaceEditEntryMetadataDto;
|
||||
}
|
||||
|
||||
export interface IWorkspaceTextEditDto {
|
||||
resource: UriComponents;
|
||||
edit: modes.TextEdit;
|
||||
modelVersionId?: number;
|
||||
metadata?: modes.WorkspaceEditMetadata;
|
||||
metadata?: IWorkspaceEditEntryMetadataDto;
|
||||
}
|
||||
|
||||
export interface IWorkspaceEditDto {
|
||||
@@ -1128,6 +1138,9 @@ export function reviveWorkspaceEditDto(data: IWorkspaceEditDto | undefined): mod
|
||||
(<IWorkspaceFileEditDto>edit).newUri = URI.revive((<IWorkspaceFileEditDto>edit).newUri);
|
||||
(<IWorkspaceFileEditDto>edit).oldUri = URI.revive((<IWorkspaceFileEditDto>edit).oldUri);
|
||||
}
|
||||
if (edit.metadata && edit.metadata.iconPath) {
|
||||
edit.metadata = revive(edit.metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
return <modes.WorkspaceEdit>data;
|
||||
|
||||
@@ -736,6 +736,35 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.toggleSelection',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter,
|
||||
handler: (accessor) => {
|
||||
const widget = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
if (!widget || isLegacyTree(widget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focus = widget.getFocus();
|
||||
|
||||
if (focus.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = widget.getSelection();
|
||||
const index = selection.indexOf(focus[0]);
|
||||
|
||||
if (index > -1) {
|
||||
widget.setSelection([...selection.slice(0, index), ...selection.slice(index + 1)]);
|
||||
} else {
|
||||
widget.setSelection([...selection, focus[0]]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.toggleExpand',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
|
||||
@@ -28,7 +28,6 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { isStandalone } from 'vs/base/browser/browser';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
|
||||
export interface IDraggedResource {
|
||||
@@ -343,7 +342,6 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
|
||||
// Editors: enables cross window DND of tabs into the editor area
|
||||
const textFileService = accessor.get(ITextFileService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const modelService = accessor.get(IModelService);
|
||||
|
||||
const draggedEditors: ISerializedDraggedEditor[] = [];
|
||||
files.forEach(file => {
|
||||
@@ -374,11 +372,8 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
|
||||
// If the resource is dirty or untitled, send over its content
|
||||
// to restore dirty state. Get that from the text model directly
|
||||
let content: string | undefined = undefined;
|
||||
if (textFileService.isDirty(file.resource)) {
|
||||
const textModel = modelService.getModel(file.resource);
|
||||
if (textModel) {
|
||||
content = textModel.getValue();
|
||||
}
|
||||
if (model?.isDirty()) {
|
||||
content = model.textEditorModel.getValue();
|
||||
}
|
||||
|
||||
// Add as dragged editor
|
||||
|
||||
@@ -148,8 +148,8 @@ export class ResourceLabels extends Disposable {
|
||||
}));
|
||||
|
||||
// notify when untitled labels change
|
||||
this.textFileService.untitled.onDidChangeLabel(resource => {
|
||||
this._widgets.forEach(widget => widget.notifyUntitledLabelChange(resource));
|
||||
this.textFileService.untitled.onDidChangeLabel(model => {
|
||||
this._widgets.forEach(widget => widget.notifyUntitledLabelChange(model.resource));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -122,8 +122,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
|
||||
private workbenchGrid!: SerializableGrid<ISerializableView>;
|
||||
|
||||
private editorWidgetSet = new Set<IEditor>();
|
||||
|
||||
private disposed: boolean | undefined;
|
||||
|
||||
private titleBarPartView!: ISerializableView;
|
||||
@@ -198,7 +196,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
wasSideBarVisible: false,
|
||||
wasPanelVisible: false,
|
||||
transitionDisposables: new DisposableStore(),
|
||||
setNotificationsFilter: false
|
||||
setNotificationsFilter: false,
|
||||
editorWidgetSet: new Set<IEditor>()
|
||||
},
|
||||
|
||||
};
|
||||
@@ -708,15 +707,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
editor.updateOptions({ lineNumbers });
|
||||
};
|
||||
|
||||
const editorWidgetSet = this.state.zenMode.editorWidgetSet;
|
||||
if (!lineNumbers) {
|
||||
// Reset line numbers on all editors visible and non-visible
|
||||
for (const editor of this.editorWidgetSet) {
|
||||
for (const editor of editorWidgetSet) {
|
||||
setEditorLineNumbers(editor);
|
||||
}
|
||||
this.editorWidgetSet.clear();
|
||||
editorWidgetSet.clear();
|
||||
} else {
|
||||
this.editorService.visibleTextEditorWidgets.forEach(editor => {
|
||||
this.editorWidgetSet.add(editor);
|
||||
if (!editorWidgetSet.has(editor)) {
|
||||
editorWidgetSet.add(editor);
|
||||
this.state.zenMode.transitionDisposables.add(editor.onDidDispose(() => {
|
||||
editorWidgetSet.delete(editor);
|
||||
}));
|
||||
}
|
||||
setEditorLineNumbers(editor);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -470,7 +470,7 @@ export class BreadcrumbsControl {
|
||||
this._ckBreadcrumbsActive.set(value);
|
||||
}
|
||||
|
||||
private _revealInEditor(event: IBreadcrumbsItemEvent, element: any, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void {
|
||||
private _revealInEditor(event: IBreadcrumbsItemEvent, element: BreadcrumbElement, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void {
|
||||
if (element instanceof FileElement) {
|
||||
if (element.kind === FileKind.FILE) {
|
||||
// open file in any editor
|
||||
|
||||
@@ -71,10 +71,8 @@ export class BaseSplitEditorAction extends Action {
|
||||
}));
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
async run(context?: IEditorIdentifier): Promise<any> {
|
||||
splitEditor(this.editorGroupService, this.direction, context);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +181,7 @@ export class JoinTwoGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
async run(context?: IEditorIdentifier): Promise<any> {
|
||||
let sourceGroup: IEditorGroup | undefined;
|
||||
if (context && typeof context.groupId === 'number') {
|
||||
sourceGroup = this.editorGroupService.getGroup(context.groupId);
|
||||
@@ -198,12 +196,10 @@ export class JoinTwoGroupsAction extends Action {
|
||||
if (targetGroup && sourceGroup !== targetGroup) {
|
||||
this.editorGroupService.mergeGroup(sourceGroup, targetGroup);
|
||||
|
||||
return Promise.resolve(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,10 +216,8 @@ export class JoinAllGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
async run(context?: IEditorIdentifier): Promise<any> {
|
||||
mergeAllGroups(this.editorGroupService);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,11 +234,9 @@ export class NavigateBetweenGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const nextGroup = this.editorGroupService.findGroup({ location: GroupLocation.NEXT }, this.editorGroupService.activeGroup, true);
|
||||
nextGroup.focus();
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,10 +253,8 @@ export class FocusActiveGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.editorGroupService.activeGroup.focus();
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,13 +269,11 @@ export abstract class BaseFocusGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const group = this.editorGroupService.findGroup(this.scope, this.editorGroupService.activeGroup, true);
|
||||
if (group) {
|
||||
group.focus();
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +409,7 @@ export class OpenToSideFromQuickOpenAction extends Action {
|
||||
this.class = (preferredDirection === GroupDirection.RIGHT) ? 'codicon-split-horizontal' : 'codicon-split-vertical';
|
||||
}
|
||||
|
||||
run(context: any): Promise<any> {
|
||||
async run(context: any): Promise<any> {
|
||||
const entry = toEditorQuickOpenEntry(context);
|
||||
if (entry) {
|
||||
const input = entry.getInput();
|
||||
@@ -436,8 +424,6 @@ export class OpenToSideFromQuickOpenAction extends Action {
|
||||
return this.editorService.openEditor(resourceInput, SIDE_GROUP);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,7 +476,7 @@ export class CloseOneEditorAction extends Action {
|
||||
super(id, label, 'codicon-close');
|
||||
}
|
||||
|
||||
run(context?: IEditorCommandsContext): Promise<any> {
|
||||
async run(context?: IEditorCommandsContext): Promise<any> {
|
||||
let group: IEditorGroup | undefined;
|
||||
let editorIndex: number | undefined;
|
||||
if (context) {
|
||||
@@ -517,8 +503,6 @@ export class CloseOneEditorAction extends Action {
|
||||
if (group.activeEditor) {
|
||||
return group.closeEditor(group.activeEditor);
|
||||
}
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,8 +538,6 @@ export class RevertAndCloseEditorAction extends Action {
|
||||
|
||||
group.closeEditor(editor);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,13 +555,11 @@ export class CloseLeftEditorsInGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
async run(context?: IEditorIdentifier): Promise<any> {
|
||||
const { group, editor } = getTarget(this.editorService, this.editorGroupService, context);
|
||||
if (group && editor) {
|
||||
return group.closeEditors({ direction: CloseDirection.LEFT, except: editor });
|
||||
}
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,9 +716,9 @@ export class CloseEditorsInOtherGroupsAction extends Action {
|
||||
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup;
|
||||
return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => {
|
||||
return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async g => {
|
||||
if (groupToSkip && g.id === groupToSkip.id) {
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
return g.closeAllEditors();
|
||||
@@ -760,13 +740,11 @@ export class CloseEditorInAllGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const activeEditor = this.editorService.activeEditor;
|
||||
if (activeEditor) {
|
||||
return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor)));
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -781,7 +759,7 @@ export class BaseMoveGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
async run(context?: IEditorIdentifier): Promise<any> {
|
||||
let sourceGroup: IEditorGroup | undefined;
|
||||
if (context && typeof context.groupId === 'number') {
|
||||
sourceGroup = this.editorGroupService.getGroup(context.groupId);
|
||||
@@ -795,8 +773,6 @@ export class BaseMoveGroupAction extends Action {
|
||||
this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private findTargetGroup(sourceGroup: IEditorGroup): IEditorGroup | undefined {
|
||||
@@ -892,10 +868,8 @@ export class MinimizeOtherGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -908,10 +882,8 @@ export class ResetGroupSizesAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.EVEN);
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -924,10 +896,8 @@ export class ToggleGroupSizesAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.TOGGLE);
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -946,13 +916,11 @@ export class MaximizeGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
if (this.editorService.activeEditor) {
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
|
||||
this.layoutService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -967,23 +935,21 @@ export abstract class BaseNavigateEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const result = this.navigate();
|
||||
if (!result) {
|
||||
return Promise.resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const { groupId, editor } = result;
|
||||
if (!editor) {
|
||||
return Promise.resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const group = this.editorGroupService.getGroup(groupId);
|
||||
if (group) {
|
||||
return group.openEditor(editor);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
protected abstract navigate(): IEditorIdentifier | undefined;
|
||||
@@ -1158,10 +1124,8 @@ export class NavigateForwardAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.forward();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1174,10 +1138,8 @@ export class NavigateBackwardsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.back();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1190,10 +1152,8 @@ export class NavigateToLastEditLocationAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.openLastEditLocation();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1206,10 +1166,8 @@ export class NavigateLastAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.last();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1226,10 +1184,8 @@ export class ReopenClosedEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.reopenLastClosedEditor();
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1247,15 +1203,13 @@ export class ClearRecentFilesAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
|
||||
// Clear global recently opened
|
||||
this.workspacesService.clearRecentlyOpened();
|
||||
|
||||
// Clear workspace specific recently opened
|
||||
this.historyService.clearRecentlyOpened();
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1313,12 +1267,10 @@ export class BaseQuickOpenEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const keybindings = this.keybindingService.lookupKeybindings(this.id);
|
||||
|
||||
this.quickOpenService.show(this.prefix, { quickNavigateConfiguration: { keybindings } });
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1396,12 +1348,10 @@ export class QuickOpenPreviousEditorFromHistoryAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const keybindings = this.keybindingService.lookupKeybindings(this.id);
|
||||
|
||||
this.quickOpenService.show(undefined, { quickNavigateConfiguration: { keybindings } });
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1418,10 +1368,8 @@ export class OpenNextRecentlyUsedEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.openNextRecentlyUsedEditor();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1438,10 +1386,8 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.openPreviouslyUsedEditor();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1459,10 +1405,8 @@ export class OpenNextRecentlyUsedEditorInGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.openNextRecentlyUsedEditor(this.editorGroupsService.activeGroup.id);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1480,10 +1424,8 @@ export class OpenPreviousRecentlyUsedEditorInGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.openPreviouslyUsedEditor(this.editorGroupsService.activeGroup.id);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1500,12 +1442,10 @@ export class ClearEditorHistoryAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
|
||||
// Editor history
|
||||
this.historyService.clear();
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1772,10 +1712,8 @@ export class BaseCreateEditorGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.editorGroupService.addGroup(this.editorGroupService.activeGroup, this.direction, { activate: true });
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -317,8 +317,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()));
|
||||
this._register(this.textFileService.untitled.onDidChangeEncoding(r => this.onResourceEncodingChange(r)));
|
||||
this._register(this.textFileService.files.onDidChangeEncoding(m => this.onResourceEncodingChange((m.resource))));
|
||||
this._register(this.textFileService.untitled.onDidChangeEncoding(model => this.onResourceEncodingChange(model.resource)));
|
||||
this._register(this.textFileService.files.onDidChangeEncoding(model => this.onResourceEncodingChange((model.resource))));
|
||||
this._register(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/commo
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
|
||||
/**
|
||||
* The text editor that leverages the diff text editor for the editing experience.
|
||||
@@ -51,8 +50,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IClipboardService private clipboardService: IClipboardService
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService);
|
||||
}
|
||||
@@ -75,10 +73,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
}
|
||||
|
||||
createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor {
|
||||
if (this.reverseColor) { // {{SQL CARBON EDIT}}
|
||||
(configuration as IDiffEditorOptions).reverse = true;
|
||||
}
|
||||
return this.instantiationService.createInstance(DiffEditorWidget as any, parent, configuration, this.clipboardService); // {{SQL CARBON EDIT}} strict-null-check...i guess?
|
||||
if (this.reverseColor) { (configuration as IDiffEditorOptions).reverse = true; } // {{SQL CARBON EDIT}}
|
||||
return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration);
|
||||
}
|
||||
|
||||
async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND,
|
||||
import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom';
|
||||
import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IActionViewItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { prepareActions } from 'vs/workbench/browser/actions';
|
||||
@@ -51,7 +51,6 @@ export interface IPaneColors extends IColorMapping {
|
||||
}
|
||||
|
||||
export interface IViewPaneOptions extends IPaneOptions {
|
||||
actionRunner?: IActionRunner;
|
||||
id: string;
|
||||
title: string;
|
||||
showActionsAlways?: boolean;
|
||||
@@ -169,7 +168,6 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
|
||||
private readonly menuActions: ViewMenuActions;
|
||||
|
||||
protected actionRunner?: IActionRunner;
|
||||
private toolbar?: ToolBar;
|
||||
private readonly showActionsAlways: boolean = false;
|
||||
private headerContainer?: HTMLElement;
|
||||
@@ -196,7 +194,6 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
|
||||
this.id = options.id;
|
||||
this.title = options.title;
|
||||
this.actionRunner = options.actionRunner;
|
||||
this.showActionsAlways = !!options.showActionsAlways;
|
||||
this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService);
|
||||
|
||||
@@ -262,7 +259,6 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
actionViewItemProvider: action => this.getActionViewItem(action),
|
||||
ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title),
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
|
||||
actionRunner: this.actionRunner
|
||||
});
|
||||
|
||||
this._register(this.toolbar);
|
||||
@@ -311,7 +307,9 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
if (this.element) {
|
||||
if (this.shouldShowWelcome()) {
|
||||
this.viewWelcomeContainer.focus();
|
||||
} else if (this.element) {
|
||||
this.element.focus();
|
||||
this._onDidFocus.fire();
|
||||
}
|
||||
@@ -453,8 +451,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
private didLayout = false;
|
||||
private dimension: Dimension | undefined;
|
||||
|
||||
protected actionRunner: IActionRunner | undefined;
|
||||
|
||||
private readonly visibleViewsCountFromCache: number | undefined;
|
||||
private readonly visibleViewsStorageId: string;
|
||||
protected readonly viewsModel: PersistentContributableViewsModel;
|
||||
@@ -800,7 +796,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
{
|
||||
id: viewDescriptor.id,
|
||||
title: viewDescriptor.name,
|
||||
actionRunner: this.getActionRunner(),
|
||||
expanded: !collapsed,
|
||||
minimumBodySize: this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Panel ? 0 : 120
|
||||
});
|
||||
@@ -831,14 +826,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
return panes;
|
||||
}
|
||||
|
||||
getActionRunner(): IActionRunner {
|
||||
if (!this.actionRunner) {
|
||||
this.actionRunner = new ActionRunner();
|
||||
}
|
||||
|
||||
return this.actionRunner;
|
||||
}
|
||||
|
||||
private onDidRemoveViewDescriptors(removed: IViewDescriptorRef[]): void {
|
||||
removed = removed.sort((a, b) => b.index - a.index);
|
||||
const panesToRemove: ViewPane[] = [];
|
||||
|
||||
@@ -118,7 +118,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry
|
||||
try {
|
||||
instantiationService.createInstance(ctor);
|
||||
} catch (error) {
|
||||
console.error(`Unable to instantiate workbench contribution ${(ctor as any).name}.`, error);
|
||||
console.error(`Unable to instantiate workbench contribution ${ctor.name}.`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ export interface IFileInputFactory {
|
||||
|
||||
createFileInput(resource: URI, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput;
|
||||
|
||||
isFileInput(obj: any): obj is IFileEditorInput;
|
||||
isFileInput(obj: unknown): obj is IFileEditorInput;
|
||||
}
|
||||
|
||||
export interface IEditorInputFactoryRegistry {
|
||||
|
||||
@@ -46,8 +46,8 @@ export interface ISerializedEditorGroup {
|
||||
preview?: number;
|
||||
}
|
||||
|
||||
export function isSerializedEditorGroup(obj?: any): obj is ISerializedEditorGroup {
|
||||
const group: ISerializedEditorGroup = obj;
|
||||
export function isSerializedEditorGroup(obj?: unknown): obj is ISerializedEditorGroup {
|
||||
const group = obj as ISerializedEditorGroup;
|
||||
|
||||
return obj && typeof obj === 'object' && Array.isArray(group.editors) && Array.isArray(group.mru);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { basename } from 'vs/base/common/resources';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { WorkspaceFileEdit } from 'vs/editor/common/modes';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
// --- VIEW MODEL
|
||||
|
||||
@@ -420,6 +421,12 @@ export class CategoryElementRenderer implements ITreeRenderer<CategoryElement, F
|
||||
const className = ThemeIcon.asClassName(metadata.iconPath);
|
||||
template.icon.className = className ? `theme-icon ${className}` : '';
|
||||
|
||||
} else if (URI.isUri(metadata.iconPath)) {
|
||||
// background-image
|
||||
template.icon.className = 'uri-icon';
|
||||
template.icon.style.setProperty('--background-dark', `url("${metadata.iconPath.toString(true)}")`);
|
||||
template.icon.style.setProperty('--background-light', `url("${metadata.iconPath.toString(true)}")`);
|
||||
|
||||
} else if (metadata.iconPath) {
|
||||
// background-image
|
||||
template.icon.className = 'uri-icon';
|
||||
|
||||
@@ -7,7 +7,7 @@ import { coalesce } from 'vs/base/common/arrays';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { basename, isEqual } from 'vs/base/common/resources';
|
||||
import { basename, isEqual, extname } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as nls from 'vs/nls';
|
||||
@@ -180,21 +180,62 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
|
||||
}
|
||||
}
|
||||
|
||||
const resourceExt = extname(resource);
|
||||
|
||||
const items = customEditors.allEditors.map((editorDescriptor): IQuickPickItem => ({
|
||||
label: editorDescriptor.displayName,
|
||||
id: editorDescriptor.id,
|
||||
description: editorDescriptor.id === currentlyOpenedEditorType
|
||||
? nls.localize('openWithCurrentlyActive', "Currently Active")
|
||||
: undefined
|
||||
: undefined,
|
||||
buttons: resourceExt ? [{
|
||||
iconClass: 'codicon-settings-gear',
|
||||
tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt)
|
||||
}] : undefined
|
||||
}));
|
||||
const pick = await this.quickInputService.pick(items, {
|
||||
placeHolder: nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", basename(resource)),
|
||||
|
||||
const picker = this.quickInputService.createQuickPick();
|
||||
picker.items = items;
|
||||
picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", basename(resource));
|
||||
|
||||
const pick = await new Promise<string | undefined>(resolve => {
|
||||
picker.onDidAccept(() => {
|
||||
resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0].id : undefined);
|
||||
picker.dispose();
|
||||
});
|
||||
picker.onDidTriggerItemButton(e => {
|
||||
const pick = e.item.id;
|
||||
resolve(pick); // open the view
|
||||
picker.dispose();
|
||||
|
||||
// And persist the setting
|
||||
if (pick) {
|
||||
const newAssociation: CustomEditorAssociation = { viewType: pick, filenamePattern: '*' + resourceExt };
|
||||
const currentAssociations = [...this.configurationService.getValue<CustomEditorsAssociations>(customEditorsAssociationsKey)] || [];
|
||||
|
||||
// First try updating existing association
|
||||
for (let i = 0; i < currentAssociations.length; ++i) {
|
||||
const existing = currentAssociations[i];
|
||||
if (existing.filenamePattern === newAssociation.filenamePattern) {
|
||||
currentAssociations.splice(i, 1, newAssociation);
|
||||
this.configurationService.updateValue(customEditorsAssociationsKey, currentAssociations);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, create a new one
|
||||
currentAssociations.unshift(newAssociation);
|
||||
this.configurationService.updateValue(customEditorsAssociationsKey, currentAssociations);
|
||||
}
|
||||
});
|
||||
picker.show();
|
||||
});
|
||||
|
||||
if (!pick || !pick.id) {
|
||||
if (!pick) {
|
||||
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
return this.openWith(resource, pick.id, options, group);
|
||||
|
||||
return this.openWith(resource, pick, options, group);
|
||||
}
|
||||
|
||||
public openWith(
|
||||
@@ -312,7 +353,11 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
|
||||
|
||||
export const customEditorsAssociationsKey = 'workbench.experimental.editorAssociations';
|
||||
|
||||
export type CustomEditorsAssociations = readonly (CustomEditorSelector & { readonly viewType: string; })[];
|
||||
export type CustomEditorAssociation = CustomEditorSelector & {
|
||||
readonly viewType: string;
|
||||
};
|
||||
|
||||
export type CustomEditorsAssociations = readonly CustomEditorAssociation[];
|
||||
|
||||
export class CustomEditorContribution extends Disposable implements IWorkbenchContribution {
|
||||
constructor(
|
||||
|
||||
@@ -915,14 +915,13 @@ export class DebugSession implements IDebugSession {
|
||||
// Disconnects and clears state. Session can be initialized again for a new connection.
|
||||
private shutdown(): void {
|
||||
dispose(this.rawListeners);
|
||||
if (this.raw) {
|
||||
this.raw.disconnect();
|
||||
this.raw.dispose();
|
||||
this.raw = undefined;
|
||||
}
|
||||
this.fetchThreadsScheduler = undefined;
|
||||
this.model.clearThreads(this.getId(), true);
|
||||
if (this.raw) {
|
||||
const raw = this.raw;
|
||||
this.raw = undefined;
|
||||
raw.disconnect();
|
||||
raw.dispose();
|
||||
}
|
||||
this._onDidChangeState.fire();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,31 +13,6 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.debug-pane .debug-start-view {
|
||||
padding: 0 20px 0 20px;
|
||||
}
|
||||
|
||||
.debug-pane .debug-start-view .monaco-button,
|
||||
.debug-pane .debug-start-view .section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.debug-pane .debug-start-view .top-section {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.debug-pane .debug-start-view .monaco-button {
|
||||
max-width: 260px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.debug-pane .debug-start-view .click {
|
||||
cursor: pointer;
|
||||
color: #007ACC;
|
||||
}
|
||||
|
||||
.monaco-workbench .debug-action.notification:after {
|
||||
content: '';
|
||||
width: 6px;
|
||||
|
||||
@@ -3,64 +3,33 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { StartAction, ConfigureAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IViewDescriptorService, IViewsRegistry, Extensions } from 'vs/workbench/common/views';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
const $ = dom.$;
|
||||
import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { OpenFolderAction, OpenFileAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
interface DebugStartMetrics {
|
||||
debuggers?: string[];
|
||||
}
|
||||
type DebugStartMetricsClassification = {
|
||||
debuggers?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
|
||||
function createClickElement(textContent: string, action: () => any): HTMLSpanElement {
|
||||
const clickElement = $('span.click');
|
||||
clickElement.textContent = textContent;
|
||||
clickElement.onclick = action;
|
||||
clickElement.tabIndex = 0;
|
||||
clickElement.onkeyup = (e) => {
|
||||
const keyboardEvent = new StandardKeyboardEvent(e);
|
||||
if (keyboardEvent.keyCode === KeyCode.Enter || (keyboardEvent.keyCode === KeyCode.Space)) {
|
||||
action();
|
||||
}
|
||||
};
|
||||
|
||||
return clickElement;
|
||||
}
|
||||
const CONTEXT_DEBUGGER_INTERESTED = new RawContextKey<boolean>('debuggerInterested', false);
|
||||
|
||||
export class StartView extends ViewPane {
|
||||
|
||||
static ID = 'workbench.debug.startView';
|
||||
static LABEL = localize('start', "Start");
|
||||
|
||||
private debugButton!: Button;
|
||||
private firstMessageContainer!: HTMLElement;
|
||||
private secondMessageContainer!: HTMLElement;
|
||||
private clickElement: HTMLElement | undefined;
|
||||
private debuggerLabels: string[] | undefined = undefined;
|
||||
private debuggerInterestedContext: IContextKey<boolean>;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@@ -69,125 +38,45 @@ export class StartView extends ViewPane {
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@IFileDialogService private readonly dialogService: IFileDialogService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
) {
|
||||
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService);
|
||||
this._register(editorService.onDidActiveEditorChange(() => this.updateView()));
|
||||
this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(() => this.updateView()));
|
||||
|
||||
this.debuggerInterestedContext = CONTEXT_DEBUGGER_INTERESTED.bindTo(contextKeyService);
|
||||
const setContextKey = () => {
|
||||
const activeEditor = this.editorService.activeTextEditorWidget;
|
||||
const debuggerLabels = this.debugService.getConfigurationManager().getDebuggerLabelsForEditor(activeEditor);
|
||||
this.debuggerInterestedContext.set(debuggerLabels.length > 0);
|
||||
};
|
||||
this._register(editorService.onDidActiveEditorChange(setContextKey));
|
||||
this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(setContextKey));
|
||||
}
|
||||
|
||||
private updateView(): void {
|
||||
const activeEditor = this.editorService.activeTextEditorWidget;
|
||||
const debuggerLabels = this.debugService.getConfigurationManager().getDebuggerLabelsForEditor(activeEditor);
|
||||
if (!equals(this.debuggerLabels, debuggerLabels)) {
|
||||
this.debuggerLabels = debuggerLabels;
|
||||
const enabled = this.debuggerLabels.length > 0;
|
||||
|
||||
this.debugButton.enabled = enabled;
|
||||
const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID);
|
||||
let debugLabel = this.debuggerLabels.length !== 1 ? localize('debug', "Run and Debug") : localize('debugWith', "Run and Debug {0}", this.debuggerLabels[0]);
|
||||
if (debugKeybinding) {
|
||||
debugLabel += ` (${debugKeybinding.getLabel()})`;
|
||||
}
|
||||
this.debugButton.label = debugLabel;
|
||||
|
||||
const emptyWorkbench = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY;
|
||||
this.firstMessageContainer.innerHTML = '';
|
||||
this.secondMessageContainer.innerHTML = '';
|
||||
const secondMessageElement = $('span');
|
||||
this.secondMessageContainer.appendChild(secondMessageElement);
|
||||
|
||||
const setSecondMessage = () => {
|
||||
secondMessageElement.textContent = localize('specifyHowToRun', "To customize Run and Debug");
|
||||
this.clickElement = createClickElement(localize('configure', " create a launch.json file."), () => {
|
||||
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.configure', { debuggers: this.debuggerLabels });
|
||||
this.commandService.executeCommand(ConfigureAction.ID);
|
||||
});
|
||||
this.secondMessageContainer.appendChild(this.clickElement);
|
||||
};
|
||||
const setSecondMessageWithFolder = () => {
|
||||
secondMessageElement.textContent = localize('noLaunchConfiguration', "To customize Run and Debug, ");
|
||||
this.clickElement = createClickElement(localize('openFolder', " open a folder"), () => {
|
||||
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.openFolder', { debuggers: this.debuggerLabels });
|
||||
this.dialogService.pickFolderAndOpen({ forceNewWindow: false });
|
||||
});
|
||||
this.secondMessageContainer.appendChild(this.clickElement);
|
||||
|
||||
const moreText = $('span.moreText');
|
||||
moreText.textContent = localize('andconfigure', " and create a launch.json file.");
|
||||
this.secondMessageContainer.appendChild(moreText);
|
||||
};
|
||||
|
||||
if (enabled && !emptyWorkbench) {
|
||||
setSecondMessage();
|
||||
}
|
||||
|
||||
if (enabled && emptyWorkbench) {
|
||||
setSecondMessageWithFolder();
|
||||
}
|
||||
|
||||
if (!enabled && !emptyWorkbench) {
|
||||
const firstMessageElement = $('span');
|
||||
this.firstMessageContainer.appendChild(firstMessageElement);
|
||||
firstMessageElement.textContent = localize('simplyDebugAndRun', "Open a file which can be debugged or run.");
|
||||
|
||||
setSecondMessage();
|
||||
}
|
||||
|
||||
if (!enabled && emptyWorkbench) {
|
||||
this.clickElement = createClickElement(localize('openFile', "Open a file"), () => {
|
||||
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.openFile');
|
||||
this.dialogService.pickFileAndOpen({ forceNewWindow: false });
|
||||
});
|
||||
this.firstMessageContainer.appendChild(this.clickElement);
|
||||
const firstMessageElement = $('span');
|
||||
this.firstMessageContainer.appendChild(firstMessageElement);
|
||||
firstMessageElement.textContent = localize('canBeDebuggedOrRun', " which can be debugged or run.");
|
||||
|
||||
setSecondMessageWithFolder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
this.firstMessageContainer = $('.top-section');
|
||||
container.appendChild(this.firstMessageContainer);
|
||||
|
||||
this.debugButton = new Button(container);
|
||||
this._register(this.debugButton.onDidClick(() => {
|
||||
this.commandService.executeCommand(StartAction.ID);
|
||||
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.runAndDebug', { debuggers: this.debuggerLabels });
|
||||
}));
|
||||
attachButtonStyler(this.debugButton, this.themeService);
|
||||
|
||||
dom.addClass(this.element, 'debug-pane');
|
||||
dom.addClass(container, 'debug-start-view');
|
||||
|
||||
this.secondMessageContainer = $('.section');
|
||||
container.appendChild(this.secondMessageContainer);
|
||||
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
protected layoutBody(_: number, __: number): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
if (this.debugButton.enabled) {
|
||||
this.debugButton.focus();
|
||||
} else if (this.clickElement) {
|
||||
this.clickElement.focus();
|
||||
}
|
||||
shouldShowWelcome(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID),
|
||||
when: CONTEXT_DEBUGGER_INTERESTED.toNegated()
|
||||
});
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('runAndDebugAction', "[Run and Debug](command:{0})", StartAction.ID)
|
||||
});
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID),
|
||||
when: WorkbenchStateContext.notEqualsTo('empty')
|
||||
});
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID),
|
||||
when: WorkbenchStateContext.isEqualTo('empty')
|
||||
});
|
||||
|
||||
@@ -1773,6 +1773,16 @@ declare module DebugProtocol {
|
||||
If missing the value 0 is assumed which results in the completion text being inserted.
|
||||
*/
|
||||
length?: number;
|
||||
/** Determines the start of the new selection after the text has been inserted (or replaced).
|
||||
The start position must in the range 0 and length of the completion text.
|
||||
If omitted the selection starts at the end of the completion text.
|
||||
*/
|
||||
selectionStart?: number;
|
||||
/** Determines the length of the new selection after the text has been inserted (or replaced).
|
||||
The selection can not extend beyond the bounds of the completion text.
|
||||
If omitted the length is assumed to be 0.
|
||||
*/
|
||||
selectionLength?: number;
|
||||
}
|
||||
|
||||
/** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. */
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
|
||||
import {
|
||||
OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction,
|
||||
@@ -48,6 +48,8 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
|
||||
// Singletons
|
||||
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
|
||||
@@ -443,6 +445,33 @@ registerAction2(class extends Action2 {
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: TOGGLE_IGNORE_EXTENSION_ACTION_ID,
|
||||
title: { value: localize('workbench.extensions.action.toggleIgnoreExtension', "Don't Sync This Extension"), original: `Don't Sync This Extension` },
|
||||
menu: {
|
||||
id: MenuId.ExtensionContext,
|
||||
group: '2_configure',
|
||||
when: CONTEXT_SYNC_ENABLEMENT
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, id: string) {
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const ignoredExtensions = [...configurationService.getValue<string[]>('sync.ignoredExtensions')];
|
||||
const index = ignoredExtensions.findIndex(ignoredExtension => areSameExtensions({ id: ignoredExtension }, { id }));
|
||||
if (index !== -1) {
|
||||
ignoredExtensions.splice(index, 1);
|
||||
} else {
|
||||
ignoredExtensions.push(id);
|
||||
}
|
||||
return configurationService.updateValue('sync.ignoredExtensions', ignoredExtensions.length ? ignoredExtensions : undefined, ConfigurationTarget.USER);
|
||||
}
|
||||
});
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
|
||||
class ExtensionsContributions implements IWorkbenchContribution {
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as json from 'vs/base/common/json';
|
||||
import { ActionViewItem, Separator, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
|
||||
import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
@@ -679,7 +679,7 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, extension: IExtension | undefined | null): ExtensionAction[][] {
|
||||
export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, instantiationService: IInstantiationService, extension: IExtension | undefined | null): ExtensionAction[][] {
|
||||
const scopedContextKeyService = contextKeyService.createScoped();
|
||||
if (extension) {
|
||||
scopedContextKeyService.createKey<boolean>('isBuiltinExtension', extension.type === ExtensionType.System);
|
||||
@@ -691,7 +691,7 @@ export function getContextMenuActions(menuService: IMenuService, contextKeyServi
|
||||
|
||||
const groups: ExtensionAction[][] = [];
|
||||
const menu = menuService.createMenu(MenuId.ExtensionContext, scopedContextKeyService);
|
||||
menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => new MenuItemExtensionAction(action))));
|
||||
menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => instantiationService.createInstance(MenuItemExtensionAction, action))));
|
||||
menu.dispose();
|
||||
|
||||
return groups;
|
||||
@@ -745,7 +745,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction {
|
||||
groups.push([this.instantiationService.createInstance(UninstallAction)]);
|
||||
groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]);
|
||||
|
||||
getContextMenuActions(this.menuService, this.contextKeyService, this.extension).forEach(actions => groups.push(actions));
|
||||
getContextMenuActions(this.menuService, this.contextKeyService, this.instantiationService, this.extension).forEach(actions => groups.push(actions));
|
||||
|
||||
groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension));
|
||||
|
||||
@@ -773,11 +773,21 @@ export class ManageExtensionAction extends ExtensionDropDownAction {
|
||||
|
||||
export class MenuItemExtensionAction extends ExtensionAction {
|
||||
|
||||
constructor(private readonly action: IAction) {
|
||||
constructor(
|
||||
private readonly action: IAction,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super(action.id, action.label);
|
||||
}
|
||||
|
||||
update() { }
|
||||
update() {
|
||||
if (!this.extension) {
|
||||
return;
|
||||
}
|
||||
if (this.action.id === TOGGLE_IGNORE_EXTENSION_ACTION_ID) {
|
||||
this.checked = this.configurationService.getValue<string[]>('sync.ignoredExtensions').some(id => areSameExtensions({ id }, this.extension!.identifier));
|
||||
}
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (this.extension) {
|
||||
|
||||
@@ -247,7 +247,7 @@ export class ExtensionsListView extends ViewPane {
|
||||
getActions: () => actions.slice(0, actions.length - 1)
|
||||
});
|
||||
} else if (e.element) {
|
||||
const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), e.element);
|
||||
const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), this.instantiationService, e.element);
|
||||
groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = e.element!));
|
||||
let actions: IAction[] = [];
|
||||
for (const menuActions of groups) {
|
||||
|
||||
@@ -143,3 +143,5 @@ export class ExtensionContainers extends Disposable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_IGNORE_EXTENSION_ACTION_ID = 'workbench.extensions.action.toggleIgnoreExtension';
|
||||
|
||||
@@ -13,13 +13,15 @@ export class ExtensionsInput extends EditorInput {
|
||||
static readonly ID = 'workbench.extensions.input2';
|
||||
get extension(): IExtension { return this._extension; }
|
||||
|
||||
readonly resource = URI.from({
|
||||
scheme: 'extension',
|
||||
path: this.extension.identifier.id
|
||||
});
|
||||
get resource() {
|
||||
return URI.from({
|
||||
scheme: 'extension',
|
||||
path: this.extension.identifier.id
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _extension: IExtension,
|
||||
private readonly _extension: IExtension
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -16,10 +16,6 @@ export class RuntimeExtensionsInput extends EditorInput {
|
||||
path: 'default'
|
||||
});
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
return RuntimeExtensionsInput.ID;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEditorViewState } from 'vs/editor/common/editorCommon';
|
||||
import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor';
|
||||
import { ITextFileService, ModelState } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, TextFileEditorModelState } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
@@ -62,9 +62,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
|
||||
this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e)));
|
||||
|
||||
// Ensure dirty text file and untitled models are always opened as editors
|
||||
this._register(this.textFileService.files.onDidChangeDirty(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource)));
|
||||
this._register(this.textFileService.files.onDidSaveError(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource)));
|
||||
this._register(this.textFileService.untitled.onDidChangeDirty(r => this.ensureDirtyFilesAreOpenedWorker.work(r)));
|
||||
this._register(this.textFileService.files.onDidChangeDirty(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource)));
|
||||
this._register(this.textFileService.files.onDidSaveError(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource)));
|
||||
this._register(this.textFileService.untitled.onDidChangeDirty(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource)));
|
||||
|
||||
// Out of workspace file watchers
|
||||
this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange()));
|
||||
@@ -290,7 +290,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
|
||||
}
|
||||
|
||||
const model = this.textFileService.files.get(resource);
|
||||
if (model?.hasState(ModelState.PENDING_SAVE)) {
|
||||
if (model?.hasState(TextFileEditorModelState.PENDING_SAVE)) {
|
||||
return false; // resource must not be pending to save
|
||||
}
|
||||
|
||||
@@ -369,18 +369,14 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
|
||||
}
|
||||
|
||||
const model = this.textFileService.files.get(resource);
|
||||
if (!model) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (model.isDirty()) {
|
||||
if (!model || model.isDirty() || !model.isResolved()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return model;
|
||||
})),
|
||||
model => model.resource.toString()
|
||||
).forEach(model => model.load());
|
||||
).forEach(model => this.textFileService.files.resolve(model.resource, { reload: { async: true } }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { isValidBasename } from 'vs/base/common/extpath';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files';
|
||||
import { ITextFileEditorModel, ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
|
||||
import { EditorOptions, TextEditorOptions, IEditorCloseEvent } from 'vs/workbench/common/editor';
|
||||
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
|
||||
@@ -135,7 +135,7 @@ export class TextFileEditor extends BaseTextEditor {
|
||||
return this.openAsBinary(input, options);
|
||||
}
|
||||
|
||||
const textFileModel = <ITextFileEditorModel>resolvedModel;
|
||||
const textFileModel = resolvedModel;
|
||||
|
||||
// Editor
|
||||
const textEditor = assertIsDefined(this.getControl());
|
||||
|
||||
@@ -221,13 +221,11 @@ class DoNotShowResolveConflictLearnMoreAction extends Action {
|
||||
super('workbench.files.action.resolveConflictLearnMoreDoNotShowAgain', nls.localize('dontShowAgain', "Don't Show Again"));
|
||||
}
|
||||
|
||||
run(notification: IDisposable): Promise<any> {
|
||||
async run(notification: IDisposable): Promise<any> {
|
||||
this.storageService.store(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, true, StorageScope.GLOBAL);
|
||||
|
||||
// Hide notification
|
||||
notification.dispose();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,8 +260,6 @@ class ResolveSaveConflictAction extends Action {
|
||||
Event.once(handle.onDidClose)(() => dispose(actions.primary!));
|
||||
pendingResolveSaveConflictMessages.push(handle);
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +272,7 @@ class SaveElevatedAction extends Action {
|
||||
super('workbench.files.action.saveElevated', triedToMakeWriteable ? isWindows ? nls.localize('overwriteElevated', "Overwrite as Admin...") : nls.localize('overwriteElevatedSudo', "Overwrite as Sudo...") : isWindows ? nls.localize('saveElevated', "Retry as Admin...") : nls.localize('saveElevatedSudo', "Retry as Sudo..."));
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
if (!this.model.isDisposed()) {
|
||||
this.model.save({
|
||||
writeElevated: true,
|
||||
@@ -284,8 +280,6 @@ class SaveElevatedAction extends Action {
|
||||
reason: SaveReason.EXPLICIT
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,12 +291,10 @@ class OverwriteReadonlyAction extends Action {
|
||||
super('workbench.files.action.overwrite', nls.localize('overwrite', "Overwrite"));
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
if (!this.model.isDisposed()) {
|
||||
this.model.save({ overwriteReadonly: true, reason: SaveReason.EXPLICIT });
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,12 +306,10 @@ class SaveIgnoreModifiedSinceAction extends Action {
|
||||
super('workbench.files.action.saveIgnoreModifiedSince', nls.localize('overwrite', "Overwrite"));
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
if (!this.model.isDisposed()) {
|
||||
this.model.save({ ignoreModifiedSince: true, reason: SaveReason.EXPLICIT });
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,10 +321,8 @@ class ConfigureSaveConflictAction extends Action {
|
||||
super('workbench.files.action.configureSaveConflict', nls.localize('configure', "Configure"));
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.preferencesService.openSettings(undefined, 'files.saveConflictResolution');
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -34,6 +34,9 @@ import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { WorkbenchStateContext, RemoteNameContext, IsWebContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { AddRootFolderAction, OpenFolderAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
@@ -61,6 +64,23 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor
|
||||
|
||||
private registerViews(): void {
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
|
||||
content: localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID),
|
||||
when: WorkbenchStateContext.isEqualTo('workspace')
|
||||
});
|
||||
|
||||
const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID;
|
||||
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
|
||||
content: localize('remoteNoFolderHelp', "Connected to remote.\n[Open Folder](command:{0})", commandId),
|
||||
when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated())
|
||||
});
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
|
||||
content: localize('noFolderHelp', "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId),
|
||||
when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext))
|
||||
});
|
||||
|
||||
const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER);
|
||||
|
||||
let viewDescriptorsToRegister: IViewDescriptor[] = [];
|
||||
|
||||
@@ -166,7 +166,7 @@ export class GlobalNewUntitledFileAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
|
||||
async function deleteFiles(workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
|
||||
let primaryButton: string;
|
||||
if (useTrash) {
|
||||
primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash");
|
||||
@@ -174,20 +174,24 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyF
|
||||
primaryButton = nls.localize({ key: 'deleteButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete");
|
||||
}
|
||||
|
||||
const distinctElements = resources.distinctParents(elements, e => e.resource);
|
||||
|
||||
// Handle dirty
|
||||
const distinctElements = resources.distinctParents(elements, e => e.resource);
|
||||
const dirtyWorkingCopies = new Set<IWorkingCopy>();
|
||||
for (const distinctElement of distinctElements) {
|
||||
for (const dirtyWorkingCopy of workingCopyFileService.getDirty(distinctElement.resource)) {
|
||||
dirtyWorkingCopies.add(dirtyWorkingCopy);
|
||||
}
|
||||
}
|
||||
let confirmed = true;
|
||||
const dirtyWorkingCopies = workingCopyService.dirtyWorkingCopies.filter(workingCopy => distinctElements.some(e => resources.isEqualOrParent(workingCopy.resource, e.resource)));
|
||||
if (dirtyWorkingCopies.length) {
|
||||
if (dirtyWorkingCopies.size) {
|
||||
let message: string;
|
||||
if (distinctElements.length > 1) {
|
||||
message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?");
|
||||
} else if (distinctElements[0].isDirectory) {
|
||||
if (dirtyWorkingCopies.length === 1) {
|
||||
if (dirtyWorkingCopies.size === 1) {
|
||||
message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder {0} with unsaved changes in 1 file. Do you want to continue?", distinctElements[0].name);
|
||||
} else {
|
||||
message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder {0} with unsaved changes in {1} files. Do you want to continue?", distinctElements[0].name, dirtyWorkingCopies.length);
|
||||
message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder {0} with unsaved changes in {1} files. Do you want to continue?", distinctElements[0].name, dirtyWorkingCopies.size);
|
||||
}
|
||||
} else {
|
||||
message = nls.localize('dirtyMessageFileDelete', "You are deleting {0} with unsaved changes. Do you want to continue?", distinctElements[0].name);
|
||||
@@ -204,7 +208,6 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyF
|
||||
confirmed = false;
|
||||
} else {
|
||||
skipConfirm = true;
|
||||
await Promise.all(dirtyWorkingCopies.map(dirty => dirty.revert()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +299,7 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyF
|
||||
|
||||
skipConfirm = true;
|
||||
|
||||
return deleteFiles(workingCopyService, workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm);
|
||||
return deleteFiles(workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -989,7 +992,7 @@ export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => {
|
||||
const explorerService = accessor.get(IExplorerService);
|
||||
const stats = explorerService.getContext(true).filter(s => !s.isRoot);
|
||||
if (stats.length) {
|
||||
await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
|
||||
await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -998,7 +1001,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => {
|
||||
const stats = explorerService.getContext(true).filter(s => !s.isRoot);
|
||||
|
||||
if (stats.length) {
|
||||
await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
|
||||
await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -66,17 +66,6 @@
|
||||
border-radius: 0; /* goes better when ellipsis shows up on narrow sidebar */
|
||||
}
|
||||
|
||||
.explorer-viewlet .explorer-empty-view {
|
||||
padding: 0 20px 0 20px;
|
||||
}
|
||||
|
||||
.explorer-viewlet .explorer-empty-view .monaco-button {
|
||||
max-width: 260px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.explorer-viewlet .explorer-item.nonexistent-root {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -4,13 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { OpenFolderAction, AddRootFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -20,10 +15,7 @@ import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/vie
|
||||
import { ResourcesDropHandler, DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { listDropBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
@@ -33,9 +25,6 @@ export class EmptyView extends ViewPane {
|
||||
static readonly ID: string = 'workbench.explorer.emptyView';
|
||||
static readonly NAME = nls.localize('noWorkspace', "No Folder Opened");
|
||||
|
||||
private button!: Button;
|
||||
private messageElement!: HTMLElement;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@@ -45,55 +34,31 @@ export class EmptyView extends ViewPane {
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,
|
||||
@ILabelService private labelService: ILabelService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IOpenerService openerService: IOpenerService
|
||||
) {
|
||||
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: No Folder Opened") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService);
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(() => this.setLabels()));
|
||||
this._register(this.labelService.onDidChangeFormatters(() => this.setLabels()));
|
||||
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(() => this.refreshTitle()));
|
||||
this._register(this.labelService.onDidChangeFormatters(() => this.refreshTitle()));
|
||||
}
|
||||
|
||||
shouldShowWelcome(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
DOM.addClass(container, 'explorer-empty-view');
|
||||
container.tabIndex = 0;
|
||||
|
||||
const messageContainer = document.createElement('div');
|
||||
DOM.addClass(messageContainer, 'section');
|
||||
container.appendChild(messageContainer);
|
||||
|
||||
this.messageElement = document.createElement('p');
|
||||
messageContainer.appendChild(this.messageElement);
|
||||
|
||||
this.button = new Button(messageContainer);
|
||||
attachButtonStyler(this.button, this.themeService);
|
||||
|
||||
this._register(this.button.onDidClick(() => {
|
||||
if (!this.actionRunner) {
|
||||
return;
|
||||
}
|
||||
const action = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE
|
||||
? this.instantiationService.createInstance(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL)
|
||||
: this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL);
|
||||
this.actionRunner.run(action).then(() => {
|
||||
action.dispose();
|
||||
}, err => {
|
||||
action.dispose();
|
||||
errors.onUnexpectedError(err);
|
||||
});
|
||||
}));
|
||||
|
||||
this._register(new DragAndDropObserver(container, {
|
||||
onDrop: e => {
|
||||
const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND);
|
||||
container.style.backgroundColor = color ? color.toString() : '';
|
||||
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true });
|
||||
dropHandler.handleDrop(e, () => undefined, targetGroup => undefined);
|
||||
dropHandler.handleDrop(e, () => undefined, () => undefined);
|
||||
},
|
||||
onDragEnter: (e) => {
|
||||
onDragEnter: () => {
|
||||
const color = this.themeService.getTheme().getColor(listDropBackground);
|
||||
container.style.backgroundColor = color ? color.toString() : '';
|
||||
},
|
||||
@@ -112,26 +77,13 @@ export class EmptyView extends ViewPane {
|
||||
}
|
||||
}));
|
||||
|
||||
this.setLabels();
|
||||
this.refreshTitle();
|
||||
}
|
||||
|
||||
private setLabels(): void {
|
||||
private refreshTitle(): void {
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
this.messageElement.textContent = nls.localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.");
|
||||
if (this.button) {
|
||||
this.button.label = nls.localize('addFolder', "Add Folder");
|
||||
}
|
||||
this.updateTitle(EmptyView.NAME);
|
||||
} else {
|
||||
if (this.environmentService.configuration.remoteAuthority && !isWeb) {
|
||||
const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.configuration.remoteAuthority);
|
||||
this.messageElement.textContent = hostLabel ? nls.localize('remoteNoFolderHelp', "Connected to {0}", hostLabel) : nls.localize('connecting', "Connecting...");
|
||||
} else {
|
||||
this.messageElement.textContent = nls.localize('noFolderHelp', "You have not yet opened a folder.");
|
||||
}
|
||||
if (this.button) {
|
||||
this.button.label = nls.localize('openFolder', "Open Folder");
|
||||
}
|
||||
this.updateTitle(this.title);
|
||||
}
|
||||
}
|
||||
@@ -139,9 +91,4 @@ export class EmptyView extends ViewPane {
|
||||
layoutBody(_size: number): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.button.element.focus();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { isNumber } from 'vs/base/common/types';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IEditableData } from 'vs/workbench/common/views';
|
||||
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
|
||||
export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> {
|
||||
|
||||
@@ -643,8 +642,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IWorkingCopyFileService private workingCopyFileService: IWorkingCopyFileService,
|
||||
@IHostService private hostService: IHostService,
|
||||
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
|
||||
@IWorkingCopyService private workingCopyService: IWorkingCopyService
|
||||
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService
|
||||
) {
|
||||
this.toDispose = [];
|
||||
|
||||
@@ -945,15 +943,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||
const sourceFile = resource;
|
||||
const targetFile = joinPath(target.resource, basename(sourceFile));
|
||||
|
||||
// if the target exists and is dirty, make sure to revert it. otherwise the dirty contents
|
||||
// of the target file would replace the contents of the added file. since we already
|
||||
// confirmed the overwrite before, this is OK.
|
||||
if (this.workingCopyService.isDirty(targetFile)) {
|
||||
await Promise.all(this.workingCopyService.getWorkingCopies(targetFile).map(workingCopy => workingCopy.revert({ soft: true })));
|
||||
}
|
||||
|
||||
const copyTarget = joinPath(target.resource, basename(sourceFile));
|
||||
const stat = await this.workingCopyFileService.copy(sourceFile, copyTarget, true);
|
||||
const stat = await this.workingCopyFileService.copy(sourceFile, targetFile, true);
|
||||
// if we only add one file, just open it directly
|
||||
if (resources.length === 1 && !stat.isDirectory) {
|
||||
this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
|
||||
|
||||
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { EncodingMode, IFileEditorInput, Verbosity, TextResourceEditorInput } from 'vs/workbench/common/editor';
|
||||
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
|
||||
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
|
||||
import { ITextFileService, ModelState, LoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, TextFileEditorModelState, TextFileLoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IReference, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
@@ -56,6 +56,8 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
|
||||
) {
|
||||
super(resource, editorService, editorGroupService, textFileService, labelService, fileService, filesConfigurationService);
|
||||
|
||||
this.model = this.textFileService.files.get(resource);
|
||||
|
||||
if (preferredEncoding) {
|
||||
this.setPreferredEncoding(preferredEncoding);
|
||||
}
|
||||
@@ -63,6 +65,11 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
|
||||
if (preferredMode) {
|
||||
this.setPreferredMode(preferredMode);
|
||||
}
|
||||
|
||||
// If a file model already exists, make sure to wire it in
|
||||
if (this.model) {
|
||||
this.registerModelListeners(this.model);
|
||||
}
|
||||
}
|
||||
|
||||
protected registerListeners(): void {
|
||||
@@ -98,10 +105,10 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
|
||||
this.modelListeners.add(model.onDidSaveError(() => this._onDidChangeDirty.fire()));
|
||||
|
||||
// remove model association once it gets disposed
|
||||
Event.once(model.onDispose)(() => {
|
||||
this.modelListeners.add(Event.once(model.onDispose)(() => {
|
||||
this.modelListeners.clear();
|
||||
this.model = undefined;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
getEncoding(): string | undefined {
|
||||
@@ -167,7 +174,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
|
||||
}
|
||||
|
||||
private decorateLabel(label: string): string {
|
||||
const orphaned = this.model?.hasState(ModelState.ORPHAN);
|
||||
const orphaned = this.model?.hasState(TextFileEditorModelState.ORPHAN);
|
||||
const readonly = this.isReadonly();
|
||||
|
||||
if (orphaned && readonly) {
|
||||
@@ -198,7 +205,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
|
||||
}
|
||||
|
||||
isSaving(): boolean {
|
||||
if (this.model?.hasState(ModelState.SAVED) || this.model?.hasState(ModelState.CONFLICT) || this.model?.hasState(ModelState.ERROR)) {
|
||||
if (this.model?.hasState(TextFileEditorModelState.SAVED) || this.model?.hasState(TextFileEditorModelState.CONFLICT) || this.model?.hasState(TextFileEditorModelState.ERROR)) {
|
||||
return false; // require the model to be dirty and not in conflict or error state
|
||||
}
|
||||
|
||||
@@ -234,7 +241,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
|
||||
encoding: this.preferredEncoding,
|
||||
reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model
|
||||
allowBinary: this.forceOpenAs === ForceOpenAs.Text,
|
||||
reason: LoadReason.EDITOR
|
||||
reason: TextFileLoadReason.EDITOR
|
||||
});
|
||||
|
||||
// This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary
|
||||
|
||||
@@ -28,10 +28,8 @@ export class LogViewerInput extends ResourceEditorInput {
|
||||
|
||||
static readonly ID = 'workbench.editorinputs.output';
|
||||
|
||||
readonly resource = this.outputChannelDescriptor.file;
|
||||
|
||||
constructor(
|
||||
private readonly outputChannelDescriptor: IFileOutputChannelDescriptor,
|
||||
outputChannelDescriptor: IFileOutputChannelDescriptor,
|
||||
@ITextModelService textModelResolverService: ITextModelService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
}
|
||||
|
||||
/* Deal with overflow */
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-value,
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-sibling {
|
||||
white-space: nowrap;
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-value,
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-sibling {
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-value {
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-value {
|
||||
max-width: 90%;
|
||||
}
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-sibling {
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-sibling {
|
||||
max-width: 10%;
|
||||
}
|
||||
|
||||
|
||||
@@ -1640,12 +1640,13 @@ class StopSyncingSettingAction extends Action {
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const currentValue = this.configService.getValue<string[]>('sync.ignoredSettings');
|
||||
let currentValue = [...this.configService.getValue<string[]>('sync.ignoredSettings')];
|
||||
if (this.checked) {
|
||||
this.configService.updateValue('sync.ignoredSettings', currentValue.filter(v => v !== this.setting.key));
|
||||
currentValue = currentValue.filter(v => v !== this.setting.key);
|
||||
} else {
|
||||
this.configService.updateValue('sync.ignoredSettings', [...currentValue, this.setting.key]);
|
||||
currentValue.push(this.setting.key);
|
||||
}
|
||||
this.configService.updateValue('sync.ignoredSettings', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER);
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
@@ -443,12 +443,12 @@ export class ListSettingWidget extends Disposable {
|
||||
|
||||
const onSubmit = (edited: boolean) => {
|
||||
this.model.setEditKey('none');
|
||||
const value = valueInput.value.trim();
|
||||
const value = valueInput.value;
|
||||
if (edited && !isUndefinedOrNull(value)) {
|
||||
this._onDidChangeList.fire({
|
||||
originalValue: item.value,
|
||||
value: value,
|
||||
sibling: siblingInput && siblingInput.value.trim(),
|
||||
sibling: siblingInput && siblingInput.value,
|
||||
targetIndex: idx
|
||||
});
|
||||
}
|
||||
|
||||
@@ -146,13 +146,16 @@ interface ResourceTemplate {
|
||||
disposables: IDisposable;
|
||||
}
|
||||
|
||||
class MultipleSelectionActionRunner extends ActionRunner {
|
||||
class RepositoryPaneActionRunner extends ActionRunner {
|
||||
|
||||
constructor(private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[]) {
|
||||
constructor(
|
||||
private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[],
|
||||
private focus: () => void
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
runAction(action: IAction, context: ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>): Promise<any> {
|
||||
async runAction(action: IAction, context: ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>): Promise<any> {
|
||||
if (!(action instanceof MenuItemAction)) {
|
||||
return super.runAction(action, context);
|
||||
}
|
||||
@@ -161,7 +164,8 @@ class MultipleSelectionActionRunner extends ActionRunner {
|
||||
const contextIsSelected = selection.some(s => s === context);
|
||||
const actualContext = contextIsSelected ? selection : [context];
|
||||
const args = flatten(actualContext.map(e => ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e]));
|
||||
return action.run(...args);
|
||||
await action.run(...args);
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +179,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
|
||||
private labels: ResourceLabels,
|
||||
private actionViewItemProvider: IActionViewItemProvider,
|
||||
private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[],
|
||||
private focus: () => void,
|
||||
private themeService: IThemeService,
|
||||
private menus: SCMMenus
|
||||
) { }
|
||||
@@ -186,7 +191,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
|
||||
const actionsContainer = append(fileLabel.element, $('.actions'));
|
||||
const actionBar = new ActionBar(actionsContainer, {
|
||||
actionViewItemProvider: this.actionViewItemProvider,
|
||||
actionRunner: new MultipleSelectionActionRunner(this.getSelectedResources)
|
||||
actionRunner: new RepositoryPaneActionRunner(this.getSelectedResources, this.focus)
|
||||
});
|
||||
|
||||
const decorationIcon = append(element, $('.decoration-icon'));
|
||||
@@ -730,7 +735,8 @@ export class RepositoryPane extends ViewPane {
|
||||
wrappingStrategy: 'advanced',
|
||||
wrappingIndent: 'none',
|
||||
padding: { top: 3, bottom: 3 },
|
||||
suggest: { showWords: false }
|
||||
suggest: { showWords: false },
|
||||
quickSuggestions: false
|
||||
};
|
||||
|
||||
const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
|
||||
@@ -820,7 +826,7 @@ export class RepositoryPane extends ViewPane {
|
||||
|
||||
const renderers = [
|
||||
new ResourceGroupRenderer(actionViewItemProvider, this.themeService, this.menus),
|
||||
new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), this.themeService, this.menus)
|
||||
new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), () => this.tree.domFocus(), this.themeService, this.menus)
|
||||
];
|
||||
|
||||
const filter = new SCMTreeFilter();
|
||||
@@ -1024,7 +1030,7 @@ export class RepositoryPane extends ViewPane {
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => actions,
|
||||
getActionsContext: () => element,
|
||||
actionRunner: new MultipleSelectionActionRunner(() => this.getSelectedResources())
|
||||
actionRunner: new RepositoryPaneActionRunner(() => this.getSelectedResources(), () => this.tree.domFocus())
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -104,14 +104,7 @@ export class ReplaceService implements IReplaceService {
|
||||
const edits: WorkspaceTextEdit[] = this.createEdits(arg, resource);
|
||||
await this.bulkEditorService.apply({ edits }, { progress });
|
||||
|
||||
return Promise.all(edits.map(e => {
|
||||
const model = this.textFileService.files.get(e.resource);
|
||||
if (model) {
|
||||
return model.save();
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}));
|
||||
return Promise.all(edits.map(e => this.textFileService.files.get(e.resource)?.save()));
|
||||
}
|
||||
|
||||
async openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<any> {
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as aria from 'vs/base/browser/ui/aria/aria';
|
||||
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IAction, ActionRunner } from 'vs/base/common/actions';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
@@ -213,7 +213,7 @@ export class SearchView extends ViewPane {
|
||||
this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE);
|
||||
|
||||
this._register(this.fileService.onDidFilesChange(e => this.onFilesChanged(e)));
|
||||
this._register(this.textFileService.untitled.onDidDisposeModel(e => this.onUntitledDidDispose(e)));
|
||||
this._register(this.textFileService.untitled.onDidDispose(model => this.onUntitledDidDispose(model.resource)));
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(() => this.onDidChangeWorkbenchState()));
|
||||
this._register(this.searchHistoryService.onDidClearHistory(() => this.clearHistory()));
|
||||
|
||||
@@ -1584,6 +1584,7 @@ export class SearchView extends ViewPane {
|
||||
const openFolderLink = dom.append(textEl,
|
||||
$('a.pointer.prominent', { tabindex: 0 }, nls.localize('openFolder', "Open Folder")));
|
||||
|
||||
const actionRunner = new ActionRunner();
|
||||
this.messageDisposables.push(dom.addDisposableListener(openFolderLink, dom.EventType.CLICK, (e: MouseEvent) => {
|
||||
dom.EventHelper.stop(e, false);
|
||||
|
||||
@@ -1591,7 +1592,7 @@ export class SearchView extends ViewPane {
|
||||
this.instantiationService.createInstance(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL) :
|
||||
this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL);
|
||||
|
||||
this.actionRunner!.run(action).then(() => {
|
||||
actionRunner.run(action).then(() => {
|
||||
action.dispose();
|
||||
}, err => {
|
||||
action.dispose();
|
||||
|
||||
@@ -53,8 +53,7 @@ class LanguageSurvey extends Disposable {
|
||||
// Process model-save event every 250ms to reduce load
|
||||
const onModelsSavedWorker = this._register(new RunOnceWorker<ITextFileEditorModel>(models => {
|
||||
models.forEach(m => {
|
||||
const model = modelService.getModel(m.resource);
|
||||
if (model && model.getModeId() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) {
|
||||
if (m.getMode() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) {
|
||||
const editedCount = storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) + 1;
|
||||
storageService.store(EDITED_LANGUAGE_COUNT_KEY, editedCount, StorageScope.GLOBAL);
|
||||
storageService.store(EDITED_LANGUAGE_DATE_KEY, date, StorageScope.GLOBAL);
|
||||
|
||||
@@ -19,7 +19,7 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry';
|
||||
import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { ITextFileService, ITextFileModelSaveEvent, ITextFileModelLoadEvent } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, ITextFileSaveEvent, ITextFileLoadEvent } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { extname, basename, isEqual, isEqualOrParent, joinPath } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
@@ -58,7 +58,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@ITextFileService textFileService: ITextFileService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -131,7 +131,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
|
||||
this._register(lifecycleService.onShutdown(() => this.dispose()));
|
||||
}
|
||||
|
||||
private onTextFileModelLoaded(e: ITextFileModelLoadEvent): void {
|
||||
private onTextFileModelLoaded(e: ITextFileLoadEvent): void {
|
||||
const settingsType = this.getTypeIfSettings(e.model.resource);
|
||||
if (settingsType) {
|
||||
type SettingsReadClassification = {
|
||||
@@ -146,7 +146,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
|
||||
}
|
||||
}
|
||||
|
||||
private onTextFileModelSaved(e: ITextFileModelSaveEvent): void {
|
||||
private onTextFileModelSaved(e: ITextFileSaveEvent): void {
|
||||
const settingsType = this.getTypeIfSettings(e.model.resource);
|
||||
if (settingsType) {
|
||||
type SettingsWrittenClassification = {
|
||||
|
||||
@@ -690,6 +690,8 @@ export class RunActiveFileInTerminalAction extends Action {
|
||||
if (!instance) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
await instance.processReady;
|
||||
|
||||
const editor = this.codeEditorService.getActiveCodeEditor();
|
||||
if (!editor || !editor.hasModel()) {
|
||||
return Promise.resolve(undefined);
|
||||
|
||||
@@ -21,7 +21,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IMenuItem, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey, ContextKeyRegexExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
@@ -31,7 +31,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { CONTEXT_SYNC_STATE, getSyncSourceFromRemoteContentResource, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { CONTEXT_SYNC_STATE, getSyncSourceFromRemoteContentResource, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
|
||||
import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
@@ -52,7 +52,6 @@ const enum AuthStatus {
|
||||
SignedOut = 'SignedOut',
|
||||
Unavailable = 'Unavailable'
|
||||
}
|
||||
const CONTEXT_SYNC_ENABLEMENT = new RawContextKey<boolean>('syncEnabled', false);
|
||||
const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey<string>('authTokenStatus', AuthStatus.Initializing);
|
||||
const CONTEXT_CONFLICTS_SOURCES = new RawContextKey<string>('conflictsSources', '');
|
||||
|
||||
@@ -547,7 +546,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
} else {
|
||||
await this.userDataSyncService.resetLocal();
|
||||
}
|
||||
await this.signOut();
|
||||
this.disableSync();
|
||||
}
|
||||
}
|
||||
@@ -574,13 +572,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
}
|
||||
|
||||
private async signOut(): Promise<void> {
|
||||
if (this.activeAccount) {
|
||||
await this.authenticationService.logout(this.userDataSyncStore!.authenticationProviderId, this.activeAccount.id);
|
||||
await this.setActiveAccount(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private getConflictsEditorInput(source: SyncSource): IEditorInput | undefined {
|
||||
const previewResource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource
|
||||
: source === SyncSource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource
|
||||
@@ -652,6 +643,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
},
|
||||
when: turnOnSyncWhenContext,
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
|
||||
group: '5_sync',
|
||||
command: {
|
||||
id: turnOnSyncCommandId,
|
||||
title: localize('global activity turn on sync', "Turn on Sync...")
|
||||
},
|
||||
when: turnOnSyncWhenContext,
|
||||
});
|
||||
|
||||
const signInCommandId = 'workbench.userData.actions.signin';
|
||||
const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut));
|
||||
@@ -697,6 +696,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
},
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT),
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
|
||||
group: '5_sync',
|
||||
command: {
|
||||
id: stopSyncCommandId,
|
||||
title: localize('global activity stop sync', "Turn off Sync")
|
||||
},
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT),
|
||||
});
|
||||
|
||||
const resolveSettingsConflictsCommandId = 'workbench.userData.actions.resolveSettingsConflicts';
|
||||
const resolveSettingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i);
|
||||
@@ -736,17 +743,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
when: resolveKeybindingsConflictsWhenContext,
|
||||
});
|
||||
|
||||
const signOutMenuItem: IMenuItem = {
|
||||
group: '5_sync',
|
||||
command: {
|
||||
id: 'workbench.userData.actions.signout',
|
||||
title: localize('sign out', "Sync: Sign out")
|
||||
},
|
||||
when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn)),
|
||||
};
|
||||
CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut());
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem);
|
||||
|
||||
const configureSyncCommandId = 'workbench.userData.actions.configureSync';
|
||||
CommandsRegistry.registerCommand(configureSyncCommandId, () => this.configureSyncOptions());
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
@@ -767,15 +763,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)),
|
||||
});
|
||||
|
||||
const resetLocalCommandId = 'workbench.userData.actions.resetLocal';
|
||||
CommandsRegistry.registerCommand(resetLocalCommandId, () => this.userDataSyncService.resetLocal());
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: resetLocalCommandId,
|
||||
title: localize('reset local', "Developer: Reset Local (Sync)")
|
||||
},
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,12 @@ export class WebviewInput extends EditorInput {
|
||||
private readonly _onDisposeWebview = this._register(new Emitter<void>());
|
||||
readonly onDisposeWebview = this._onDisposeWebview.event;
|
||||
|
||||
readonly resource = URI.from({
|
||||
scheme: WebviewPanelResourceScheme,
|
||||
path: `webview-panel/webview-${this.id}`
|
||||
});
|
||||
get resource() {
|
||||
return URI.from({
|
||||
scheme: WebviewPanelResourceScheme,
|
||||
path: `webview-panel/webview-${this.id}`
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
|
||||
@@ -53,10 +53,10 @@ export class WalkThroughInput extends EditorInput {
|
||||
private maxTopScroll = 0;
|
||||
private maxBottomScroll = 0;
|
||||
|
||||
readonly resource = this.options.resource;
|
||||
get resource() { return this.options.resource; }
|
||||
|
||||
constructor(
|
||||
private options: WalkThroughInputOptions,
|
||||
private readonly options: WalkThroughInputOptions,
|
||||
@ITextModelService private readonly textModelResolverService: ITextModelService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -289,10 +289,9 @@ export class ElectronWindow extends Disposable {
|
||||
|
||||
private updateDocumentEdited(isDirty = this.workingCopyService.hasDirty): void {
|
||||
if ((!this.isDocumentedEdited && isDirty) || (this.isDocumentedEdited && !isDirty)) {
|
||||
const hasDirtyFiles = this.workingCopyService.hasDirty;
|
||||
this.isDocumentedEdited = hasDirtyFiles;
|
||||
this.isDocumentedEdited = isDirty;
|
||||
|
||||
this.electronService.setDocumentEdited(hasDirtyFiles);
|
||||
this.electronService.setDocumentEdited(isDirty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,73 +5,6 @@
|
||||
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export class BrowserClipboardService implements IClipboardService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private _internalResourcesClipboard: URI[] | undefined;
|
||||
|
||||
async writeText(text: string, type?: string): Promise<void> {
|
||||
if (type) {
|
||||
return; // TODO@sbatten
|
||||
}
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
return navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
const activeElement = <HTMLElement>document.activeElement;
|
||||
const newTextarea = document.createElement('textarea');
|
||||
newTextarea.className = 'clipboard-copy';
|
||||
newTextarea.style.visibility = 'false';
|
||||
newTextarea.style.height = '1px';
|
||||
newTextarea.style.width = '1px';
|
||||
newTextarea.setAttribute('aria-hidden', 'true');
|
||||
newTextarea.style.position = 'absolute';
|
||||
newTextarea.style.top = '-1000';
|
||||
newTextarea.style.left = '-1000';
|
||||
document.body.appendChild(newTextarea);
|
||||
newTextarea.value = text;
|
||||
newTextarea.focus();
|
||||
newTextarea.select();
|
||||
document.execCommand('copy');
|
||||
activeElement.focus();
|
||||
document.body.removeChild(newTextarea);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async readText(type?: string): Promise<string> {
|
||||
if (type) {
|
||||
return ''; // TODO@sbatten
|
||||
}
|
||||
|
||||
return navigator.clipboard.readText();
|
||||
}
|
||||
|
||||
readTextSync(): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
readFindText(): string {
|
||||
// @ts-ignore
|
||||
return undefined;
|
||||
}
|
||||
|
||||
writeFindText(text: string): void { }
|
||||
|
||||
writeResources(resources: URI[]): void {
|
||||
this._internalResourcesClipboard = resources;
|
||||
}
|
||||
|
||||
readResources(): URI[] {
|
||||
return this._internalResourcesClipboard || [];
|
||||
}
|
||||
|
||||
hasResources(): boolean {
|
||||
return this._internalResourcesClipboard !== undefined && this._internalResourcesClipboard.length > 0;
|
||||
}
|
||||
}
|
||||
import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService';
|
||||
|
||||
registerSingleton(IClipboardService, BrowserClipboardService, true);
|
||||
|
||||
@@ -191,22 +191,6 @@ export class ProgressService extends Disposable implements IProgressService {
|
||||
}
|
||||
};
|
||||
|
||||
const createWindowProgress = () => {
|
||||
this.withWindowProgress<R>({
|
||||
location: ProgressLocation.Window,
|
||||
title: options.title
|
||||
}, progress => {
|
||||
if (progressStateModel.step) {
|
||||
progress.report(progressStateModel.step);
|
||||
}
|
||||
|
||||
const disposable = progressStateModel.onDidReport(step => progress.report(step));
|
||||
Event.once(progressStateModel.onDispose)(() => disposable.dispose());
|
||||
|
||||
return progressStateModel.promise;
|
||||
});
|
||||
};
|
||||
|
||||
const createNotification = (message: string, increment?: number): INotificationHandle => {
|
||||
const notificationDisposables = new DisposableStore();
|
||||
|
||||
@@ -254,18 +238,8 @@ export class ProgressService extends Disposable implements IProgressService {
|
||||
|
||||
updateProgress(handle, increment);
|
||||
|
||||
Event.once(handle.onDidClose)(() => {
|
||||
|
||||
// Switch to window based progress once the notification
|
||||
// is being closed even though still running and not
|
||||
// cancelled.
|
||||
if (!progressStateModel.done) {
|
||||
createWindowProgress();
|
||||
}
|
||||
|
||||
// Clear disposables
|
||||
notificationDisposables.dispose();
|
||||
});
|
||||
// Clear upon dispose
|
||||
Event.once(handle.onDidClose)(() => notificationDisposables.dispose());
|
||||
|
||||
return handle;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService';
|
||||
import { ITextFileService, IResourceEncodings, IResourceEncoding, ModelState } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, IResourceEncodings, IResourceEncoding, TextFileEditorModelState } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
@@ -24,7 +24,7 @@ export class BrowserTextFileService extends AbstractTextFileService {
|
||||
}
|
||||
|
||||
protected onBeforeShutdown(reason: ShutdownReason): boolean {
|
||||
if (this.files.getAll().some(model => model.hasState(ModelState.PENDING_SAVE))) {
|
||||
if (this.files.models.some(model => model.hasState(TextFileEditorModelState.PENDING_SAVE))) {
|
||||
console.warn('Unload prevented: pending file saves');
|
||||
|
||||
return true; // files are pending to be saved: veto
|
||||
|
||||
@@ -204,11 +204,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
||||
else {
|
||||
const model = this.files.get(resource);
|
||||
if (model) {
|
||||
|
||||
// Save with options
|
||||
await model.save(options);
|
||||
|
||||
return !model.isDirty() ? resource : undefined;
|
||||
return await model.save(options) ? resource : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,9 +378,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
||||
}
|
||||
|
||||
// save model
|
||||
await targetModel.save(options);
|
||||
|
||||
return true;
|
||||
return await targetModel.save(options);
|
||||
}
|
||||
|
||||
private async confirmOverwrite(resource: URI): Promise<boolean> {
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { ITextFileService, ModelState, ITextFileEditorModel, ITextFileStreamContent, ILoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, LoadReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileLoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, TextFileLoadReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/editor';
|
||||
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
|
||||
import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup';
|
||||
@@ -43,7 +43,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
private readonly _onDidChangeContent = this._register(new Emitter<void>());
|
||||
readonly onDidChangeContent = this._onDidChangeContent.event;
|
||||
|
||||
private readonly _onDidLoad = this._register(new Emitter<LoadReason>());
|
||||
private readonly _onDidLoad = this._register(new Emitter<TextFileLoadReason>());
|
||||
readonly onDidLoad = this._onDidLoad.event;
|
||||
|
||||
private readonly _onDidChangeDirty = this._register(new Emitter<void>());
|
||||
@@ -248,7 +248,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
|
||||
//#region Load
|
||||
|
||||
async load(options?: ILoadOptions): Promise<ITextFileEditorModel> {
|
||||
async load(options?: ITextFileLoadOptions): Promise<TextFileEditorModel> {
|
||||
this.logService.trace('[text file model] load() - enter', this.resource.toString());
|
||||
|
||||
// It is very important to not reload the model when the model is dirty.
|
||||
@@ -281,7 +281,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
return this.loadFromFile(options);
|
||||
}
|
||||
|
||||
private async loadFromBackup(backup: IResolvedBackup<IBackupMetaData>, options?: ILoadOptions): Promise<TextFileEditorModel> {
|
||||
private async loadFromBackup(backup: IResolvedBackup<IBackupMetaData>, options?: ITextFileLoadOptions): Promise<TextFileEditorModel> {
|
||||
|
||||
// Load with backup
|
||||
this.loadFromContent({
|
||||
@@ -303,7 +303,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
return this;
|
||||
}
|
||||
|
||||
private async loadFromFile(options?: ILoadOptions): Promise<TextFileEditorModel> {
|
||||
private async loadFromFile(options?: ITextFileLoadOptions): Promise<TextFileEditorModel> {
|
||||
const forceReadFromDisk = options?.forceReadFromDisk;
|
||||
const allowBinary = this.isResolved() /* always allow if we resolved previously */ || options?.allowBinary;
|
||||
|
||||
@@ -358,7 +358,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
}
|
||||
|
||||
private loadFromContent(content: ITextFileStreamContent, options?: ILoadOptions, fromBackup?: boolean): TextFileEditorModel {
|
||||
private loadFromContent(content: ITextFileStreamContent, options?: ITextFileLoadOptions, fromBackup?: boolean): TextFileEditorModel {
|
||||
this.logService.trace('[text file model] load() - resolved content', this.resource.toString());
|
||||
|
||||
// Update our resolved disk stat model
|
||||
@@ -396,7 +396,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
// Emit as event
|
||||
this._onDidLoad.fire(options?.reason ?? LoadReason.OTHER);
|
||||
this._onDidLoad.fire(options?.reason ?? TextFileLoadReason.OTHER);
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -544,7 +544,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
if (
|
||||
(this.hasState(ModelState.CONFLICT) || this.hasState(ModelState.ERROR)) &&
|
||||
(this.hasState(TextFileEditorModelState.CONFLICT) || this.hasState(TextFileEditorModelState.ERROR)) &&
|
||||
(options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)
|
||||
) {
|
||||
this.logService.trace('[text file model] save() - ignoring auto save request for model that is in conflict or error', this.resource.toString());
|
||||
@@ -794,19 +794,19 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
|
||||
//#endregion
|
||||
|
||||
hasState(state: ModelState): boolean {
|
||||
hasState(state: TextFileEditorModelState): boolean {
|
||||
switch (state) {
|
||||
case ModelState.CONFLICT:
|
||||
case TextFileEditorModelState.CONFLICT:
|
||||
return this.inConflictMode;
|
||||
case ModelState.DIRTY:
|
||||
case TextFileEditorModelState.DIRTY:
|
||||
return this.dirty;
|
||||
case ModelState.ERROR:
|
||||
case TextFileEditorModelState.ERROR:
|
||||
return this.inErrorMode;
|
||||
case ModelState.ORPHAN:
|
||||
case TextFileEditorModelState.ORPHAN:
|
||||
return this.inOrphanMode;
|
||||
case ModelState.PENDING_SAVE:
|
||||
case TextFileEditorModelState.PENDING_SAVE:
|
||||
return this.saveSequentializer.hasPending();
|
||||
case ModelState.SAVED:
|
||||
case TextFileEditorModelState.SAVED:
|
||||
return !this.dirty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
|
||||
import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ITextFileEditorModel, ITextFileEditorModelManager, IModelLoadOrCreateOptions, ITextFileModelLoadEvent, ITextFileModelSaveEvent, ITextFileSaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileEditorModel, ITextFileEditorModelManager, ITextFileEditorModelLoadOrCreateOptions, ITextFileLoadEvent, ITextFileSaveEvent, ITextFileSaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
@@ -29,27 +29,34 @@ import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
||||
|
||||
export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager {
|
||||
|
||||
private readonly _onDidCreate = this._register(new Emitter<ITextFileEditorModel>());
|
||||
private readonly _onDidCreate = this._register(new Emitter<TextFileEditorModel>());
|
||||
readonly onDidCreate = this._onDidCreate.event;
|
||||
|
||||
private readonly _onDidLoad = this._register(new Emitter<ITextFileModelLoadEvent>());
|
||||
private readonly _onDidLoad = this._register(new Emitter<ITextFileLoadEvent>());
|
||||
readonly onDidLoad = this._onDidLoad.event;
|
||||
|
||||
private readonly _onDidChangeDirty = this._register(new Emitter<ITextFileEditorModel>());
|
||||
private readonly _onDidChangeDirty = this._register(new Emitter<TextFileEditorModel>());
|
||||
readonly onDidChangeDirty = this._onDidChangeDirty.event;
|
||||
|
||||
private readonly _onDidSaveError = this._register(new Emitter<ITextFileEditorModel>());
|
||||
private readonly _onDidSaveError = this._register(new Emitter<TextFileEditorModel>());
|
||||
readonly onDidSaveError = this._onDidSaveError.event;
|
||||
|
||||
private readonly _onDidSave = this._register(new Emitter<ITextFileModelSaveEvent>());
|
||||
private readonly _onDidSave = this._register(new Emitter<ITextFileSaveEvent>());
|
||||
readonly onDidSave = this._onDidSave.event;
|
||||
|
||||
private readonly _onDidRevert = this._register(new Emitter<ITextFileEditorModel>());
|
||||
private readonly _onDidRevert = this._register(new Emitter<TextFileEditorModel>());
|
||||
readonly onDidRevert = this._onDidRevert.event;
|
||||
|
||||
private readonly _onDidChangeEncoding = this._register(new Emitter<ITextFileEditorModel>());
|
||||
private readonly _onDidChangeEncoding = this._register(new Emitter<TextFileEditorModel>());
|
||||
readonly onDidChangeEncoding = this._onDidChangeEncoding.event;
|
||||
|
||||
private readonly mapResourceToModel = new ResourceMap<TextFileEditorModel>();
|
||||
private readonly mapResourceToModelListeners = new ResourceMap<IDisposable>();
|
||||
private readonly mapResourceToDisposeListener = new ResourceMap<IDisposable>();
|
||||
private readonly mapResourceToPendingModelLoaders = new ResourceMap<Promise<TextFileEditorModel>>();
|
||||
|
||||
private readonly modelLoadQueue = this._register(new ResourceQueue());
|
||||
|
||||
saveErrorHandler = (() => {
|
||||
const notificationService = this.notificationService;
|
||||
|
||||
@@ -60,12 +67,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
};
|
||||
})();
|
||||
|
||||
private readonly mapResourceToModel = new ResourceMap<ITextFileEditorModel>();
|
||||
private readonly mapResourceToModelListeners = new ResourceMap<IDisposable>();
|
||||
private readonly mapResourceToDisposeListener = new ResourceMap<IDisposable>();
|
||||
private readonly mapResourceToPendingModelLoaders = new ResourceMap<Promise<ITextFileEditorModel>>();
|
||||
|
||||
private readonly modelLoadQueue = this._register(new ResourceQueue());
|
||||
get models(): TextFileEditorModel[] {
|
||||
return this.mapResourceToModel.values();
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
@@ -99,13 +103,15 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
//
|
||||
// Note: we also consider the added event because it could be that a file was added
|
||||
// and updated right after.
|
||||
distinct(coalesce([...e.getUpdated(), ...e.getAdded()]
|
||||
.map(({ resource }) => this.get(resource)))
|
||||
.filter(model => model && !model.isDirty()), model => model.resource.toString())
|
||||
.forEach(model => this.queueModelLoad(model));
|
||||
distinct(
|
||||
coalesce(
|
||||
[...e.getUpdated(), ...e.getAdded()].map(({ resource }) => this.get(resource))
|
||||
).filter(model => model && model.isResolved() && !model.isDirty()),
|
||||
model => model.resource.toString()
|
||||
).forEach(model => this.queueModelLoad(model));
|
||||
}
|
||||
|
||||
private queueModelLoad(model: ITextFileEditorModel): void {
|
||||
private queueModelLoad(model: TextFileEditorModel): void {
|
||||
|
||||
// Load model to update (use a queue to prevent accumulation of loads
|
||||
// when the load actually takes long. At most we only want the queue
|
||||
@@ -131,9 +137,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
if (source && (e.operation === FileOperation.COPY || e.operation === FileOperation.MOVE)) {
|
||||
|
||||
// find all models that related to either source or target (can be many if resource is a folder)
|
||||
const sourceModels: ITextFileEditorModel[] = [];
|
||||
const targetModels: ITextFileEditorModel[] = [];
|
||||
for (const model of this.getAll()) {
|
||||
const sourceModels: TextFileEditorModel[] = [];
|
||||
const targetModels: TextFileEditorModel[] = [];
|
||||
for (const model of this.models) {
|
||||
const resource = model.resource;
|
||||
|
||||
if (isEqualOrParent(resource, e.target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) {
|
||||
@@ -232,11 +238,11 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
}
|
||||
}
|
||||
|
||||
get(resource: URI): ITextFileEditorModel | undefined {
|
||||
get(resource: URI): TextFileEditorModel | undefined {
|
||||
return this.mapResourceToModel.get(resource);
|
||||
}
|
||||
|
||||
async resolve(resource: URI, options?: IModelLoadOrCreateOptions): Promise<ITextFileEditorModel> {
|
||||
async resolve(resource: URI, options?: ITextFileEditorModelLoadOrCreateOptions): Promise<TextFileEditorModel> {
|
||||
|
||||
// Return early if model is currently being loaded
|
||||
const pendingLoad = this.mapResourceToPendingModelLoaders.get(resource);
|
||||
@@ -244,7 +250,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
return pendingLoad;
|
||||
}
|
||||
|
||||
let modelPromise: Promise<ITextFileEditorModel>;
|
||||
let modelPromise: Promise<TextFileEditorModel>;
|
||||
let model = this.get(resource);
|
||||
let didCreateModel = false;
|
||||
|
||||
@@ -275,20 +281,23 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
modelPromise = model.load(options);
|
||||
|
||||
// Install model listeners
|
||||
const listeners = new DisposableStore();
|
||||
listeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model: newModel, reason })));
|
||||
listeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(newModel)));
|
||||
listeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(newModel)));
|
||||
listeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: newModel, reason })));
|
||||
listeners.add(model.onDidRevert(() => this._onDidRevert.fire(newModel)));
|
||||
listeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(newModel)));
|
||||
const modelListeners = new DisposableStore();
|
||||
modelListeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model: newModel, reason })));
|
||||
modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(newModel)));
|
||||
modelListeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(newModel)));
|
||||
modelListeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: newModel, reason })));
|
||||
modelListeners.add(model.onDidRevert(() => this._onDidRevert.fire(newModel)));
|
||||
modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(newModel)));
|
||||
|
||||
this.mapResourceToModelListeners.set(resource, listeners);
|
||||
this.mapResourceToModelListeners.set(resource, modelListeners);
|
||||
}
|
||||
|
||||
// Store pending loads to avoid race conditions
|
||||
this.mapResourceToPendingModelLoaders.set(resource, modelPromise);
|
||||
|
||||
// Make known to manager (if not already known)
|
||||
this.add(resource, model);
|
||||
|
||||
// Signal as event if we created the model
|
||||
if (didCreateModel) {
|
||||
this._onDidCreate.fire(model);
|
||||
@@ -297,9 +306,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
try {
|
||||
const resolvedModel = await modelPromise;
|
||||
|
||||
// Make known to manager (if not already known)
|
||||
this.add(resource, resolvedModel);
|
||||
|
||||
// Remove from pending loads
|
||||
this.mapResourceToPendingModelLoaders.delete(resource);
|
||||
|
||||
@@ -308,8 +314,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
resolvedModel.setMode(options.mode);
|
||||
}
|
||||
|
||||
// Model can be dirty if a backup was restored, so we make sure to have this event delivered
|
||||
if (resolvedModel.isDirty()) {
|
||||
// Model can be dirty if a backup was restored, so we make sure to
|
||||
// have this event delivered if we created the model here
|
||||
if (didCreateModel && resolvedModel.isDirty()) {
|
||||
this._onDidChangeDirty.fire(resolvedModel);
|
||||
}
|
||||
|
||||
@@ -328,18 +335,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
}
|
||||
}
|
||||
|
||||
getAll(filter?: (model: ITextFileEditorModel) => boolean): ITextFileEditorModel[] {
|
||||
const res: ITextFileEditorModel[] = [];
|
||||
this.mapResourceToModel.forEach(model => {
|
||||
if (!filter || filter(model)) {
|
||||
res.push(model);
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
add(resource: URI, model: ITextFileEditorModel): void {
|
||||
add(resource: URI, model: TextFileEditorModel): void {
|
||||
const knownModel = this.mapResourceToModel.get(resource);
|
||||
if (knownModel === model) {
|
||||
return; // already cached
|
||||
|
||||
@@ -161,13 +161,18 @@ export const enum TextFileOperationResult {
|
||||
}
|
||||
|
||||
export class TextFileOperationError extends FileOperationError {
|
||||
constructor(message: string, public textFileOperationResult: TextFileOperationResult, public options?: IReadTextFileOptions & IWriteTextFileOptions) {
|
||||
super(message, FileOperationResult.FILE_OTHER_ERROR);
|
||||
}
|
||||
|
||||
static isTextFileOperationError(obj: unknown): obj is TextFileOperationError {
|
||||
return obj instanceof Error && !isUndefinedOrNull((obj as TextFileOperationError).textFileOperationResult);
|
||||
}
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
public textFileOperationResult: TextFileOperationResult,
|
||||
public options?: IReadTextFileOptions & IWriteTextFileOptions
|
||||
) {
|
||||
super(message, FileOperationResult.FILE_OTHER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IResourceEncodings {
|
||||
@@ -193,7 +198,7 @@ export interface ISaveErrorHandler {
|
||||
/**
|
||||
* States the text file editor model can be in.
|
||||
*/
|
||||
export const enum ModelState {
|
||||
export const enum TextFileEditorModelState {
|
||||
|
||||
/**
|
||||
* A model is saved.
|
||||
@@ -228,17 +233,7 @@ export const enum ModelState {
|
||||
ERROR
|
||||
}
|
||||
|
||||
export interface ITextFileOperationResult {
|
||||
results: IResult[];
|
||||
}
|
||||
|
||||
export interface IResult {
|
||||
source: URI;
|
||||
target?: URI;
|
||||
error?: boolean;
|
||||
}
|
||||
|
||||
export const enum LoadReason {
|
||||
export const enum TextFileLoadReason {
|
||||
EDITOR = 1,
|
||||
REFERENCE = 2,
|
||||
OTHER = 3
|
||||
@@ -268,12 +263,12 @@ export interface ITextFileStreamContent extends IBaseTextFileContent {
|
||||
value: ITextBufferFactory;
|
||||
}
|
||||
|
||||
export interface IModelLoadOrCreateOptions {
|
||||
export interface ITextFileEditorModelLoadOrCreateOptions {
|
||||
|
||||
/**
|
||||
* Context why the model is being loaded or created.
|
||||
*/
|
||||
reason?: LoadReason;
|
||||
reason?: TextFileLoadReason;
|
||||
|
||||
/**
|
||||
* The language mode to use for the model text content.
|
||||
@@ -303,14 +298,14 @@ export interface IModelLoadOrCreateOptions {
|
||||
allowBinary?: boolean;
|
||||
}
|
||||
|
||||
export interface ITextFileModelSaveEvent {
|
||||
export interface ITextFileSaveEvent {
|
||||
model: ITextFileEditorModel;
|
||||
reason: SaveReason;
|
||||
}
|
||||
|
||||
export interface ITextFileModelLoadEvent {
|
||||
export interface ITextFileLoadEvent {
|
||||
model: ITextFileEditorModel;
|
||||
reason: LoadReason;
|
||||
reason: TextFileLoadReason;
|
||||
}
|
||||
|
||||
export interface ITextFileSaveParticipant {
|
||||
@@ -330,35 +325,69 @@ export interface ITextFileSaveParticipant {
|
||||
export interface ITextFileEditorModelManager {
|
||||
|
||||
readonly onDidCreate: Event<ITextFileEditorModel>;
|
||||
readonly onDidLoad: Event<ITextFileModelLoadEvent>;
|
||||
readonly onDidLoad: Event<ITextFileLoadEvent>;
|
||||
readonly onDidChangeDirty: Event<ITextFileEditorModel>;
|
||||
readonly onDidSaveError: Event<ITextFileEditorModel>;
|
||||
readonly onDidSave: Event<ITextFileModelSaveEvent>;
|
||||
readonly onDidRevert: Event<ITextFileEditorModel>;
|
||||
readonly onDidChangeEncoding: Event<ITextFileEditorModel>;
|
||||
readonly onDidSaveError: Event<ITextFileEditorModel>;
|
||||
readonly onDidSave: Event<ITextFileSaveEvent>;
|
||||
readonly onDidRevert: Event<ITextFileEditorModel>;
|
||||
|
||||
readonly models: ITextFileEditorModel[];
|
||||
|
||||
saveErrorHandler: ISaveErrorHandler;
|
||||
|
||||
/**
|
||||
* Returns the text file editor model for the provided resource
|
||||
* or undefined if none.
|
||||
*/
|
||||
get(resource: URI): ITextFileEditorModel | undefined;
|
||||
getAll(): ITextFileEditorModel[];
|
||||
|
||||
resolve(resource: URI, options?: IModelLoadOrCreateOptions): Promise<ITextFileEditorModel>;
|
||||
/**
|
||||
* Allows to load a text file model from disk.
|
||||
*/
|
||||
resolve(resource: URI, options?: ITextFileEditorModelLoadOrCreateOptions): Promise<ITextFileEditorModel>;
|
||||
|
||||
/**
|
||||
* Adds a participant for saving text file models.
|
||||
*/
|
||||
addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable;
|
||||
|
||||
runSaveParticipants(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise<void>
|
||||
|
||||
disposeModel(model: ITextFileEditorModel): void;
|
||||
}
|
||||
|
||||
export interface ITextFileSaveOptions extends ISaveOptions {
|
||||
|
||||
/**
|
||||
* Makes the file writable if it is readonly.
|
||||
*/
|
||||
overwriteReadonly?: boolean;
|
||||
|
||||
/**
|
||||
* Overwrite the encoding of the file on disk as configured.
|
||||
*/
|
||||
overwriteEncoding?: boolean;
|
||||
|
||||
/**
|
||||
* Save the file with elevated privileges.
|
||||
*
|
||||
* Note: This may not be supported in all environments.
|
||||
*/
|
||||
writeElevated?: boolean;
|
||||
|
||||
/**
|
||||
* Allows to write to a file even if it has been modified on disk.
|
||||
*/
|
||||
ignoreModifiedSince?: boolean;
|
||||
|
||||
/**
|
||||
* If set, will bubble up the error to the caller instead of handling it.
|
||||
*/
|
||||
ignoreErrorHandler?: boolean;
|
||||
}
|
||||
|
||||
export interface ILoadOptions {
|
||||
export interface ITextFileLoadOptions {
|
||||
|
||||
/**
|
||||
* Go to disk bypassing any cache of the model if any.
|
||||
@@ -373,39 +402,29 @@ export interface ILoadOptions {
|
||||
/**
|
||||
* Context why the model is being loaded.
|
||||
*/
|
||||
reason?: LoadReason;
|
||||
reason?: TextFileLoadReason;
|
||||
}
|
||||
|
||||
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport, IWorkingCopy {
|
||||
|
||||
readonly onDidChangeContent: Event<void>;
|
||||
readonly onDidLoad: Event<LoadReason>;
|
||||
readonly onDidSaveError: Event<void>;
|
||||
readonly onDidSave: Event<SaveReason>;
|
||||
readonly onDidRevert: Event<void>;
|
||||
readonly onDidChangeEncoding: Event<void>;
|
||||
readonly onDidChangeOrphaned: Event<void>;
|
||||
|
||||
hasState(state: ModelState): boolean;
|
||||
hasState(state: TextFileEditorModelState): boolean;
|
||||
|
||||
updatePreferredEncoding(encoding: string | undefined): void;
|
||||
|
||||
updateTextEditorModel(newValue?: ITextBufferFactory, preferredMode?: string): void;
|
||||
|
||||
save(options?: ITextFileSaveOptions): Promise<boolean>;
|
||||
|
||||
load(options?: ILoadOptions): Promise<ITextFileEditorModel>;
|
||||
|
||||
revert(options?: IRevertOptions): Promise<boolean>;
|
||||
|
||||
isDirty(): boolean; // {{SQL CARBON EDIT}} strict-null-check
|
||||
load(options?: ITextFileLoadOptions): Promise<ITextFileEditorModel>;
|
||||
|
||||
setDirty(dirty: boolean): void;
|
||||
isDirty(): boolean; // {{SQL CARBON EDIT}} strict-null-check
|
||||
|
||||
getMode(): string | undefined;
|
||||
|
||||
isResolved(): this is IResolvedTextFileEditorModel;
|
||||
|
||||
isDisposed(): boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as assert from 'assert';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { EncodingMode } from 'vs/workbench/common/editor';
|
||||
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
|
||||
import { ITextFileService, ModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, TextFileEditorModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { createFileInput, TestFileService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { toResource } from 'vs/base/test/common/utils';
|
||||
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
|
||||
@@ -100,7 +100,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
|
||||
model.textEditorModel!.setValue('bar');
|
||||
assert.ok(getLastModifiedTime(model) <= Date.now());
|
||||
assert.ok(model.hasState(ModelState.DIRTY));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.DIRTY));
|
||||
|
||||
assert.equal(accessor.workingCopyService.dirtyCount, 1);
|
||||
assert.equal(accessor.workingCopyService.isDirty(model.resource), true);
|
||||
@@ -116,11 +116,11 @@ suite('Files - TextFileEditorModel', () => {
|
||||
});
|
||||
|
||||
const pendingSave = model.save();
|
||||
assert.ok(model.hasState(ModelState.PENDING_SAVE));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE));
|
||||
|
||||
await pendingSave;
|
||||
|
||||
assert.ok(model.hasState(ModelState.SAVED));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.SAVED));
|
||||
assert.ok(!model.isDirty());
|
||||
assert.ok(savedEvent);
|
||||
assert.ok(workingCopyEvent);
|
||||
@@ -169,11 +169,11 @@ suite('Files - TextFileEditorModel', () => {
|
||||
accessor.fileService.writeShouldThrowError = new Error('failed to write');
|
||||
try {
|
||||
const pendingSave = model.save();
|
||||
assert.ok(model.hasState(ModelState.PENDING_SAVE));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE));
|
||||
|
||||
await pendingSave;
|
||||
|
||||
assert.ok(model.hasState(ModelState.ERROR));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.ERROR));
|
||||
assert.ok(model.isDirty());
|
||||
assert.ok(saveErrorEvent);
|
||||
|
||||
@@ -199,11 +199,11 @@ suite('Files - TextFileEditorModel', () => {
|
||||
accessor.fileService.writeShouldThrowError = new FileOperationError('save conflict', FileOperationResult.FILE_MODIFIED_SINCE);
|
||||
try {
|
||||
const pendingSave = model.save();
|
||||
assert.ok(model.hasState(ModelState.PENDING_SAVE));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE));
|
||||
|
||||
await pendingSave;
|
||||
|
||||
assert.ok(model.hasState(ModelState.CONFLICT));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.CONFLICT));
|
||||
assert.ok(model.isDirty());
|
||||
assert.ok(saveErrorEvent);
|
||||
|
||||
@@ -273,7 +273,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
|
||||
test('Load does not trigger save', async function () {
|
||||
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined);
|
||||
assert.ok(model.hasState(ModelState.SAVED));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.SAVED));
|
||||
|
||||
model.onDidSave(e => assert.fail());
|
||||
model.onDidChangeDirty(e => assert.fail());
|
||||
@@ -290,7 +290,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
await model.load();
|
||||
model.textEditorModel!.setValue('foo');
|
||||
assert.ok(model.isDirty());
|
||||
assert.ok(model.hasState(ModelState.DIRTY));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.DIRTY));
|
||||
|
||||
await model.load();
|
||||
assert.ok(model.isDirty());
|
||||
|
||||
@@ -51,7 +51,7 @@ suite('Files - TextFileEditorModelManager', () => {
|
||||
|
||||
assert.ok(!manager.get(fileUpper));
|
||||
|
||||
let results = manager.getAll();
|
||||
let results = manager.models;
|
||||
assert.strictEqual(3, results.length);
|
||||
|
||||
let result = manager.get(URI.file('/yes'));
|
||||
@@ -68,19 +68,19 @@ suite('Files - TextFileEditorModelManager', () => {
|
||||
|
||||
manager.remove(URI.file(''));
|
||||
|
||||
results = manager.getAll();
|
||||
results = manager.models;
|
||||
assert.strictEqual(3, results.length);
|
||||
|
||||
manager.remove(URI.file('/some/other.html'));
|
||||
results = manager.getAll();
|
||||
results = manager.models;
|
||||
assert.strictEqual(2, results.length);
|
||||
|
||||
manager.remove(fileUpper);
|
||||
results = manager.getAll();
|
||||
results = manager.models;
|
||||
assert.strictEqual(2, results.length);
|
||||
|
||||
manager.clear();
|
||||
results = manager.getAll();
|
||||
results = manager.models;
|
||||
assert.strictEqual(0, results.length);
|
||||
|
||||
model1.dispose();
|
||||
@@ -98,7 +98,10 @@ suite('Files - TextFileEditorModelManager', () => {
|
||||
events.push(model);
|
||||
});
|
||||
|
||||
const model = await manager.resolve(resource, { encoding });
|
||||
const modelPromise = manager.resolve(resource, { encoding });
|
||||
assert.ok(manager.get(resource)); // model known even before resolved()
|
||||
|
||||
const model = await modelPromise;
|
||||
assert.ok(model);
|
||||
assert.equal(model.getEncoding(), encoding);
|
||||
assert.equal(manager.get(resource), model);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IDisposable, toDisposable, IReference, ReferenceCollection, ImmortalReference } from 'vs/base/common/lifecycle';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
|
||||
import { ITextFileService, LoadReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, TextFileLoadReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import * as network from 'vs/base/common/network';
|
||||
import { ITextModelService, ITextModelContentProvider, ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
|
||||
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
|
||||
@@ -38,7 +38,7 @@ class ResourceModelCollection extends ReferenceCollection<Promise<ITextEditorMod
|
||||
|
||||
// File or remote file provider already known
|
||||
if (this.fileService.canHandleResource(resource)) {
|
||||
return this.textFileService.files.resolve(resource, { reason: LoadReason.REFERENCE });
|
||||
return this.textFileService.files.resolve(resource, { reason: TextFileLoadReason.REFERENCE });
|
||||
}
|
||||
|
||||
// Virtual documents
|
||||
|
||||
@@ -67,22 +67,22 @@ export interface IUntitledTextEditorModelManager {
|
||||
/**
|
||||
* Events for when untitled text editors change (e.g. getting dirty, saved or reverted).
|
||||
*/
|
||||
readonly onDidChangeDirty: Event<URI>;
|
||||
readonly onDidChangeDirty: Event<IUntitledTextEditorModel>;
|
||||
|
||||
/**
|
||||
* Events for when untitled text editor encodings change.
|
||||
*/
|
||||
readonly onDidChangeEncoding: Event<URI>;
|
||||
readonly onDidChangeEncoding: Event<IUntitledTextEditorModel>;
|
||||
|
||||
/**
|
||||
* Events for when untitled text editor labels change.
|
||||
*/
|
||||
readonly onDidChangeLabel: Event<URI>;
|
||||
readonly onDidChangeLabel: Event<IUntitledTextEditorModel>;
|
||||
|
||||
/**
|
||||
* Events for when untitled text editors are disposed.
|
||||
*/
|
||||
readonly onDidDisposeModel: Event<URI>;
|
||||
readonly onDidDispose: Event<IUntitledTextEditorModel>;
|
||||
|
||||
/**
|
||||
* Creates a new untitled editor model with the provided options. If the `untitledResource`
|
||||
@@ -117,16 +117,16 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidChangeDirty = this._register(new Emitter<URI>());
|
||||
private readonly _onDidChangeDirty = this._register(new Emitter<IUntitledTextEditorModel>());
|
||||
readonly onDidChangeDirty = this._onDidChangeDirty.event;
|
||||
|
||||
private readonly _onDidChangeEncoding = this._register(new Emitter<URI>());
|
||||
private readonly _onDidChangeEncoding = this._register(new Emitter<IUntitledTextEditorModel>());
|
||||
readonly onDidChangeEncoding = this._onDidChangeEncoding.event;
|
||||
|
||||
private readonly _onDidDisposeModel = this._register(new Emitter<URI>());
|
||||
readonly onDidDisposeModel = this._onDidDisposeModel.event;
|
||||
private readonly _onDidDispose = this._register(new Emitter<IUntitledTextEditorModel>());
|
||||
readonly onDidDispose = this._onDidDispose.event;
|
||||
|
||||
private readonly _onDidChangeLabel = this._register(new Emitter<URI>());
|
||||
private readonly _onDidChangeLabel = this._register(new Emitter<IUntitledTextEditorModel>());
|
||||
readonly onDidChangeLabel = this._onDidChangeLabel.event;
|
||||
|
||||
private readonly mapResourceToModel = new ResourceMap<UntitledTextEditorModel>();
|
||||
@@ -220,10 +220,10 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe
|
||||
|
||||
private registerModel(model: UntitledTextEditorModel): void {
|
||||
const modelDisposables = new DisposableStore();
|
||||
modelDisposables.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model.resource)));
|
||||
modelDisposables.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model.resource)));
|
||||
modelDisposables.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model.resource)));
|
||||
modelDisposables.add(model.onDispose(() => this._onDidDisposeModel.fire(model.resource)));
|
||||
modelDisposables.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model)));
|
||||
modelDisposables.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model)));
|
||||
modelDisposables.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model)));
|
||||
modelDisposables.add(model.onDispose(() => this._onDidDispose.fire(model)));
|
||||
|
||||
// Remove from cache on dispose
|
||||
Event.once(model.onDispose)(() => {
|
||||
|
||||
@@ -108,10 +108,10 @@ suite('Untitled text editors', () => {
|
||||
|
||||
function awaitDidChangeDirty(service: IUntitledTextEditorService): Promise<URI> {
|
||||
return new Promise(c => {
|
||||
const listener = service.onDidChangeDirty(async resource => {
|
||||
const listener = service.onDidChangeDirty(async model => {
|
||||
listener.dispose();
|
||||
|
||||
c(resource);
|
||||
c(model.resource);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -328,9 +328,9 @@ suite('Untitled text editors', () => {
|
||||
|
||||
let counter = 0;
|
||||
|
||||
service.onDidChangeEncoding(r => {
|
||||
service.onDidChangeEncoding(model => {
|
||||
counter++;
|
||||
assert.equal(r.toString(), input.resource.toString());
|
||||
assert.equal(model.resource.toString(), input.resource.toString());
|
||||
});
|
||||
|
||||
// encoding
|
||||
@@ -347,9 +347,9 @@ suite('Untitled text editors', () => {
|
||||
|
||||
let counter = 0;
|
||||
|
||||
service.onDidChangeLabel(r => {
|
||||
service.onDidChangeLabel(model => {
|
||||
counter++;
|
||||
assert.equal(r.toString(), input.resource.toString());
|
||||
assert.equal(model.resource.toString(), input.resource.toString());
|
||||
});
|
||||
|
||||
// label
|
||||
@@ -366,9 +366,9 @@ suite('Untitled text editors', () => {
|
||||
|
||||
let counter = 0;
|
||||
|
||||
service.onDidDisposeModel(r => {
|
||||
service.onDidDispose(model => {
|
||||
counter++;
|
||||
assert.equal(r.toString(), input.resource.toString());
|
||||
assert.equal(model.resource.toString(), input.resource.toString());
|
||||
});
|
||||
|
||||
const model = await input.resolve();
|
||||
|
||||
@@ -118,6 +118,18 @@ export interface IWorkingCopyFileService {
|
||||
delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise<void>;
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Path related
|
||||
|
||||
/**
|
||||
* Will return all working copies that are dirty matching the provided resource.
|
||||
* If the resource is a folder and the scheme supports file operations, a working
|
||||
* copy that is dirty and is a child of that folder will also be returned.
|
||||
*/
|
||||
getDirty(resource: URI): IWorkingCopy[];
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
export class WorkingCopyFileService extends Disposable implements IWorkingCopyFileService {
|
||||
@@ -167,7 +179,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
||||
// handle dirty working copies depending on the operation:
|
||||
// - move: revert both source and target (if any)
|
||||
// - copy: revert target (if any)
|
||||
const dirtyWorkingCopies = (move ? [...this.getDirtyWorkingCopies(source), ...this.getDirtyWorkingCopies(target)] : this.getDirtyWorkingCopies(target));
|
||||
const dirtyWorkingCopies = (move ? [...this.getDirty(source), ...this.getDirty(target)] : this.getDirty(target));
|
||||
await Promise.all(dirtyWorkingCopies.map(dirtyWorkingCopy => dirtyWorkingCopy.revert({ soft: true })));
|
||||
|
||||
// now we can rename the source to target via file operation
|
||||
@@ -202,7 +214,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
||||
// Check for any existing dirty working copies for the resource
|
||||
// and do a soft revert before deleting to be able to close
|
||||
// any opened editor with these working copies
|
||||
const dirtyWorkingCopies = this.getDirtyWorkingCopies(resource);
|
||||
const dirtyWorkingCopies = this.getDirty(resource);
|
||||
await Promise.all(dirtyWorkingCopies.map(dirtyWorkingCopy => dirtyWorkingCopy.revert({ soft: true })));
|
||||
|
||||
// Now actually delete from disk
|
||||
@@ -220,7 +232,10 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
||||
await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None);
|
||||
}
|
||||
|
||||
private getDirtyWorkingCopies(resource: URI): IWorkingCopy[] {
|
||||
|
||||
//#region Path related
|
||||
|
||||
getDirty(resource: URI): IWorkingCopy[] {
|
||||
return this.workingCopyService.dirtyWorkingCopies.filter(dirty => {
|
||||
if (this.fileService.canHandleResource(resource)) {
|
||||
// only check for parents if the resource can be handled
|
||||
@@ -232,6 +247,8 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
||||
return isEqual(dirty.resource, resource);
|
||||
});
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
registerSingleton(IWorkingCopyFileService, WorkingCopyFileService, true);
|
||||
|
||||
@@ -165,4 +165,35 @@ suite('WorkingCopyFileService', () => {
|
||||
listener1.dispose();
|
||||
listener2.dispose();
|
||||
}
|
||||
|
||||
test('getDirty', async function () {
|
||||
const model1 = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file-1.txt'), 'utf8', undefined);
|
||||
(<TextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
|
||||
|
||||
const model2 = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file-2.txt'), 'utf8', undefined);
|
||||
(<TextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
|
||||
|
||||
let dirty = accessor.workingCopyFileService.getDirty(model1.resource);
|
||||
assert.equal(dirty.length, 0);
|
||||
|
||||
await model1.load();
|
||||
model1.textEditorModel!.setValue('foo');
|
||||
|
||||
dirty = accessor.workingCopyFileService.getDirty(model1.resource);
|
||||
assert.equal(dirty.length, 1);
|
||||
assert.equal(dirty[0], model1);
|
||||
|
||||
dirty = accessor.workingCopyFileService.getDirty(toResource.call(this, '/path'));
|
||||
assert.equal(dirty.length, 1);
|
||||
assert.equal(dirty[0], model1);
|
||||
|
||||
await model2.load();
|
||||
model2.textEditorModel!.setValue('bar');
|
||||
|
||||
dirty = accessor.workingCopyFileService.getDirty(toResource.call(this, '/path'));
|
||||
assert.equal(dirty.length, 2);
|
||||
|
||||
model1.dispose();
|
||||
model2.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user