Merge from vscode 777931080477e28b7c27e8f7d4b0d69897945946 (#9220)

This commit is contained in:
Anthony Dresser
2020-02-19 22:27:53 -08:00
committed by GitHub
parent ab6fb810f8
commit 0cec223301
115 changed files with 1431 additions and 1133 deletions

View File

@@ -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 = [];

View File

@@ -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';

View File

@@ -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;

View File

@@ -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,

View File

@@ -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

View File

@@ -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));
});
}

View File

@@ -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);
});
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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()));
}

View File

@@ -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> {

View File

@@ -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[] = [];

View File

@@ -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);
}
}
}

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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';

View File

@@ -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(

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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')
});

View File

@@ -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. */

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -143,3 +143,5 @@ export class ExtensionContainers extends Disposable {
}
}
}
export const TOGGLE_IGNORE_EXTENSION_ACTION_ID = 'workbench.extensions.action.toggleIgnoreExtension';

View File

@@ -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();
}

View File

@@ -16,10 +16,6 @@ export class RuntimeExtensionsInput extends EditorInput {
path: 'default'
});
constructor() {
super();
}
getTypeId(): string {
return RuntimeExtensionsInput.ID;
}

View File

@@ -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 } }));
}
}

View File

@@ -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());

View File

@@ -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);
}
}

View File

@@ -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[] = [];

View File

@@ -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);
}
};

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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 } });

View File

@@ -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

View File

@@ -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,

View File

@@ -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%;
}

View File

@@ -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);
}

View File

@@ -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
});
}

View File

@@ -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())
});
}

View File

@@ -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> {

View File

@@ -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();

View File

@@ -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);

View File

@@ -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 = {

View File

@@ -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);

View File

@@ -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)),
});
}
}

View File

@@ -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,

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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> {

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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());

View File

@@ -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);

View File

@@ -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

View File

@@ -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)(() => {

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();
});
});