Archived
Merge from vscode 777931080477e28b7c27e8f7d4b0d69897945946 (#9220)
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
@font-face {
|
||||
font-family: "codicon";
|
||||
src: url("./codicon.ttf?d0510f6ecacbb2788db2b3162273a3d8") format("truetype");
|
||||
src: url("./codicon.ttf?279add2ec8b3d516ca20a123230cbf9f") format("truetype");
|
||||
}
|
||||
|
||||
.codicon[class*='codicon-'] {
|
||||
@@ -303,6 +303,7 @@
|
||||
.codicon-paintcan:before { content: "\eb2a" }
|
||||
.codicon-pin:before { content: "\eb2b" }
|
||||
.codicon-play:before { content: "\eb2c" }
|
||||
.codicon-run:before { content: "\eb2c" }
|
||||
.codicon-plug:before { content: "\eb2d" }
|
||||
.codicon-preserve-case:before { content: "\eb2e" }
|
||||
.codicon-preview:before { content: "\eb2f" }
|
||||
@@ -413,5 +414,6 @@
|
||||
.codicon-feedback:before { content: "\eb96" }
|
||||
.codicon-group-by-ref-type:before { content: "\eb97" }
|
||||
.codicon-ungroup-by-ref-type:before { content: "\eb98" }
|
||||
.codicon-debug-alt-2:before { content: "\f101" }
|
||||
.codicon-debug-alt:before { content: "\f102" }
|
||||
.codicon-bell-dot:before { content: "\f101" }
|
||||
.codicon-debug-alt-2:before { content: "\f102" }
|
||||
.codicon-debug-alt:before { content: "\f103" }
|
||||
|
||||
Binary file not shown.
@@ -52,6 +52,7 @@ export interface IListViewOptions<T> {
|
||||
readonly useShadows?: boolean;
|
||||
readonly verticalScrollMode?: ScrollbarVisibility;
|
||||
readonly setRowLineHeight?: boolean;
|
||||
readonly setRowHeight?: boolean;
|
||||
readonly supportDynamicHeights?: boolean;
|
||||
readonly mouseSupport?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
@@ -63,6 +64,7 @@ const DefaultOptions = {
|
||||
useShadows: true,
|
||||
verticalScrollMode: ScrollbarVisibility.Auto,
|
||||
setRowLineHeight: true,
|
||||
setRowHeight: true,
|
||||
supportDynamicHeights: false,
|
||||
dnd: {
|
||||
getDragElements<T>(e: T) { return [e]; },
|
||||
@@ -174,6 +176,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
private dragOverAnimationStopDisposable: IDisposable = Disposable.None;
|
||||
private dragOverMouseY: number = 0;
|
||||
private setRowLineHeight: boolean;
|
||||
private setRowHeight: boolean;
|
||||
private supportDynamicHeights: boolean;
|
||||
private horizontalScrolling: boolean;
|
||||
private additionalScrollHeight: number;
|
||||
@@ -262,6 +265,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
domEvent(window, 'dragend')(this.onDragEnd, this, this.disposables);
|
||||
|
||||
this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight);
|
||||
this.setRowHeight = getOrDefault(options, o => o.setRowHeight, DefaultOptions.setRowHeight);
|
||||
this.supportDynamicHeights = getOrDefault(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights);
|
||||
this.dnd = getOrDefault<IListViewOptions<T>, IListViewDragAndDrop<T>>(options, o => o.dnd, DefaultOptions.dnd);
|
||||
|
||||
@@ -614,7 +618,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
private updateItemInDOM(item: IItem<T>, index: number): void {
|
||||
item.row!.domNode!.style.top = `${this.elementTop(index)}px`;
|
||||
item.row!.domNode!.style.height = `${item.size}px`;
|
||||
|
||||
if (this.setRowHeight) {
|
||||
item.row!.domNode!.style.height = `${item.size}px`;
|
||||
}
|
||||
|
||||
if (this.setRowLineHeight) {
|
||||
item.row!.domNode!.style.lineHeight = `${item.size}px`;
|
||||
|
||||
@@ -847,6 +847,7 @@ export interface IListOptions<T> {
|
||||
readonly useShadows?: boolean;
|
||||
readonly verticalScrollMode?: ScrollbarVisibility;
|
||||
readonly setRowLineHeight?: boolean;
|
||||
readonly setRowHeight?: boolean;
|
||||
readonly supportDynamicHeights?: boolean;
|
||||
readonly mouseSupport?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
|
||||
@@ -453,8 +453,7 @@ export class QuickInputList {
|
||||
if ((what === 'Previous' || what === 'PreviousPage') && this.list.getFocus()[0] === 0) {
|
||||
what = 'Last';
|
||||
}
|
||||
|
||||
(this.list as any)['focus' + what]();
|
||||
this.list['focus' + what as 'focusFirst' | 'focusLast' | 'focusNext' | 'focusPrevious' | 'focusNextPage' | 'focusPreviousPage']();
|
||||
this.list.reveal(this.list.getFocus()[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -677,8 +677,8 @@ export class CodeApplication extends Disposable {
|
||||
const noRecentEntry = args['skip-add-to-recently-opened'] === true;
|
||||
const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;
|
||||
|
||||
// new window if "-n" was used without paths
|
||||
if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
|
||||
// new window if "-n" or "--remote" was used without paths
|
||||
if ((args['new-window'] || args.remote) && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
|
||||
return windowsMainService.open({
|
||||
context,
|
||||
cli: args,
|
||||
|
||||
@@ -86,7 +86,7 @@ class VisualEditorState {
|
||||
|
||||
constructor(
|
||||
private _contextMenuService: IContextMenuService,
|
||||
private _clipboardService: IClipboardService | null
|
||||
private _clipboardService: IClipboardService
|
||||
) {
|
||||
this._zones = [];
|
||||
this.inlineDiffMargins = [];
|
||||
@@ -136,7 +136,7 @@ class VisualEditorState {
|
||||
this._zones.push(zoneId);
|
||||
this._zonesMap[String(zoneId)] = true;
|
||||
|
||||
if (newDecorations.zones[i].diff && viewZone.marginDomNode && this._clipboardService) {
|
||||
if (newDecorations.zones[i].diff && viewZone.marginDomNode) {
|
||||
viewZone.suppressMouseDown = false;
|
||||
this.inlineDiffMargins.push(new InlineDiffMargin(zoneId, viewZone.marginDomNode, editor, newDecorations.zones[i].diff!, this._contextMenuService, this._clipboardService));
|
||||
}
|
||||
@@ -223,7 +223,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
|
||||
constructor(
|
||||
domElement: HTMLElement,
|
||||
options: IDiffEditorOptions,
|
||||
clipboardService: IClipboardService | null,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@IEditorWorkerService editorWorkerService: IEditorWorkerService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
|
||||
@@ -12,10 +12,10 @@ import { DeleteOperations } from 'vs/editor/common/controller/cursorDeleteOperat
|
||||
import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { TypeOperations, TypeWithAutoClosingCommand } from 'vs/editor/common/controller/cursorTypeOperations';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer } from 'vs/editor/common/model';
|
||||
import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model';
|
||||
import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
|
||||
@@ -903,8 +903,8 @@ class CommandExecutor {
|
||||
if (commandsData.hadTrackedEditOperation && filteredOperations.length > 0) {
|
||||
filteredOperations[0]._isTracked = true;
|
||||
}
|
||||
let selectionsAfter = ctx.model.pushEditOperations(ctx.selectionsBefore, filteredOperations, (inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] => {
|
||||
let groupedInverseEditOperations: IIdentifiedSingleEditOperation[][] = [];
|
||||
let selectionsAfter = ctx.model.pushEditOperations(ctx.selectionsBefore, filteredOperations, (inverseEditOperations: IValidEditOperation[]): Selection[] => {
|
||||
let groupedInverseEditOperations: IValidEditOperation[][] = [];
|
||||
for (let i = 0; i < ctx.selectionsBefore.length; i++) {
|
||||
groupedInverseEditOperations[i] = [];
|
||||
}
|
||||
@@ -915,7 +915,7 @@ class CommandExecutor {
|
||||
}
|
||||
groupedInverseEditOperations[op.identifier.major].push(op);
|
||||
}
|
||||
const minorBasedSorter = (a: IIdentifiedSingleEditOperation, b: IIdentifiedSingleEditOperation) => {
|
||||
const minorBasedSorter = (a: IValidEditOperation, b: IValidEditOperation) => {
|
||||
return a.identifier!.minor - b.identifier!.minor;
|
||||
};
|
||||
let cursorSelections: Selection[] = [];
|
||||
@@ -1000,8 +1000,8 @@ class CommandExecutor {
|
||||
let operations: IIdentifiedSingleEditOperation[] = [];
|
||||
let operationMinor = 0;
|
||||
|
||||
const addEditOperation = (selection: Range, text: string | null, forceMoveMarkers: boolean = false) => {
|
||||
if (selection.isEmpty() && text === '') {
|
||||
const addEditOperation = (range: IRange, text: string | null, forceMoveMarkers: boolean = false) => {
|
||||
if (Range.isEmpty(range) && text === '') {
|
||||
// This command wants to add a no-op => no thank you
|
||||
return;
|
||||
}
|
||||
@@ -1010,7 +1010,7 @@ class CommandExecutor {
|
||||
major: majorIdentifier,
|
||||
minor: operationMinor++
|
||||
},
|
||||
range: selection,
|
||||
range: range,
|
||||
text: text,
|
||||
forceMoveMarkers: forceMoveMarkers,
|
||||
isAutoWhitespaceEdit: command.insertsAutoWhitespace
|
||||
@@ -1018,12 +1018,13 @@ class CommandExecutor {
|
||||
};
|
||||
|
||||
let hadTrackedEditOperation = false;
|
||||
const addTrackedEditOperation = (selection: Range, text: string | null, forceMoveMarkers?: boolean) => {
|
||||
const addTrackedEditOperation = (selection: IRange, text: string | null, forceMoveMarkers?: boolean) => {
|
||||
hadTrackedEditOperation = true;
|
||||
addEditOperation(selection, text, forceMoveMarkers);
|
||||
};
|
||||
|
||||
const trackSelection = (selection: Selection, trackPreviousOnEmpty?: boolean) => {
|
||||
const trackSelection = (_selection: ISelection, trackPreviousOnEmpty?: boolean) => {
|
||||
const selection = Selection.liftSelection(_selection);
|
||||
let stickiness: TrackedRangeStickiness;
|
||||
if (selection.isEmpty()) {
|
||||
if (typeof trackPreviousOnEmpty === 'boolean') {
|
||||
@@ -1093,7 +1094,7 @@ class CommandExecutor {
|
||||
const previousOp = operations[i - 1];
|
||||
const currentOp = operations[i];
|
||||
|
||||
if (previousOp.range.getStartPosition().isBefore(currentOp.range.getEndPosition())) {
|
||||
if (Range.getStartPosition(previousOp.range).isBefore(Range.getEndPosition(currentOp.range))) {
|
||||
|
||||
let loserMajor: number;
|
||||
|
||||
|
||||
@@ -264,14 +264,28 @@ export class Range {
|
||||
* Return the end position (which will be after or equal to the start position)
|
||||
*/
|
||||
public getEndPosition(): Position {
|
||||
return new Position(this.endLineNumber, this.endColumn);
|
||||
return Range.getEndPosition(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the end position (which will be after or equal to the start position)
|
||||
*/
|
||||
public static getEndPosition(range: IRange): Position {
|
||||
return new Position(range.endLineNumber, range.endColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the start position (which will be before or equal to the end position)
|
||||
*/
|
||||
public getStartPosition(): Position {
|
||||
return new Position(this.startLineNumber, this.startColumn);
|
||||
return Range.getStartPosition(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the start position (which will be before or equal to the end position)
|
||||
*/
|
||||
public static getStartPosition(range: IRange): Position {
|
||||
return new Position(range.startLineNumber, range.startColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ConfigurationChangedEvent, IComputedEditorOptions, IEditorOptions } fro
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
||||
import { IIdentifiedSingleEditOperation, IModelDecorationsChangeAccessor, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { IModelDecorationsChangeAccessor, ITextModel, OverviewRulerLane, TrackedRangeStickiness, IValidEditOperation } from 'vs/editor/common/model';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,7 @@ export interface IEditOperationBuilder {
|
||||
* @param range The range to replace (delete). May be empty to represent a simple insert.
|
||||
* @param text The text to replace with. May be null to represent a simple delete.
|
||||
*/
|
||||
addEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void;
|
||||
addEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void;
|
||||
|
||||
/**
|
||||
* Add a new edit operation (a replace operation).
|
||||
@@ -30,7 +30,7 @@ export interface IEditOperationBuilder {
|
||||
* @param range The range to replace (delete). May be empty to represent a simple insert.
|
||||
* @param text The text to replace with. May be null to represent a simple delete.
|
||||
*/
|
||||
addTrackedEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void;
|
||||
addTrackedEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void;
|
||||
|
||||
/**
|
||||
* Track `selection` when applying edit operations.
|
||||
@@ -51,7 +51,7 @@ export interface ICursorStateComputerData {
|
||||
/**
|
||||
* Get the inverse edit operations of the added edit operations.
|
||||
*/
|
||||
getInverseEditOperations(): IIdentifiedSingleEditOperation[];
|
||||
getInverseEditOperations(): IValidEditOperation[];
|
||||
/**
|
||||
* Get a previously tracked selection.
|
||||
* @param id The unique identifier returned by `trackSelection`.
|
||||
|
||||
@@ -335,7 +335,7 @@ export interface IIdentifiedSingleEditOperation {
|
||||
/**
|
||||
* The range to replace. This can be empty to emulate a simple insert.
|
||||
*/
|
||||
range: Range;
|
||||
range: IRange;
|
||||
/**
|
||||
* The text to replace with. This can be null to emulate a simple delete.
|
||||
*/
|
||||
@@ -358,6 +358,27 @@ export interface IIdentifiedSingleEditOperation {
|
||||
_isTracked?: boolean;
|
||||
}
|
||||
|
||||
export interface IValidEditOperation {
|
||||
/**
|
||||
* An identifier associated with this single edit operation.
|
||||
* @internal
|
||||
*/
|
||||
identifier: ISingleEditOperationIdentifier | null;
|
||||
/**
|
||||
* The range to replace. This can be empty to emulate a simple insert.
|
||||
*/
|
||||
range: Range;
|
||||
/**
|
||||
* The text to replace with. This can be null to emulate a simple delete.
|
||||
*/
|
||||
text: string | null;
|
||||
/**
|
||||
* This indicates that this operation has "insert" semantics.
|
||||
* i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved.
|
||||
*/
|
||||
forceMoveMarkers: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback that can compute the cursor state after applying a series of edit operations.
|
||||
*/
|
||||
@@ -365,7 +386,7 @@ export interface ICursorStateComputer {
|
||||
/**
|
||||
* A callback that can compute the resulting cursors state after some edit operations have been executed.
|
||||
*/
|
||||
(inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] | null;
|
||||
(inverseEditOperations: IValidEditOperation[]): Selection[] | null;
|
||||
}
|
||||
|
||||
export class TextModelResolvedOptions {
|
||||
@@ -1063,7 +1084,7 @@ export interface ITextModel {
|
||||
* @param operations The edit operations.
|
||||
* @return The inverse edit operations, that, when applied, will bring the model back to the previous state.
|
||||
*/
|
||||
applyEdits(operations: IIdentifiedSingleEditOperation[]): IIdentifiedSingleEditOperation[];
|
||||
applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[];
|
||||
|
||||
/**
|
||||
* Change the end of line sequence without recording in the undo stack.
|
||||
@@ -1206,6 +1227,20 @@ export const enum ModelConstants {
|
||||
FIRST_LINE_DETECTION_LENGTH_LIMIT = 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class ValidAnnotatedEditOperation implements IIdentifiedSingleEditOperation {
|
||||
constructor(
|
||||
public readonly identifier: ISingleEditOperationIdentifier | null,
|
||||
public readonly range: Range,
|
||||
public readonly text: string | null,
|
||||
public readonly forceMoveMarkers: boolean,
|
||||
public readonly isAutoWhitespaceEdit: boolean,
|
||||
public readonly _isTracked: boolean,
|
||||
) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -1234,7 +1269,7 @@ export interface ITextBuffer {
|
||||
getLineLastNonWhitespaceColumn(lineNumber: number): number;
|
||||
|
||||
setEOL(newEOL: '\r\n' | '\n'): void;
|
||||
applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult;
|
||||
applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult;
|
||||
findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[];
|
||||
}
|
||||
|
||||
@@ -1244,7 +1279,7 @@ export interface ITextBuffer {
|
||||
export class ApplyEditsResult {
|
||||
|
||||
constructor(
|
||||
public readonly reverseEdits: IIdentifiedSingleEditOperation[],
|
||||
public readonly reverseEdits: IValidEditOperation[],
|
||||
public readonly changes: IInternalModelContentChange[],
|
||||
public readonly trimAutoWhitespaceLineNumbers: number[] | null
|
||||
) { }
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
|
||||
interface IEditOperation {
|
||||
operations: IIdentifiedSingleEditOperation[];
|
||||
operations: IValidEditOperation[];
|
||||
}
|
||||
|
||||
interface IStackElement {
|
||||
@@ -174,7 +174,7 @@ export class EditStack {
|
||||
return stackElement!.afterCursorState;
|
||||
}
|
||||
|
||||
private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] | null {
|
||||
private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IValidEditOperation[]): Selection[] | null {
|
||||
try {
|
||||
return cursorStateComputer ? cursorStateComputer(inverseEditOperations) : null;
|
||||
} catch (e) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ApplyEditsResult, EndOfLinePreference, FindMatch, IIdentifiedSingleEditOperation, IInternalModelContentChange, ISingleEditOperationIdentifier, ITextBuffer, ITextSnapshot } from 'vs/editor/common/model';
|
||||
import { ApplyEditsResult, EndOfLinePreference, FindMatch, IInternalModelContentChange, ISingleEditOperationIdentifier, ITextBuffer, ITextSnapshot, ValidAnnotatedEditOperation, IValidEditOperation } from 'vs/editor/common/model';
|
||||
import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase';
|
||||
import { SearchData } from 'vs/editor/common/model/textModelSearch';
|
||||
|
||||
@@ -21,7 +21,7 @@ export interface IValidatedEditOperation {
|
||||
isAutoWhitespaceEdit: boolean;
|
||||
}
|
||||
|
||||
export interface IReverseSingleEditOperation extends IIdentifiedSingleEditOperation {
|
||||
export interface IReverseSingleEditOperation extends IValidEditOperation {
|
||||
sortIndex: number;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
this._pieceTree.setEOL(newEOL);
|
||||
}
|
||||
|
||||
public applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult {
|
||||
public applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult {
|
||||
let mightContainRTL = this._mightContainRTL;
|
||||
let mightContainNonBasicASCII = this._mightContainNonBasicASCII;
|
||||
let canReduceOperations = true;
|
||||
|
||||
@@ -1154,18 +1154,40 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
}
|
||||
}
|
||||
|
||||
private _validateEditOperation(rawOperation: model.IIdentifiedSingleEditOperation): model.ValidAnnotatedEditOperation {
|
||||
if (rawOperation instanceof model.ValidAnnotatedEditOperation) {
|
||||
return rawOperation;
|
||||
}
|
||||
return new model.ValidAnnotatedEditOperation(
|
||||
rawOperation.identifier || null,
|
||||
this.validateRange(rawOperation.range),
|
||||
rawOperation.text,
|
||||
rawOperation.forceMoveMarkers || false,
|
||||
rawOperation.isAutoWhitespaceEdit || false,
|
||||
rawOperation._isTracked || false
|
||||
);
|
||||
}
|
||||
|
||||
private _validateEditOperations(rawOperations: model.IIdentifiedSingleEditOperation[]): model.ValidAnnotatedEditOperation[] {
|
||||
const result: model.ValidAnnotatedEditOperation[] = [];
|
||||
for (let i = 0, len = rawOperations.length; i < len; i++) {
|
||||
result[i] = this._validateEditOperation(rawOperations[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public pushEditOperations(beforeCursorState: Selection[], editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null {
|
||||
try {
|
||||
this._onDidChangeDecorations.beginDeferredEmit();
|
||||
this._eventEmitter.beginDeferredEmit();
|
||||
return this._pushEditOperations(beforeCursorState, editOperations, cursorStateComputer);
|
||||
return this._pushEditOperations(beforeCursorState, this._validateEditOperations(editOperations), cursorStateComputer);
|
||||
} finally {
|
||||
this._eventEmitter.endDeferredEmit();
|
||||
this._onDidChangeDecorations.endDeferredEmit();
|
||||
}
|
||||
}
|
||||
|
||||
private _pushEditOperations(beforeCursorState: Selection[], editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null {
|
||||
private _pushEditOperations(beforeCursorState: Selection[], editOperations: model.ValidAnnotatedEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null {
|
||||
if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) {
|
||||
// Go through each saved line number and insert a trim whitespace edit
|
||||
// if it is safe to do so (no conflicts with other edits).
|
||||
@@ -1238,10 +1260,8 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
}
|
||||
|
||||
if (allowTrimLine) {
|
||||
editOperations.push({
|
||||
range: new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn),
|
||||
text: null
|
||||
});
|
||||
const trimRange = new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn);
|
||||
editOperations.push(new model.ValidAnnotatedEditOperation(null, trimRange, null, false, false, false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1252,21 +1272,18 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer);
|
||||
}
|
||||
|
||||
public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IIdentifiedSingleEditOperation[] {
|
||||
public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IValidEditOperation[] {
|
||||
try {
|
||||
this._onDidChangeDecorations.beginDeferredEmit();
|
||||
this._eventEmitter.beginDeferredEmit();
|
||||
return this._applyEdits(rawOperations);
|
||||
return this._applyEdits(this._validateEditOperations(rawOperations));
|
||||
} finally {
|
||||
this._eventEmitter.endDeferredEmit();
|
||||
this._onDidChangeDecorations.endDeferredEmit();
|
||||
}
|
||||
}
|
||||
|
||||
private _applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IIdentifiedSingleEditOperation[] {
|
||||
for (let i = 0, len = rawOperations.length; i < len; i++) {
|
||||
rawOperations[i].range = this.validateRange(rawOperations[i].range);
|
||||
}
|
||||
private _applyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] {
|
||||
|
||||
const oldLineCount = this._buffer.getLineCount();
|
||||
const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace);
|
||||
|
||||
@@ -1324,7 +1324,7 @@ export interface WorkspaceEditMetadata {
|
||||
needsConfirmation: boolean;
|
||||
label: string;
|
||||
description?: string;
|
||||
iconPath?: { id: string } | { light: URI, dark: URI };
|
||||
iconPath?: { id: string } | URI | { light: URI, dark: URI };
|
||||
}
|
||||
|
||||
export interface WorkspaceFileEditOptions {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model';
|
||||
import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions, IValidEditOperation } from 'vs/editor/common/model';
|
||||
import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel';
|
||||
import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
|
||||
import { LanguageIdentifier, DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits, TokenMetadata, FontStyle, MetadataConsts } from 'vs/editor/common/modes';
|
||||
@@ -305,7 +305,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
model.pushEditOperations(
|
||||
[],
|
||||
ModelServiceImpl._computeEdits(model, textBuffer),
|
||||
(inverseEditOperations: IIdentifiedSingleEditOperation[]) => []
|
||||
(inverseEditOperations: IValidEditOperation[]) => []
|
||||
);
|
||||
model.pushStackElement();
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ICommand, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon';
|
||||
import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model';
|
||||
import { ITextModel, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
|
||||
export class BlockCommentCommand implements ICommand {
|
||||
|
||||
@@ -205,7 +205,7 @@ export class LineCommentCommand implements ICommand {
|
||||
|
||||
for (let i = 0, len = ops.length; i < len; i++) {
|
||||
builder.addEditOperation(ops[i].range, ops[i].text);
|
||||
if (ops[i].range.isEmpty() && ops[i].range.getStartPosition().equals(cursorPosition)) {
|
||||
if (Range.isEmpty(ops[i].range) && Range.getStartPosition(ops[i].range).equals(cursorPosition)) {
|
||||
const lineContent = model.getLineContent(cursorPosition.lineNumber);
|
||||
if (lineContent.length + 1 === cursorPosition.column) {
|
||||
this._deltaColumn = (ops[i].text || '').length;
|
||||
|
||||
@@ -25,6 +25,10 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
|
||||
function getHoverMessage(link: Link, useMetaKey: boolean): MarkdownString {
|
||||
const executeCmd = link.url && /^command:/i.test(link.url.toString());
|
||||
@@ -291,7 +295,29 @@ class LinkDetector implements IEditorContribution {
|
||||
const { link } = occurrence;
|
||||
|
||||
link.resolve(CancellationToken.None).then(uri => {
|
||||
// open the uri
|
||||
|
||||
// Support for relative file URIs of the shape file://./relativeFile.txt or file:///./relativeFile.txt
|
||||
if (typeof uri === 'string' && this.editor.hasModel()) {
|
||||
const modelUri = this.editor.getModel().uri;
|
||||
if (modelUri.scheme === Schemas.file && strings.startsWith(uri, 'file:')) {
|
||||
const parsedUri = URI.parse(uri);
|
||||
if (parsedUri.scheme === Schemas.file) {
|
||||
const fsPath = resources.originalFSPath(parsedUri);
|
||||
|
||||
let relativePath: string | null = null;
|
||||
if (strings.startsWith(fsPath, '/./')) {
|
||||
relativePath = `.${fsPath.substr(1)}`;
|
||||
} else if (strings.startsWith(fsPath, '//./')) {
|
||||
relativePath = `.${fsPath.substr(2)}`;
|
||||
}
|
||||
|
||||
if (relativePath) {
|
||||
uri = resources.joinPath(modelUri, relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.openerService.open(uri, { openToSide, fromUserGesture });
|
||||
|
||||
}, err => {
|
||||
|
||||
@@ -26,7 +26,7 @@ import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService
|
||||
import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { CommandsRegistry, ICommand, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel, IConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { Configuration, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
|
||||
import { Configuration, ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -448,10 +448,6 @@ export class SimpleConfigurationService implements IConfigurationService {
|
||||
this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel());
|
||||
}
|
||||
|
||||
private configuration(): Configuration {
|
||||
return this._configuration;
|
||||
}
|
||||
|
||||
getValue<T>(): T;
|
||||
getValue<T>(section: string): T;
|
||||
getValue<T>(overrides: IConfigurationOverrides): T;
|
||||
@@ -459,20 +455,43 @@ export class SimpleConfigurationService implements IConfigurationService {
|
||||
getValue(arg1?: any, arg2?: any): any {
|
||||
const section = typeof arg1 === 'string' ? arg1 : undefined;
|
||||
const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {};
|
||||
return this.configuration().getValue(section, overrides, undefined);
|
||||
return this._configuration.getValue(section, overrides, undefined);
|
||||
}
|
||||
|
||||
public updateValue(key: string, value: any, arg3?: any, arg4?: any): Promise<void> {
|
||||
this.configuration().updateValue(key, value);
|
||||
public updateValues(values: [string, any][]): Promise<void> {
|
||||
const previous = { data: this._configuration.toData() };
|
||||
|
||||
let changedKeys: string[] = [];
|
||||
|
||||
for (const entry of values) {
|
||||
const [key, value] = entry;
|
||||
if (this.getValue(key) === value) {
|
||||
continue;
|
||||
}
|
||||
this._configuration.updateValue(key, value);
|
||||
changedKeys.push(key);
|
||||
}
|
||||
|
||||
if (changedKeys.length > 0) {
|
||||
const configurationChangeEvent = new ConfigurationChangeEvent({ keys: changedKeys, overrides: [] }, previous, this._configuration);
|
||||
configurationChangeEvent.source = ConfigurationTarget.MEMORY;
|
||||
configurationChangeEvent.sourceConfig = null;
|
||||
this._onDidChangeConfiguration.fire(configurationChangeEvent);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public updateValue(key: string, value: any, arg3?: any, arg4?: any): Promise<void> {
|
||||
return this.updateValues([[key, value]]);
|
||||
}
|
||||
|
||||
public inspect<C>(key: string, options: IConfigurationOverrides = {}): IConfigurationValue<C> {
|
||||
return this.configuration().inspect<C>(key, options, undefined);
|
||||
return this._configuration.inspect<C>(key, options, undefined);
|
||||
}
|
||||
|
||||
public keys() {
|
||||
return this.configuration().keys(undefined);
|
||||
return this._configuration.keys(undefined);
|
||||
}
|
||||
|
||||
public reloadConfiguration(): Promise<void> {
|
||||
@@ -622,14 +641,18 @@ export function applyConfigurationValues(configurationService: IConfigurationSer
|
||||
if (!(configurationService instanceof SimpleConfigurationService)) {
|
||||
return;
|
||||
}
|
||||
let toUpdate: [string, any][] = [];
|
||||
Object.keys(source).forEach((key) => {
|
||||
if (isEditorConfigurationKey(key)) {
|
||||
configurationService.updateValue(`editor.${key}`, source[key]);
|
||||
toUpdate.push([`editor.${key}`, source[key]]);
|
||||
}
|
||||
if (isDiffEditor && isDiffEditorConfigurationKey(key)) {
|
||||
configurationService.updateValue(`diffEditor.${key}`, source[key]);
|
||||
toUpdate.push([`diffEditor.${key}`, source[key]]);
|
||||
}
|
||||
});
|
||||
if (toUpdate.length > 0) {
|
||||
configurationService.updateValues(toUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
export class SimpleBulkEditService implements IBulkEditService {
|
||||
|
||||
@@ -23,7 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService';
|
||||
import { IInstantiationService, optional, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
@@ -114,6 +114,11 @@ export interface IGlobalEditorOptions {
|
||||
* Defaults to true.
|
||||
*/
|
||||
wordBasedSuggestions?: boolean;
|
||||
/**
|
||||
* Controls whether the semanticHighlighting is shown for the languages that support it.
|
||||
* Defaults to true.
|
||||
*/
|
||||
'semanticHighlighting.enabled'?: boolean;
|
||||
/**
|
||||
* Keep peek editors open even when double clicking their content or when hitting `Escape`.
|
||||
* Defaults to false.
|
||||
@@ -443,7 +448,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IEditorProgressService editorProgressService: IEditorProgressService,
|
||||
@optional(IClipboardService) clipboardService: IClipboardService | null,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
) {
|
||||
applyConfigurationValues(configurationService, options, true);
|
||||
const themeDomRegistration = (<StandaloneThemeServiceImpl>themeService).registerEditorContainer(domElement);
|
||||
|
||||
@@ -39,6 +39,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { clearAllFontInfos } from 'vs/editor/browser/config/configuration';
|
||||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
|
||||
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||
|
||||
@@ -122,7 +123,7 @@ export function createDiffEditor(domElement: HTMLElement, options?: IDiffEditorC
|
||||
services.get(IConfigurationService),
|
||||
services.get(IContextMenuService),
|
||||
services.get(IEditorProgressService),
|
||||
null
|
||||
services.get(IClipboardService)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions';
|
||||
import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService';
|
||||
|
||||
export interface IEditorOverrideServices {
|
||||
[index: string]: any;
|
||||
@@ -204,6 +206,8 @@ export class DynamicStandaloneServices extends Disposable {
|
||||
|
||||
let contextViewService = ensure(IContextViewService, () => this._register(new ContextViewService(layoutService)));
|
||||
|
||||
ensure(IClipboardService, () => new BrowserClipboardService());
|
||||
|
||||
ensure(IContextMenuService, () => {
|
||||
const contextMenuService = new ContextMenuService(telemetryService, notificationService, contextViewService, keybindingService, themeService);
|
||||
contextMenuService.configure({ blockMouse: false }); // we do not want that in the standalone editor
|
||||
|
||||
@@ -2880,6 +2880,33 @@ suite('Editor Controller - Cursor Configuration', () => {
|
||||
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
test('issue #90973: Undo brings back model alternative version', () => {
|
||||
let model = createTextModel(
|
||||
[
|
||||
''
|
||||
].join('\n'),
|
||||
{
|
||||
insertSpaces: false,
|
||||
}
|
||||
);
|
||||
|
||||
withTestCodeEditor(null, { model: model }, (editor, cursor) => {
|
||||
const beforeVersion = model.getVersionId();
|
||||
const beforeAltVersion = model.getAlternativeVersionId();
|
||||
cursorCommand(cursor, H.Type, { text: 'Hello' }, 'keyboard');
|
||||
cursorCommand(cursor, H.Undo, {});
|
||||
const afterVersion = model.getVersionId();
|
||||
const afterAltVersion = model.getAlternativeVersionId();
|
||||
|
||||
assert.notEqual(beforeVersion, afterVersion);
|
||||
assert.equal(beforeAltVersion, afterAltVersion);
|
||||
});
|
||||
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
suite('Editor Controller - Indentation Rules', () => {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { Selection, ISelection } from 'vs/editor/common/core/selection';
|
||||
import { ICommand, Handler, IEditOperationBuilder } from 'vs/editor/common/editorCommon';
|
||||
import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
@@ -50,7 +50,7 @@ export function testCommand(
|
||||
export function getEditOperation(model: ITextModel, command: ICommand): IIdentifiedSingleEditOperation[] {
|
||||
let operations: IIdentifiedSingleEditOperation[] = [];
|
||||
let editOperationBuilder: IEditOperationBuilder = {
|
||||
addEditOperation: (range: Range, text: string, forceMoveMarkers: boolean = false) => {
|
||||
addEditOperation: (range: IRange, text: string, forceMoveMarkers: boolean = false) => {
|
||||
operations.push({
|
||||
range: range,
|
||||
text: text,
|
||||
@@ -58,7 +58,7 @@ export function getEditOperation(model: ITextModel, command: ICommand): IIdentif
|
||||
});
|
||||
},
|
||||
|
||||
addTrackedEditOperation: (range: Range, text: string, forceMoveMarkers: boolean = false) => {
|
||||
addTrackedEditOperation: (range: IRange, text: string, forceMoveMarkers: boolean = false) => {
|
||||
operations.push({
|
||||
range: range,
|
||||
text: text,
|
||||
@@ -67,7 +67,7 @@ export function getEditOperation(model: ITextModel, command: ICommand): IIdentif
|
||||
},
|
||||
|
||||
|
||||
trackSelection: (selection: Selection) => {
|
||||
trackSelection: (selection: ISelection) => {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { DefaultEndOfLine, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferBuilder } from 'vs/editor/common/model';
|
||||
import { DefaultEndOfLine, ITextBuffer, ITextBufferBuilder, ValidAnnotatedEditOperation } from 'vs/editor/common/model';
|
||||
|
||||
export function getRandomInt(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
@@ -31,7 +31,7 @@ export function getRandomString(minLength: number, maxLength: number): string {
|
||||
return r;
|
||||
}
|
||||
|
||||
export function generateRandomEdits(chunks: string[], editCnt: number): IIdentifiedSingleEditOperation[] {
|
||||
export function generateRandomEdits(chunks: string[], editCnt: number): ValidAnnotatedEditOperation[] {
|
||||
let lines: string[] = [];
|
||||
for (const chunk of chunks) {
|
||||
let newLines = chunk.split(/\r\n|\r|\n/);
|
||||
@@ -43,7 +43,7 @@ export function generateRandomEdits(chunks: string[], editCnt: number): IIdentif
|
||||
}
|
||||
}
|
||||
|
||||
let ops: IIdentifiedSingleEditOperation[] = [];
|
||||
let ops: ValidAnnotatedEditOperation[] = [];
|
||||
|
||||
for (let i = 0; i < editCnt; i++) {
|
||||
let line = getRandomInt(1, lines.length);
|
||||
@@ -54,17 +54,14 @@ export function generateRandomEdits(chunks: string[], editCnt: number): IIdentif
|
||||
text = getRandomString(5, 10);
|
||||
}
|
||||
|
||||
ops.push({
|
||||
text: text,
|
||||
range: new Range(line, startColumn, line, endColumn)
|
||||
});
|
||||
ops.push(new ValidAnnotatedEditOperation(null, new Range(line, startColumn, line, endColumn), text, false, false, false));
|
||||
lines[line - 1] = lines[line - 1].substring(0, startColumn - 1) + text + lines[line - 1].substring(endColumn - 1);
|
||||
}
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
export function generateSequentialInserts(chunks: string[], editCnt: number): IIdentifiedSingleEditOperation[] {
|
||||
export function generateSequentialInserts(chunks: string[], editCnt: number): ValidAnnotatedEditOperation[] {
|
||||
let lines: string[] = [];
|
||||
for (const chunk of chunks) {
|
||||
let newLines = chunk.split(/\r\n|\r|\n/);
|
||||
@@ -76,7 +73,7 @@ export function generateSequentialInserts(chunks: string[], editCnt: number): II
|
||||
}
|
||||
}
|
||||
|
||||
let ops: IIdentifiedSingleEditOperation[] = [];
|
||||
let ops: ValidAnnotatedEditOperation[] = [];
|
||||
|
||||
for (let i = 0; i < editCnt; i++) {
|
||||
let line = lines.length;
|
||||
@@ -90,16 +87,13 @@ export function generateSequentialInserts(chunks: string[], editCnt: number): II
|
||||
lines[line - 1] += text;
|
||||
}
|
||||
|
||||
ops.push({
|
||||
text: text,
|
||||
range: new Range(line, column, line, column)
|
||||
});
|
||||
ops.push(new ValidAnnotatedEditOperation(null, new Range(line, column, line, column), text, false, false, false));
|
||||
}
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
export function generateRandomReplaces(chunks: string[], editCnt: number, searchStringLen: number, replaceStringLen: number): IIdentifiedSingleEditOperation[] {
|
||||
export function generateRandomReplaces(chunks: string[], editCnt: number, searchStringLen: number, replaceStringLen: number): ValidAnnotatedEditOperation[] {
|
||||
let lines: string[] = [];
|
||||
for (const chunk of chunks) {
|
||||
let newLines = chunk.split(/\r\n|\r|\n/);
|
||||
@@ -111,7 +105,7 @@ export function generateRandomReplaces(chunks: string[], editCnt: number, search
|
||||
}
|
||||
}
|
||||
|
||||
let ops: IIdentifiedSingleEditOperation[] = [];
|
||||
let ops: ValidAnnotatedEditOperation[] = [];
|
||||
let chunkSize = Math.max(1, Math.floor(lines.length / editCnt));
|
||||
let chunkCnt = Math.floor(lines.length / chunkSize);
|
||||
let replaceString = getRandomString(replaceStringLen, replaceStringLen);
|
||||
@@ -125,10 +119,7 @@ export function generateRandomReplaces(chunks: string[], editCnt: number, search
|
||||
let startColumn = getRandomInt(1, maxColumn);
|
||||
let endColumn = Math.min(maxColumn, startColumn + searchStringLen);
|
||||
|
||||
ops.push({
|
||||
text: replaceString,
|
||||
range: new Range(line, startColumn, line, endColumn)
|
||||
});
|
||||
ops.push(new ValidAnnotatedEditOperation(null, new Range(line, startColumn, line, endColumn), replaceString, false, false, false));
|
||||
previousChunksLength = endLine;
|
||||
}
|
||||
|
||||
@@ -166,4 +157,4 @@ export function generateRandomChunkWithLF(minLength: number, maxLength: number):
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+36
-7
@@ -638,10 +638,18 @@ declare namespace monaco {
|
||||
* Return the end position (which will be after or equal to the start position)
|
||||
*/
|
||||
getEndPosition(): Position;
|
||||
/**
|
||||
* Return the end position (which will be after or equal to the start position)
|
||||
*/
|
||||
static getEndPosition(range: IRange): Position;
|
||||
/**
|
||||
* Return the start position (which will be before or equal to the end position)
|
||||
*/
|
||||
getStartPosition(): Position;
|
||||
/**
|
||||
* Return the start position (which will be before or equal to the end position)
|
||||
*/
|
||||
static getStartPosition(range: IRange): Position;
|
||||
/**
|
||||
* Transform to a user presentable string representation.
|
||||
*/
|
||||
@@ -1098,6 +1106,11 @@ declare namespace monaco.editor {
|
||||
* Defaults to true.
|
||||
*/
|
||||
wordBasedSuggestions?: boolean;
|
||||
/**
|
||||
* Controls whether the semanticHighlighting is shown for the languages that support it.
|
||||
* Defaults to true.
|
||||
*/
|
||||
'semanticHighlighting.enabled'?: boolean;
|
||||
/**
|
||||
* Keep peek editors open even when double clicking their content or when hitting `Escape`.
|
||||
* Defaults to false.
|
||||
@@ -1508,7 +1521,7 @@ declare namespace monaco.editor {
|
||||
/**
|
||||
* The range to replace. This can be empty to emulate a simple insert.
|
||||
*/
|
||||
range: Range;
|
||||
range: IRange;
|
||||
/**
|
||||
* The text to replace with. This can be null to emulate a simple delete.
|
||||
*/
|
||||
@@ -1520,6 +1533,22 @@ declare namespace monaco.editor {
|
||||
forceMoveMarkers?: boolean;
|
||||
}
|
||||
|
||||
export interface IValidEditOperation {
|
||||
/**
|
||||
* The range to replace. This can be empty to emulate a simple insert.
|
||||
*/
|
||||
range: Range;
|
||||
/**
|
||||
* The text to replace with. This can be null to emulate a simple delete.
|
||||
*/
|
||||
text: string | null;
|
||||
/**
|
||||
* This indicates that this operation has "insert" semantics.
|
||||
* i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved.
|
||||
*/
|
||||
forceMoveMarkers: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback that can compute the cursor state after applying a series of edit operations.
|
||||
*/
|
||||
@@ -1527,7 +1556,7 @@ declare namespace monaco.editor {
|
||||
/**
|
||||
* A callback that can compute the resulting cursors state after some edit operations have been executed.
|
||||
*/
|
||||
(inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] | null;
|
||||
(inverseEditOperations: IValidEditOperation[]): Selection[] | null;
|
||||
}
|
||||
|
||||
export class TextModelResolvedOptions {
|
||||
@@ -1867,7 +1896,7 @@ declare namespace monaco.editor {
|
||||
* @param operations The edit operations.
|
||||
* @return The inverse edit operations, that, when applied, will bring the model back to the previous state.
|
||||
*/
|
||||
applyEdits(operations: IIdentifiedSingleEditOperation[]): IIdentifiedSingleEditOperation[];
|
||||
applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[];
|
||||
/**
|
||||
* Change the end of line sequence without recording in the undo stack.
|
||||
* This can have dire consequences on the undo stack! See @pushEOL for the preferred way.
|
||||
@@ -1919,14 +1948,14 @@ declare namespace monaco.editor {
|
||||
* @param range The range to replace (delete). May be empty to represent a simple insert.
|
||||
* @param text The text to replace with. May be null to represent a simple delete.
|
||||
*/
|
||||
addEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void;
|
||||
addEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void;
|
||||
/**
|
||||
* Add a new edit operation (a replace operation).
|
||||
* The inverse edits will be accessible in `ICursorStateComputerData.getInverseEditOperations()`
|
||||
* @param range The range to replace (delete). May be empty to represent a simple insert.
|
||||
* @param text The text to replace with. May be null to represent a simple delete.
|
||||
*/
|
||||
addTrackedEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void;
|
||||
addTrackedEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void;
|
||||
/**
|
||||
* Track `selection` when applying edit operations.
|
||||
* A best effort will be made to not grow/expand the selection.
|
||||
@@ -1946,7 +1975,7 @@ declare namespace monaco.editor {
|
||||
/**
|
||||
* Get the inverse edit operations of the added edit operations.
|
||||
*/
|
||||
getInverseEditOperations(): IIdentifiedSingleEditOperation[];
|
||||
getInverseEditOperations(): IValidEditOperation[];
|
||||
/**
|
||||
* Get a previously tracked selection.
|
||||
* @param id The unique identifier returned by `trackSelection`.
|
||||
@@ -6043,7 +6072,7 @@ declare namespace monaco.languages {
|
||||
description?: string;
|
||||
iconPath?: {
|
||||
id: string;
|
||||
} | {
|
||||
} | Uri | {
|
||||
light: Uri;
|
||||
dark: Uri;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@ export interface IProductConfiguration {
|
||||
readonly extensionTips?: { [id: string]: string; };
|
||||
readonly extensionImportantTips?: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; };
|
||||
readonly exeBasedExtensionTips?: { [id: string]: IExeBasedExtensionTip; };
|
||||
readonly remoteExtensionTips?: { [remoteName: string]: IRemoteExtensionTip; };
|
||||
readonly extensionKeywords?: { [extension: string]: readonly string[]; };
|
||||
readonly keymapExtensionTips?: readonly string[];
|
||||
|
||||
@@ -118,6 +119,11 @@ export interface IExeBasedExtensionTip {
|
||||
exeFriendlyName?: string;
|
||||
}
|
||||
|
||||
export interface IRemoteExtensionTip {
|
||||
friendlyName: string;
|
||||
extensionId: string;
|
||||
}
|
||||
|
||||
export interface ISurveyData {
|
||||
surveyId: string;
|
||||
surveyUrl: string;
|
||||
|
||||
@@ -12,6 +12,9 @@ export function getRemoteAuthority(uri: URI): string | undefined {
|
||||
return uri.scheme === REMOTE_HOST_SCHEME ? uri.authority : undefined;
|
||||
}
|
||||
|
||||
export function getRemoteName(authority: string): string;
|
||||
export function getRemoteName(authority: undefined): undefined;
|
||||
export function getRemoteName(authority: string | undefined): string | undefined;
|
||||
export function getRemoteName(authority: string | undefined): string | undefined {
|
||||
if (!authority) {
|
||||
return undefined;
|
||||
@@ -22,4 +25,4 @@ export function getRemoteName(authority: string | undefined): string | undefined
|
||||
return authority;
|
||||
}
|
||||
return authority.substr(0, pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,30 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ParseError, parse } from 'vs/base/common/json';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
type SyncConflictsClassification = {
|
||||
type SyncSourceClassification = {
|
||||
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
export interface IRemoteUserData {
|
||||
ref: string;
|
||||
syncData: ISyncData | null;
|
||||
}
|
||||
|
||||
export interface ISyncData {
|
||||
version: number;
|
||||
content: string;
|
||||
}
|
||||
|
||||
function isSyncData(thing: any): thing is ISyncData {
|
||||
return thing
|
||||
&& (thing.version && typeof thing.version === 'number')
|
||||
&& (thing.content && typeof thing.content === 'string')
|
||||
&& Object.keys(thing).length === 2;
|
||||
}
|
||||
|
||||
export abstract class AbstractSynchroniser extends Disposable {
|
||||
|
||||
protected readonly syncFolder: URI;
|
||||
@@ -58,11 +77,11 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
this._onDidChangStatus.fire(status);
|
||||
if (status === SyncStatus.HasConflicts) {
|
||||
// Log to telemetry when there is a sync conflict
|
||||
this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsDetected', { source: this.source });
|
||||
this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsDetected', { source: this.source });
|
||||
}
|
||||
if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) {
|
||||
// Log to telemetry when conflicts are resolved
|
||||
this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsResolved', { source: this.source });
|
||||
this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.source });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,6 +107,13 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = ref && lastSyncUserData && lastSyncUserData.ref === ref ? lastSyncUserData : await this.getRemoteUserData(lastSyncUserData);
|
||||
|
||||
if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) {
|
||||
// current version is not compatible with cloud version
|
||||
this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: this.source });
|
||||
throw new UserDataSyncError(localize('incompatible', "Cannot sync {0} as its version {1} is not compatible with cloud {2}", this.source, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.source);
|
||||
}
|
||||
|
||||
return this.doSync(remoteUserData, lastSyncUserData);
|
||||
}
|
||||
|
||||
@@ -98,8 +124,8 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
|
||||
async getRemoteContent(): Promise<string | null> {
|
||||
const lastSyncData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncData);
|
||||
return remoteUserData.content;
|
||||
const { syncData } = await this.getRemoteUserData(lastSyncData);
|
||||
return syncData ? syncData.content : null;
|
||||
}
|
||||
|
||||
async resetLocal(): Promise<void> {
|
||||
@@ -108,25 +134,56 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
protected async getLastSyncUserData<T extends IUserData>(): Promise<T | null> {
|
||||
protected async getLastSyncUserData<T extends IRemoteUserData>(): Promise<T | null> {
|
||||
try {
|
||||
const content = await this.fileService.readFile(this.lastSyncResource);
|
||||
return JSON.parse(content.value.toString());
|
||||
const parsed = JSON.parse(content.value.toString());
|
||||
let syncData: ISyncData = JSON.parse(parsed.content);
|
||||
|
||||
// Migration from old content to sync data
|
||||
if (!isSyncData(syncData)) {
|
||||
syncData = { version: this.version, content: parsed.content };
|
||||
}
|
||||
|
||||
return { ...parsed, ...{ syncData, content: undefined } };
|
||||
} catch (error) {
|
||||
return null;
|
||||
if (!(error instanceof FileOperationError && error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND)) {
|
||||
// log error always except when file does not exist
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected async updateLastSyncUserData<T extends IUserData>(lastSyncUserData: T): Promise<void> {
|
||||
protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary<any> = {}): Promise<void> {
|
||||
const lastSyncUserData: IUserData = { ref: lastSyncRemoteUserData.ref, content: JSON.stringify(lastSyncRemoteUserData.syncData), ...additionalProps };
|
||||
await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData)));
|
||||
}
|
||||
|
||||
protected async getRemoteUserData(lastSyncData: IUserData | null): Promise<IUserData> {
|
||||
return this.userDataSyncStoreService.read(this.resourceKey, lastSyncData, this.source);
|
||||
protected async getRemoteUserData(lastSyncData: IRemoteUserData | null): Promise<IRemoteUserData> {
|
||||
const lastSyncUserData: IUserData | null = lastSyncData ? { ref: lastSyncData.ref, content: lastSyncData.syncData ? JSON.stringify(lastSyncData.syncData) : null } : null;
|
||||
const { ref, content } = await this.userDataSyncStoreService.read(this.resourceKey, lastSyncUserData, this.source);
|
||||
let syncData: ISyncData | null = null;
|
||||
if (content !== null) {
|
||||
try {
|
||||
syncData = <ISyncData>JSON.parse(content);
|
||||
|
||||
// Migration from old content to sync data
|
||||
if (!isSyncData(syncData)) {
|
||||
syncData = { version: this.version, content };
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
return { ref, syncData };
|
||||
}
|
||||
|
||||
protected async updateRemoteUserData(content: string, ref: string | null): Promise<string> {
|
||||
return this.userDataSyncStoreService.write(this.resourceKey, content, ref, this.source);
|
||||
protected async updateRemoteUserData(content: string, ref: string | null): Promise<IRemoteUserData> {
|
||||
const syncData: ISyncData = { version: this.version, content };
|
||||
ref = await this.userDataSyncStoreService.write(this.resourceKey, JSON.stringify(syncData), ref, this.source);
|
||||
return { ref, syncData };
|
||||
}
|
||||
|
||||
protected async backupLocal(content: VSBuffer): Promise<void> {
|
||||
@@ -145,13 +202,14 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
}
|
||||
|
||||
abstract readonly resourceKey: ResourceKey;
|
||||
protected abstract doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise<void>;
|
||||
protected abstract readonly version: number;
|
||||
protected abstract doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IFileSyncPreviewResult {
|
||||
readonly fileContent: IFileContent | null;
|
||||
readonly remoteUserData: IUserData;
|
||||
readonly lastSyncUserData: IUserData | null;
|
||||
readonly remoteUserData: IRemoteUserData;
|
||||
readonly lastSyncUserData: IRemoteUserData | null;
|
||||
readonly content: string | null;
|
||||
readonly hasLocalChanged: boolean;
|
||||
readonly hasRemoteChanged: boolean;
|
||||
@@ -190,7 +248,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
|
||||
if (preview) {
|
||||
if (this.syncPreviewResultPromise) {
|
||||
const result = await this.syncPreviewResultPromise;
|
||||
return result.remoteUserData ? result.remoteUserData.content : null;
|
||||
return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null;
|
||||
}
|
||||
}
|
||||
return super.getRemoteContent();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
@@ -14,7 +14,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { localize } from 'vs/nls';
|
||||
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
interface ISyncPreviewResult {
|
||||
@@ -22,18 +22,19 @@ interface ISyncPreviewResult {
|
||||
readonly removed: IExtensionIdentifier[];
|
||||
readonly updated: ISyncExtension[];
|
||||
readonly remote: ISyncExtension[] | null;
|
||||
readonly remoteUserData: IUserData;
|
||||
readonly remoteUserData: IRemoteUserData;
|
||||
readonly skippedExtensions: ISyncExtension[];
|
||||
readonly lastSyncUserData: ILastSyncUserData | null;
|
||||
}
|
||||
|
||||
interface ILastSyncUserData extends IUserData {
|
||||
interface ILastSyncUserData extends IRemoteUserData {
|
||||
skippedExtensions: ISyncExtension[] | undefined;
|
||||
}
|
||||
|
||||
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
readonly resourceKey: ResourceKey = 'extensions';
|
||||
protected readonly version: number = 1;
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@@ -72,9 +73,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
|
||||
if (remoteUserData.content !== null) {
|
||||
if (remoteUserData.syncData !== null) {
|
||||
const localExtensions = await this.getLocalExtensions();
|
||||
const remoteExtensions: ISyncExtension[] = JSON.parse(remoteUserData.content);
|
||||
const remoteExtensions: ISyncExtension[] = JSON.parse(remoteUserData.syncData.content);
|
||||
const { added, updated, remote } = merge(localExtensions, remoteExtensions, [], [], this.getIgnoredExtensions());
|
||||
await this.apply({ added, removed: [], updated, remote, remoteUserData, skippedExtensions: [], lastSyncUserData });
|
||||
}
|
||||
@@ -145,7 +146,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
return null;
|
||||
}
|
||||
|
||||
protected async doSync(remoteUserData: IUserData, lastSyncUserData: ILastSyncUserData | null): Promise<void> {
|
||||
protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<void> {
|
||||
try {
|
||||
const previewResult = await this.getPreview(remoteUserData, lastSyncUserData);
|
||||
await this.apply(previewResult);
|
||||
@@ -163,9 +164,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
}
|
||||
|
||||
private async getPreview(remoteUserData: IUserData, lastSyncUserData: ILastSyncUserData | null): Promise<ISyncPreviewResult> {
|
||||
const remoteExtensions: ISyncExtension[] = remoteUserData.content ? JSON.parse(remoteUserData.content) : null;
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? JSON.parse(lastSyncUserData.content!) : null;
|
||||
private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<ISyncPreviewResult> {
|
||||
const remoteExtensions: ISyncExtension[] = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? JSON.parse(lastSyncUserData.syncData!.content) : null;
|
||||
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
|
||||
|
||||
const localExtensions = await this.getLocalExtensions();
|
||||
@@ -201,15 +202,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
// update remote
|
||||
this.logService.trace('Extensions: Updating remote extensions...');
|
||||
const content = JSON.stringify(remote);
|
||||
const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
|
||||
remoteUserData = { ref, content };
|
||||
remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
|
||||
this.logService.info('Extensions: Updated remote extensions');
|
||||
}
|
||||
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref) {
|
||||
// update last sync
|
||||
this.logService.trace('Extensions: Updating last synchronized extensions...');
|
||||
await this.updateLastSyncUserData<ILastSyncUserData>({ ...remoteUserData, skippedExtensions });
|
||||
await this.updateLastSyncUserData(remoteUserData, { skippedExtensions });
|
||||
this.logService.info('Extensions: Updated last synchronized extensions');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -13,7 +13,7 @@ import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
import { merge } from 'vs/platform/userDataSync/common/globalStateMerge';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
const argvProperties: string[] = ['locale'];
|
||||
@@ -21,13 +21,14 @@ const argvProperties: string[] = ['locale'];
|
||||
interface ISyncPreviewResult {
|
||||
readonly local: IGlobalState | undefined;
|
||||
readonly remote: IGlobalState | undefined;
|
||||
readonly remoteUserData: IUserData;
|
||||
readonly lastSyncUserData: IUserData | null;
|
||||
readonly remoteUserData: IRemoteUserData;
|
||||
readonly lastSyncUserData: IRemoteUserData | null;
|
||||
}
|
||||
|
||||
export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
readonly resourceKey: ResourceKey = 'globalState';
|
||||
protected readonly version: number = 1;
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@@ -57,8 +58,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
|
||||
if (remoteUserData.content !== null) {
|
||||
const local: IGlobalState = JSON.parse(remoteUserData.content);
|
||||
if (remoteUserData.syncData !== null) {
|
||||
const local: IGlobalState = JSON.parse(remoteUserData.syncData.content);
|
||||
await this.apply({ local, remote: undefined, remoteUserData, lastSyncUserData });
|
||||
}
|
||||
|
||||
@@ -119,7 +120,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
return null;
|
||||
}
|
||||
|
||||
protected async doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise<void> {
|
||||
protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<void> {
|
||||
try {
|
||||
const result = await this.getPreview(remoteUserData, lastSyncUserData);
|
||||
await this.apply(result);
|
||||
@@ -137,9 +138,9 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
}
|
||||
}
|
||||
|
||||
private async getPreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, ): Promise<ISyncPreviewResult> {
|
||||
const remoteGlobalState: IGlobalState = remoteUserData.content ? JSON.parse(remoteUserData.content) : null;
|
||||
const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.content ? JSON.parse(lastSyncUserData.content) : null;
|
||||
private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, ): Promise<ISyncPreviewResult> {
|
||||
const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
|
||||
const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null;
|
||||
|
||||
const localGloablState = await this.getLocalGlobalState();
|
||||
|
||||
@@ -173,9 +174,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
// update remote
|
||||
this.logService.trace('UI State: Updating remote ui state...');
|
||||
const content = JSON.stringify(remote);
|
||||
const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
|
||||
remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
|
||||
this.logService.info('UI State: Updated remote ui state');
|
||||
remoteUserData = { ref, content };
|
||||
}
|
||||
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser, IUserData, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
@@ -16,7 +16,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { OS, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { isUndefined } from 'vs/base/common/types';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
@@ -31,6 +31,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
|
||||
readonly resourceKey: ResourceKey = 'keybindings';
|
||||
protected get conflictsPreviewResource(): URI { return this.environmentService.keybindingsSyncPreviewResource; }
|
||||
protected readonly version: number = 1;
|
||||
|
||||
constructor(
|
||||
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@@ -59,7 +60,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
const content = remoteUserData.content !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null;
|
||||
const content = remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
|
||||
|
||||
if (content !== null) {
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
@@ -160,7 +161,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null;
|
||||
}
|
||||
|
||||
protected async doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise<void> {
|
||||
protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<void> {
|
||||
try {
|
||||
const result = await this.getPreview(remoteUserData, lastSyncUserData);
|
||||
if (result.hasConflicts) {
|
||||
@@ -213,9 +214,8 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
|
||||
if (hasRemoteChanged) {
|
||||
this.logService.trace('Keybindings: Updating remote keybindings...');
|
||||
const remoteContents = this.updateSyncContent(content, remoteUserData.content);
|
||||
const ref = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref);
|
||||
remoteUserData = { ref, content: remoteContents };
|
||||
const remoteContents = this.updateSyncContent(content, remoteUserData.syncData ? remoteUserData.syncData.content : null);
|
||||
remoteUserData = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref);
|
||||
this.logService.info('Keybindings: Updated remote keybindings');
|
||||
}
|
||||
|
||||
@@ -230,23 +230,23 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref && (content !== null || fileContent !== null)) {
|
||||
this.logService.trace('Keybindings: Updating last synchronized keybindings...');
|
||||
const lastSyncContent = this.updateSyncContent(content !== null ? content : fileContent!.value.toString(), null);
|
||||
await this.updateLastSyncUserData({ ref: remoteUserData.ref, content: lastSyncContent });
|
||||
await this.updateLastSyncUserData({ ref: remoteUserData.ref, syncData: { version: remoteUserData.syncData!.version, content: lastSyncContent } });
|
||||
this.logService.info('Keybindings: Updated last synchronized keybindings');
|
||||
}
|
||||
|
||||
this.syncPreviewResultPromise = null;
|
||||
}
|
||||
|
||||
private getPreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise<IFileSyncPreviewResult> {
|
||||
private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<IFileSyncPreviewResult> {
|
||||
if (!this.syncPreviewResultPromise) {
|
||||
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, token));
|
||||
}
|
||||
return this.syncPreviewResultPromise;
|
||||
}
|
||||
|
||||
private async generatePreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, token: CancellationToken): Promise<IFileSyncPreviewResult> {
|
||||
const remoteContent = remoteUserData.content ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null;
|
||||
const lastSyncContent = lastSyncUserData && lastSyncUserData.content ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.content) : null;
|
||||
private async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IFileSyncPreviewResult> {
|
||||
const remoteContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
|
||||
const lastSyncContent = lastSyncUserData && lastSyncUserData.syncData ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content) : null;
|
||||
// Get file content last to get the latest
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const formattingOptions = await this.getFormattingOptions();
|
||||
|
||||
@@ -576,15 +576,17 @@ function parseSettings(content: string): INode[] {
|
||||
if (hierarchyLevel === 0) {
|
||||
if (sep === ',') {
|
||||
const node = nodes.pop();
|
||||
nodes.push({
|
||||
startOffset: node!.startOffset,
|
||||
endOffset: node!.endOffset,
|
||||
value: node!.value,
|
||||
setting: {
|
||||
key: node!.setting!.key,
|
||||
hasCommaSeparator: true
|
||||
}
|
||||
});
|
||||
if (node) {
|
||||
nodes.push({
|
||||
startOffset: node.startOffset,
|
||||
endOffset: node.endOffset,
|
||||
value: node.value,
|
||||
setting: {
|
||||
key: node.setting!.key,
|
||||
hasCommaSeparator: true
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, IUserData, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -18,15 +18,26 @@ import * as arrays from 'vs/base/common/arrays';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { isEmptyObject } from 'vs/base/common/types';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
interface ISettingsSyncContent {
|
||||
settings: string;
|
||||
}
|
||||
|
||||
function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent {
|
||||
return thing
|
||||
&& (thing.settings && typeof thing.settings === 'string')
|
||||
&& Object.keys(thing).length === 1;
|
||||
}
|
||||
|
||||
export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements ISettingsSyncService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
readonly resourceKey: ResourceKey = 'settings';
|
||||
protected readonly version: number = 1;
|
||||
protected get conflictsPreviewResource(): URI { return this.environmentService.settingsSyncPreviewResource; }
|
||||
|
||||
private _conflicts: IConflictSetting[] = [];
|
||||
@@ -77,12 +88,13 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
|
||||
|
||||
if (remoteUserData.content !== null) {
|
||||
if (remoteSettingsSyncContent !== null) {
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// Update ignored settings from local file content
|
||||
const content = updateIgnoredSettings(remoteUserData.content, fileContent ? fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils);
|
||||
const content = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils);
|
||||
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<IFileSyncPreviewResult>({
|
||||
fileContent,
|
||||
remoteUserData,
|
||||
@@ -173,6 +185,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
|
||||
async getRemoteContent(preview?: boolean): Promise<string | null> {
|
||||
let content = await super.getRemoteContent(preview);
|
||||
if (content !== null) {
|
||||
const settingsSyncContent = this.parseSettingsSyncContent(content);
|
||||
content = settingsSyncContent ? settingsSyncContent.settings : null;
|
||||
}
|
||||
if (preview && content !== null) {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// remove ignored settings from the remote content for preview
|
||||
@@ -202,7 +218,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
}
|
||||
}
|
||||
|
||||
protected async doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null, resolvedConflicts: { key: string, value: any | undefined }[] = []): Promise<void> {
|
||||
protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any | undefined }[] = []): Promise<void> {
|
||||
try {
|
||||
const result = await this.getPreview(remoteUserData, lastSyncUserData, resolvedConflicts);
|
||||
if (result.hasConflicts) {
|
||||
@@ -256,11 +272,11 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
if (hasRemoteChanged) {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// Update ignored settings from remote
|
||||
content = updateIgnoredSettings(content, remoteUserData.content || '{}', getIgnoredSettings(this.configurationService, content), formatUtils);
|
||||
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
|
||||
content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', getIgnoredSettings(this.configurationService, content), formatUtils);
|
||||
this.logService.trace('Settings: Updating remote settings...');
|
||||
const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
|
||||
remoteUserData = await this.updateRemoteUserData(JSON.stringify(<ISettingsSyncContent>{ settings: content }), forcePush ? null : remoteUserData.ref);
|
||||
this.logService.info('Settings: Updated remote settings');
|
||||
remoteUserData = { ref, content };
|
||||
}
|
||||
|
||||
// Delete the preview
|
||||
@@ -280,16 +296,18 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
this.syncPreviewResultPromise = null;
|
||||
}
|
||||
|
||||
private getPreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, resolvedConflicts: { key: string, value: any }[]): Promise<IFileSyncPreviewResult> {
|
||||
private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[]): Promise<IFileSyncPreviewResult> {
|
||||
if (!this.syncPreviewResultPromise) {
|
||||
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, resolvedConflicts, token));
|
||||
}
|
||||
return this.syncPreviewResultPromise;
|
||||
}
|
||||
|
||||
protected async generatePreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise<IFileSyncPreviewResult> {
|
||||
protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise<IFileSyncPreviewResult> {
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const formattingOptions = await this.getFormattingOptions();
|
||||
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
|
||||
const lastSettingsSyncContent = lastSyncUserData ? this.getSettingsSyncContent(lastSyncUserData) : null;
|
||||
|
||||
let content: string | null = null;
|
||||
let hasLocalChanged: boolean = false;
|
||||
@@ -297,7 +315,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
let hasConflicts: boolean = false;
|
||||
let conflictSettings: IConflictSetting[] = [];
|
||||
|
||||
if (remoteUserData.content) {
|
||||
if (remoteSettingsSyncContent) {
|
||||
const localContent: string = fileContent ? fileContent.value.toString() : '{}';
|
||||
|
||||
// No action when there are errors
|
||||
@@ -307,7 +325,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
|
||||
else {
|
||||
this.logService.trace('Settings: Merging remote settings with local settings...');
|
||||
const result = merge(localContent, remoteUserData.content, lastSyncUserData ? lastSyncUserData.content : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions);
|
||||
const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions);
|
||||
content = result.localContent || result.remoteContent;
|
||||
hasLocalChanged = result.localContent !== null;
|
||||
hasRemoteChanged = result.remoteContent !== null;
|
||||
@@ -333,4 +351,17 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
private getSettingsSyncContent(remoteUserData: IRemoteUserData): ISettingsSyncContent | null {
|
||||
return remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null;
|
||||
}
|
||||
|
||||
private parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null {
|
||||
try {
|
||||
const parsed = <ISettingsSyncContent>JSON.parse(syncContent);
|
||||
return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent };
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionIdentifier, EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, allSettings } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
@@ -83,7 +83,12 @@ export function registerConfiguration(): IDisposable {
|
||||
},
|
||||
'sync.ignoredExtensions': {
|
||||
'type': 'array',
|
||||
description: localize('sync.ignoredExtensions', "Configure extensions to be ignored while synchronizing."),
|
||||
'description': localize('sync.ignoredExtensions', "List of extensions to be ignored while synchronizing. The identifier of an extension is always ${publisher}.${name}. For example: vscode.csharp."),
|
||||
items: {
|
||||
type: 'string',
|
||||
pattern: EXTENSION_IDENTIFIER_PATTERN,
|
||||
errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.")
|
||||
},
|
||||
'default': [],
|
||||
'scope': ConfigurationScope.APPLICATION,
|
||||
uniqueItems: true
|
||||
@@ -171,6 +176,7 @@ export enum UserDataSyncErrorCode {
|
||||
// Local Errors
|
||||
LocalPreconditionFailed = 'LocalPreconditionFailed',
|
||||
LocalInvalidContent = 'LocalInvalidContent',
|
||||
Incompatible = 'Incompatible',
|
||||
|
||||
Unknown = 'Unknown',
|
||||
}
|
||||
@@ -335,6 +341,7 @@ export interface ISettingsSyncService extends IUserDataSynchroniser {
|
||||
//#endregion
|
||||
|
||||
export const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized);
|
||||
export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey<boolean>('syncEnabled', false);
|
||||
|
||||
export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync';
|
||||
export function toRemoteContentResource(source: SyncSource): URI {
|
||||
|
||||
@@ -7,6 +7,11 @@ import { IUserDataSyncEnablementService, ResourceKey, ALL_RESOURCE_KEYS } from '
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
type SyncEnablementClassification = {
|
||||
enabled?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
const enablementKey = 'sync.enable';
|
||||
function getEnablementKey(resourceKey: ResourceKey) { return `${enablementKey}.${resourceKey}`; }
|
||||
@@ -22,7 +27,8 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa
|
||||
readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]> = this._onDidChangeResourceEnablement.event;
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
) {
|
||||
super();
|
||||
this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
|
||||
@@ -34,6 +40,7 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa
|
||||
|
||||
setEnablement(enabled: boolean): void {
|
||||
if (this.isEnabled() !== enabled) {
|
||||
this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(enablementKey, { enabled });
|
||||
this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
@@ -44,7 +51,9 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa
|
||||
|
||||
setResourceEnablement(resourceKey: ResourceKey, enabled: boolean): void {
|
||||
if (this.isResourceEnabled(resourceKey) !== enabled) {
|
||||
this.storageService.store(getEnablementKey(resourceKey), enabled, StorageScope.GLOBAL);
|
||||
const resourceEnablementKey = getEnablementKey(resourceKey);
|
||||
this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(resourceEnablementKey, { enabled });
|
||||
this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
@@ -49,7 +49,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService,
|
||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||
@IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
@@ -62,7 +61,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
|
||||
if (this.userDataSyncStoreService.userDataSyncStore) {
|
||||
this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus()));
|
||||
this._register(this.userDataAuthTokenService.onDidChangeToken(e => this.onDidChangeAuthTokenStatus(e)));
|
||||
}
|
||||
|
||||
this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal));
|
||||
@@ -140,6 +138,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
await this.checkEnablement();
|
||||
if (this.status === SyncStatus.Idle) {
|
||||
return;
|
||||
}
|
||||
@@ -201,7 +200,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
}
|
||||
|
||||
private async hasPreviouslySynced(): Promise<boolean> {
|
||||
await this.checkEnablement();
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
if (await synchroniser.hasPreviouslySynced()) {
|
||||
return true;
|
||||
@@ -211,7 +209,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
}
|
||||
|
||||
private async hasLocalData(): Promise<boolean> {
|
||||
await this.checkEnablement();
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
if (await synchroniser.hasLocalData()) {
|
||||
return true;
|
||||
@@ -288,14 +285,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
if (!this.userDataSyncStoreService.userDataSyncStore) {
|
||||
throw new Error('Not enabled');
|
||||
}
|
||||
if (!(await this.userDataAuthTokenService.getToken())) {
|
||||
throw new UserDataSyncError('Not Authenticated. Please sign in to start sync.', UserDataSyncErrorCode.Unauthorized);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeAuthTokenStatus(token: string | undefined): void {
|
||||
if (!token) {
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +458,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
||||
let workspacesToRestore: IWorkspacePathToOpen[] = [];
|
||||
if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) {
|
||||
let foldersToRestore = this.backupMainService.getFolderBackupPaths();
|
||||
foldersToAdd.push(...foldersToRestore.map(f => ({ folderUri: f, remoteAuhority: getRemoteAuthority(f), isRestored: true })));
|
||||
foldersToOpen.push(...foldersToRestore.map(f => ({ folderUri: f, remoteAuhority: getRemoteAuthority(f) })));
|
||||
|
||||
// collect from workspaces with hot-exit backups and from previous window session
|
||||
workspacesToRestore = [...this.backupMainService.getWorkspaceBackups(), ...this.workspacesMainService.getUntitledWorkspacesSync()];
|
||||
|
||||
Vendored
+58
@@ -2465,6 +2465,53 @@ declare module 'vscode' {
|
||||
provideHover(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Hover>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime.
|
||||
* The result of this evaluation is shown in a tooltip-like widget.
|
||||
* If only a range is specified, the expression will be extracted from the underlying document.
|
||||
* An optional expression can be used to override the extracted expression.
|
||||
* In this case the range is still used to highlight the range in the document.
|
||||
*/
|
||||
export class EvaluatableExpression {
|
||||
|
||||
/*
|
||||
* The range is used to extract the evaluatable expression from the underlying document and to highlight it.
|
||||
*/
|
||||
readonly range: Range;
|
||||
|
||||
/*
|
||||
* If specified the expression overrides the extracted expression.
|
||||
*/
|
||||
readonly expression?: string;
|
||||
|
||||
/**
|
||||
* Creates a new evaluatable expression object.
|
||||
*
|
||||
* @param range The range in the underlying document from which the evaluatable expression is extracted.
|
||||
* @param expression If specified overrides the extracted expression.
|
||||
*/
|
||||
constructor(range: Range, expression?: string);
|
||||
}
|
||||
|
||||
/**
|
||||
* The evaluatable expression provider interface defines the contract between extensions and
|
||||
* the debug hover.
|
||||
*/
|
||||
export interface EvaluatableExpressionProvider {
|
||||
|
||||
/**
|
||||
* Provide an evaluatable expression for the given document and position.
|
||||
* The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression.
|
||||
*
|
||||
* @param document The document in which the debug hover is opened.
|
||||
* @param position The position in the document where the debug hover is opened.
|
||||
* @param token A cancellation token.
|
||||
* @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be
|
||||
* signaled by returning `undefined` or `null`.
|
||||
*/
|
||||
provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<EvaluatableExpression>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A document highlight kind.
|
||||
*/
|
||||
@@ -9080,6 +9127,17 @@ declare module 'vscode' {
|
||||
*/
|
||||
export function registerHoverProvider(selector: DocumentSelector, provider: HoverProvider): Disposable;
|
||||
|
||||
/**
|
||||
* Register a provider that locates evaluatable expressions in text documents.
|
||||
*
|
||||
* If multiple providers are registered for a language an arbitrary provider will be used.
|
||||
*
|
||||
* @param selector A selector that defines the documents this provider is applicable to.
|
||||
* @param provider An evaluatable expression provider.
|
||||
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
|
||||
*/
|
||||
export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable;
|
||||
|
||||
/**
|
||||
* Register a document highlight provider.
|
||||
*
|
||||
|
||||
Vendored
+30
-61
@@ -876,67 +876,7 @@ declare module 'vscode' {
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region locate evaluatable expressions for debug hover: https://github.com/microsoft/vscode/issues/89084
|
||||
|
||||
/**
|
||||
* An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime.
|
||||
* The result of this evaluation is shown in a tooltip-like widget.
|
||||
* If only a range is specified, the expression will be extracted from the underlying document.
|
||||
* An optional expression can be used to override the extracted expression.
|
||||
* In this case the range is still used to highlight the range in the document.
|
||||
*/
|
||||
export class EvaluatableExpression {
|
||||
/*
|
||||
* The range is used to extract the evaluatable expression from the underlying document and to highlight it.
|
||||
*/
|
||||
readonly range: Range;
|
||||
/*
|
||||
* If specified the expression overrides the extracted expression.
|
||||
*/
|
||||
readonly expression?: string;
|
||||
|
||||
/**
|
||||
* Creates a new evaluatable expression object.
|
||||
*
|
||||
* @param range The range in the underlying document from which the evaluatable expression is extracted.
|
||||
* @param expression If specified overrides the extracted expression.
|
||||
*/
|
||||
constructor(range: Range, expression?: string);
|
||||
}
|
||||
|
||||
/**
|
||||
* The evaluatable expression provider interface defines the contract between extensions and
|
||||
* the debug hover.
|
||||
*/
|
||||
export interface EvaluatableExpressionProvider {
|
||||
|
||||
/**
|
||||
* Provide an evaluatable expression for the given document and position.
|
||||
* The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression.
|
||||
*
|
||||
* @param document The document in which the command was invoked.
|
||||
* @param position The position where the command was invoked.
|
||||
* @param token A cancellation token.
|
||||
* @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be
|
||||
* signaled by returning `undefined` or `null`.
|
||||
*/
|
||||
provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<EvaluatableExpression>;
|
||||
}
|
||||
|
||||
export namespace languages {
|
||||
/**
|
||||
* Register a provider that locates evaluatable expressions in text documents.
|
||||
*
|
||||
* If multiple providers are registered for a language an arbitrary provider will be used.
|
||||
*
|
||||
* @param selector A selector that defines the documents this provider is applicable to.
|
||||
* @param provider An evaluatable expression provider.
|
||||
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
|
||||
*/
|
||||
export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable;
|
||||
}
|
||||
|
||||
// deprecated
|
||||
//#region deprecated debug API
|
||||
|
||||
export interface DebugConfigurationProvider {
|
||||
/**
|
||||
@@ -1734,4 +1674,33 @@ declare module 'vscode' {
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Dialog title: https://github.com/microsoft/vscode/issues/82871
|
||||
|
||||
/**
|
||||
* Options to configure the behaviour of a file open dialog.
|
||||
*
|
||||
* * Note 1: A dialog can select files, folders, or both. This is not true for Windows
|
||||
* which enforces to open either files or folder, but *not both*.
|
||||
* * Note 2: Explicitly setting `canSelectFiles` and `canSelectFolders` to `false` is futile
|
||||
* and the editor then silently adjusts the options to select files.
|
||||
*/
|
||||
export interface OpenDialogOptions {
|
||||
/**
|
||||
* Dialog title
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to configure the behaviour of a file save dialog.
|
||||
*/
|
||||
export interface SaveDialogOptions {
|
||||
/**
|
||||
* Dialog title
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape {
|
||||
canSelectFiles: options.canSelectFiles || (!options.canSelectFiles && !options.canSelectFolders),
|
||||
canSelectFolders: options.canSelectFolders,
|
||||
canSelectMany: options.canSelectMany,
|
||||
defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined
|
||||
defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined,
|
||||
title: options.title || undefined
|
||||
};
|
||||
if (options.filters) {
|
||||
result.filters = [];
|
||||
@@ -49,7 +50,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape {
|
||||
private static _convertSaveOptions(options: MainThreadDialogSaveOptions): ISaveDialogOptions {
|
||||
const result: ISaveDialogOptions = {
|
||||
defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined,
|
||||
saveLabel: options.saveLabel || undefined
|
||||
saveLabel: options.saveLabel || undefined,
|
||||
title: options.title || undefined
|
||||
};
|
||||
if (options.filters) {
|
||||
result.filters = [];
|
||||
|
||||
@@ -10,7 +10,7 @@ import { RenderLineNumbersType, TextEditorCursorStyle, cursorStyleToString, Edit
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
||||
import { IDecorationOptions, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { IIdentifiedSingleEditOperation, ISingleEditOperation, ITextModel, ITextModelUpdateOptions } from 'vs/editor/common/model';
|
||||
import { ISingleEditOperation, ITextModel, ITextModelUpdateOptions, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
|
||||
import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol';
|
||||
|
||||
@@ -50,6 +50,7 @@ import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtens
|
||||
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views';
|
||||
@@ -177,12 +178,14 @@ export interface MainThreadDialogOpenOptions {
|
||||
canSelectFolders?: boolean;
|
||||
canSelectMany?: boolean;
|
||||
filters?: { [name: string]: string[]; };
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface MainThreadDialogSaveOptions {
|
||||
defaultUri?: UriComponents;
|
||||
saveLabel?: string;
|
||||
filters?: { [name: string]: string[]; };
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface MainThreadDiaglogsShape extends IDisposable {
|
||||
@@ -1098,18 +1101,25 @@ export interface IWorkspaceSymbolsDto extends IdObject {
|
||||
symbols: IWorkspaceSymbolDto[];
|
||||
}
|
||||
|
||||
export interface IWorkspaceEditEntryMetadataDto {
|
||||
needsConfirmation: boolean;
|
||||
label: string;
|
||||
description?: string;
|
||||
iconPath?: { id: string } | UriComponents | { light: UriComponents, dark: UriComponents };
|
||||
}
|
||||
|
||||
export interface IWorkspaceFileEditDto {
|
||||
oldUri?: UriComponents;
|
||||
newUri?: UriComponents;
|
||||
options?: modes.WorkspaceFileEditOptions
|
||||
metadata?: modes.WorkspaceEditMetadata;
|
||||
metadata?: IWorkspaceEditEntryMetadataDto;
|
||||
}
|
||||
|
||||
export interface IWorkspaceTextEditDto {
|
||||
resource: UriComponents;
|
||||
edit: modes.TextEdit;
|
||||
modelVersionId?: number;
|
||||
metadata?: modes.WorkspaceEditMetadata;
|
||||
metadata?: IWorkspaceEditEntryMetadataDto;
|
||||
}
|
||||
|
||||
export interface IWorkspaceEditDto {
|
||||
@@ -1128,6 +1138,9 @@ export function reviveWorkspaceEditDto(data: IWorkspaceEditDto | undefined): mod
|
||||
(<IWorkspaceFileEditDto>edit).newUri = URI.revive((<IWorkspaceFileEditDto>edit).newUri);
|
||||
(<IWorkspaceFileEditDto>edit).oldUri = URI.revive((<IWorkspaceFileEditDto>edit).oldUri);
|
||||
}
|
||||
if (edit.metadata && edit.metadata.iconPath) {
|
||||
edit.metadata = revive(edit.metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
return <modes.WorkspaceEdit>data;
|
||||
|
||||
@@ -736,6 +736,35 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.toggleSelection',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: WorkbenchListFocusContextKey,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter,
|
||||
handler: (accessor) => {
|
||||
const widget = accessor.get(IListService).lastFocusedList;
|
||||
|
||||
if (!widget || isLegacyTree(widget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focus = widget.getFocus();
|
||||
|
||||
if (focus.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = widget.getSelection();
|
||||
const index = selection.indexOf(focus[0]);
|
||||
|
||||
if (index > -1) {
|
||||
widget.setSelection([...selection.slice(0, index), ...selection.slice(index + 1)]);
|
||||
} else {
|
||||
widget.setSelection([...selection, focus[0]]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'list.toggleExpand',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
|
||||
@@ -28,7 +28,6 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { isStandalone } from 'vs/base/browser/browser';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
|
||||
export interface IDraggedResource {
|
||||
@@ -343,7 +342,6 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
|
||||
// Editors: enables cross window DND of tabs into the editor area
|
||||
const textFileService = accessor.get(ITextFileService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const modelService = accessor.get(IModelService);
|
||||
|
||||
const draggedEditors: ISerializedDraggedEditor[] = [];
|
||||
files.forEach(file => {
|
||||
@@ -374,11 +372,8 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
|
||||
// If the resource is dirty or untitled, send over its content
|
||||
// to restore dirty state. Get that from the text model directly
|
||||
let content: string | undefined = undefined;
|
||||
if (textFileService.isDirty(file.resource)) {
|
||||
const textModel = modelService.getModel(file.resource);
|
||||
if (textModel) {
|
||||
content = textModel.getValue();
|
||||
}
|
||||
if (model?.isDirty()) {
|
||||
content = model.textEditorModel.getValue();
|
||||
}
|
||||
|
||||
// Add as dragged editor
|
||||
|
||||
@@ -148,8 +148,8 @@ export class ResourceLabels extends Disposable {
|
||||
}));
|
||||
|
||||
// notify when untitled labels change
|
||||
this.textFileService.untitled.onDidChangeLabel(resource => {
|
||||
this._widgets.forEach(widget => widget.notifyUntitledLabelChange(resource));
|
||||
this.textFileService.untitled.onDidChangeLabel(model => {
|
||||
this._widgets.forEach(widget => widget.notifyUntitledLabelChange(model.resource));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -122,8 +122,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
|
||||
private workbenchGrid!: SerializableGrid<ISerializableView>;
|
||||
|
||||
private editorWidgetSet = new Set<IEditor>();
|
||||
|
||||
private disposed: boolean | undefined;
|
||||
|
||||
private titleBarPartView!: ISerializableView;
|
||||
@@ -198,7 +196,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
wasSideBarVisible: false,
|
||||
wasPanelVisible: false,
|
||||
transitionDisposables: new DisposableStore(),
|
||||
setNotificationsFilter: false
|
||||
setNotificationsFilter: false,
|
||||
editorWidgetSet: new Set<IEditor>()
|
||||
},
|
||||
|
||||
};
|
||||
@@ -708,15 +707,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
editor.updateOptions({ lineNumbers });
|
||||
};
|
||||
|
||||
const editorWidgetSet = this.state.zenMode.editorWidgetSet;
|
||||
if (!lineNumbers) {
|
||||
// Reset line numbers on all editors visible and non-visible
|
||||
for (const editor of this.editorWidgetSet) {
|
||||
for (const editor of editorWidgetSet) {
|
||||
setEditorLineNumbers(editor);
|
||||
}
|
||||
this.editorWidgetSet.clear();
|
||||
editorWidgetSet.clear();
|
||||
} else {
|
||||
this.editorService.visibleTextEditorWidgets.forEach(editor => {
|
||||
this.editorWidgetSet.add(editor);
|
||||
if (!editorWidgetSet.has(editor)) {
|
||||
editorWidgetSet.add(editor);
|
||||
this.state.zenMode.transitionDisposables.add(editor.onDidDispose(() => {
|
||||
editorWidgetSet.delete(editor);
|
||||
}));
|
||||
}
|
||||
setEditorLineNumbers(editor);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -470,7 +470,7 @@ export class BreadcrumbsControl {
|
||||
this._ckBreadcrumbsActive.set(value);
|
||||
}
|
||||
|
||||
private _revealInEditor(event: IBreadcrumbsItemEvent, element: any, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void {
|
||||
private _revealInEditor(event: IBreadcrumbsItemEvent, element: BreadcrumbElement, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void {
|
||||
if (element instanceof FileElement) {
|
||||
if (element.kind === FileKind.FILE) {
|
||||
// open file in any editor
|
||||
|
||||
@@ -71,10 +71,8 @@ export class BaseSplitEditorAction extends Action {
|
||||
}));
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
async run(context?: IEditorIdentifier): Promise<any> {
|
||||
splitEditor(this.editorGroupService, this.direction, context);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +181,7 @@ export class JoinTwoGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
async run(context?: IEditorIdentifier): Promise<any> {
|
||||
let sourceGroup: IEditorGroup | undefined;
|
||||
if (context && typeof context.groupId === 'number') {
|
||||
sourceGroup = this.editorGroupService.getGroup(context.groupId);
|
||||
@@ -198,12 +196,10 @@ export class JoinTwoGroupsAction extends Action {
|
||||
if (targetGroup && sourceGroup !== targetGroup) {
|
||||
this.editorGroupService.mergeGroup(sourceGroup, targetGroup);
|
||||
|
||||
return Promise.resolve(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,10 +216,8 @@ export class JoinAllGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
async run(context?: IEditorIdentifier): Promise<any> {
|
||||
mergeAllGroups(this.editorGroupService);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,11 +234,9 @@ export class NavigateBetweenGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const nextGroup = this.editorGroupService.findGroup({ location: GroupLocation.NEXT }, this.editorGroupService.activeGroup, true);
|
||||
nextGroup.focus();
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,10 +253,8 @@ export class FocusActiveGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.editorGroupService.activeGroup.focus();
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,13 +269,11 @@ export abstract class BaseFocusGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const group = this.editorGroupService.findGroup(this.scope, this.editorGroupService.activeGroup, true);
|
||||
if (group) {
|
||||
group.focus();
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +409,7 @@ export class OpenToSideFromQuickOpenAction extends Action {
|
||||
this.class = (preferredDirection === GroupDirection.RIGHT) ? 'codicon-split-horizontal' : 'codicon-split-vertical';
|
||||
}
|
||||
|
||||
run(context: any): Promise<any> {
|
||||
async run(context: any): Promise<any> {
|
||||
const entry = toEditorQuickOpenEntry(context);
|
||||
if (entry) {
|
||||
const input = entry.getInput();
|
||||
@@ -436,8 +424,6 @@ export class OpenToSideFromQuickOpenAction extends Action {
|
||||
return this.editorService.openEditor(resourceInput, SIDE_GROUP);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,7 +476,7 @@ export class CloseOneEditorAction extends Action {
|
||||
super(id, label, 'codicon-close');
|
||||
}
|
||||
|
||||
run(context?: IEditorCommandsContext): Promise<any> {
|
||||
async run(context?: IEditorCommandsContext): Promise<any> {
|
||||
let group: IEditorGroup | undefined;
|
||||
let editorIndex: number | undefined;
|
||||
if (context) {
|
||||
@@ -517,8 +503,6 @@ export class CloseOneEditorAction extends Action {
|
||||
if (group.activeEditor) {
|
||||
return group.closeEditor(group.activeEditor);
|
||||
}
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,8 +538,6 @@ export class RevertAndCloseEditorAction extends Action {
|
||||
|
||||
group.closeEditor(editor);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,13 +555,11 @@ export class CloseLeftEditorsInGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
async run(context?: IEditorIdentifier): Promise<any> {
|
||||
const { group, editor } = getTarget(this.editorService, this.editorGroupService, context);
|
||||
if (group && editor) {
|
||||
return group.closeEditors({ direction: CloseDirection.LEFT, except: editor });
|
||||
}
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,9 +716,9 @@ export class CloseEditorsInOtherGroupsAction extends Action {
|
||||
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup;
|
||||
return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => {
|
||||
return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async g => {
|
||||
if (groupToSkip && g.id === groupToSkip.id) {
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
return g.closeAllEditors();
|
||||
@@ -760,13 +740,11 @@ export class CloseEditorInAllGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const activeEditor = this.editorService.activeEditor;
|
||||
if (activeEditor) {
|
||||
return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor)));
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -781,7 +759,7 @@ export class BaseMoveGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
async run(context?: IEditorIdentifier): Promise<any> {
|
||||
let sourceGroup: IEditorGroup | undefined;
|
||||
if (context && typeof context.groupId === 'number') {
|
||||
sourceGroup = this.editorGroupService.getGroup(context.groupId);
|
||||
@@ -795,8 +773,6 @@ export class BaseMoveGroupAction extends Action {
|
||||
this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private findTargetGroup(sourceGroup: IEditorGroup): IEditorGroup | undefined {
|
||||
@@ -892,10 +868,8 @@ export class MinimizeOtherGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -908,10 +882,8 @@ export class ResetGroupSizesAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.EVEN);
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -924,10 +896,8 @@ export class ToggleGroupSizesAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.TOGGLE);
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -946,13 +916,11 @@ export class MaximizeGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
if (this.editorService.activeEditor) {
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
|
||||
this.layoutService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -967,23 +935,21 @@ export abstract class BaseNavigateEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const result = this.navigate();
|
||||
if (!result) {
|
||||
return Promise.resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const { groupId, editor } = result;
|
||||
if (!editor) {
|
||||
return Promise.resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const group = this.editorGroupService.getGroup(groupId);
|
||||
if (group) {
|
||||
return group.openEditor(editor);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
protected abstract navigate(): IEditorIdentifier | undefined;
|
||||
@@ -1158,10 +1124,8 @@ export class NavigateForwardAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.forward();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1174,10 +1138,8 @@ export class NavigateBackwardsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.back();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1190,10 +1152,8 @@ export class NavigateToLastEditLocationAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.openLastEditLocation();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1206,10 +1166,8 @@ export class NavigateLastAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.last();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1226,10 +1184,8 @@ export class ReopenClosedEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.reopenLastClosedEditor();
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1247,15 +1203,13 @@ export class ClearRecentFilesAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
|
||||
// Clear global recently opened
|
||||
this.workspacesService.clearRecentlyOpened();
|
||||
|
||||
// Clear workspace specific recently opened
|
||||
this.historyService.clearRecentlyOpened();
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1313,12 +1267,10 @@ export class BaseQuickOpenEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const keybindings = this.keybindingService.lookupKeybindings(this.id);
|
||||
|
||||
this.quickOpenService.show(this.prefix, { quickNavigateConfiguration: { keybindings } });
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1396,12 +1348,10 @@ export class QuickOpenPreviousEditorFromHistoryAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const keybindings = this.keybindingService.lookupKeybindings(this.id);
|
||||
|
||||
this.quickOpenService.show(undefined, { quickNavigateConfiguration: { keybindings } });
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1418,10 +1368,8 @@ export class OpenNextRecentlyUsedEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.openNextRecentlyUsedEditor();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1438,10 +1386,8 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.openPreviouslyUsedEditor();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1459,10 +1405,8 @@ export class OpenNextRecentlyUsedEditorInGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.openNextRecentlyUsedEditor(this.editorGroupsService.activeGroup.id);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1480,10 +1424,8 @@ export class OpenPreviousRecentlyUsedEditorInGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.historyService.openPreviouslyUsedEditor(this.editorGroupsService.activeGroup.id);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1500,12 +1442,10 @@ export class ClearEditorHistoryAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
|
||||
// Editor history
|
||||
this.historyService.clear();
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1772,10 +1712,8 @@ export class BaseCreateEditorGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.editorGroupService.addGroup(this.editorGroupService.activeGroup, this.direction, { activate: true });
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -317,8 +317,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()));
|
||||
this._register(this.textFileService.untitled.onDidChangeEncoding(r => this.onResourceEncodingChange(r)));
|
||||
this._register(this.textFileService.files.onDidChangeEncoding(m => this.onResourceEncodingChange((m.resource))));
|
||||
this._register(this.textFileService.untitled.onDidChangeEncoding(model => this.onResourceEncodingChange(model.resource)));
|
||||
this._register(this.textFileService.files.onDidChangeEncoding(model => this.onResourceEncodingChange((model.resource))));
|
||||
this._register(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/commo
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
|
||||
/**
|
||||
* The text editor that leverages the diff text editor for the editing experience.
|
||||
@@ -51,8 +50,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IClipboardService private clipboardService: IClipboardService
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService);
|
||||
}
|
||||
@@ -75,10 +73,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
}
|
||||
|
||||
createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor {
|
||||
if (this.reverseColor) { // {{SQL CARBON EDIT}}
|
||||
(configuration as IDiffEditorOptions).reverse = true;
|
||||
}
|
||||
return this.instantiationService.createInstance(DiffEditorWidget as any, parent, configuration, this.clipboardService); // {{SQL CARBON EDIT}} strict-null-check...i guess?
|
||||
if (this.reverseColor) { (configuration as IDiffEditorOptions).reverse = true; } // {{SQL CARBON EDIT}}
|
||||
return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration);
|
||||
}
|
||||
|
||||
async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND,
|
||||
import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom';
|
||||
import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IActionViewItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { prepareActions } from 'vs/workbench/browser/actions';
|
||||
@@ -51,7 +51,6 @@ export interface IPaneColors extends IColorMapping {
|
||||
}
|
||||
|
||||
export interface IViewPaneOptions extends IPaneOptions {
|
||||
actionRunner?: IActionRunner;
|
||||
id: string;
|
||||
title: string;
|
||||
showActionsAlways?: boolean;
|
||||
@@ -169,7 +168,6 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
|
||||
private readonly menuActions: ViewMenuActions;
|
||||
|
||||
protected actionRunner?: IActionRunner;
|
||||
private toolbar?: ToolBar;
|
||||
private readonly showActionsAlways: boolean = false;
|
||||
private headerContainer?: HTMLElement;
|
||||
@@ -196,7 +194,6 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
|
||||
this.id = options.id;
|
||||
this.title = options.title;
|
||||
this.actionRunner = options.actionRunner;
|
||||
this.showActionsAlways = !!options.showActionsAlways;
|
||||
this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService);
|
||||
|
||||
@@ -262,7 +259,6 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
actionViewItemProvider: action => this.getActionViewItem(action),
|
||||
ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title),
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
|
||||
actionRunner: this.actionRunner
|
||||
});
|
||||
|
||||
this._register(this.toolbar);
|
||||
@@ -311,7 +307,9 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
if (this.element) {
|
||||
if (this.shouldShowWelcome()) {
|
||||
this.viewWelcomeContainer.focus();
|
||||
} else if (this.element) {
|
||||
this.element.focus();
|
||||
this._onDidFocus.fire();
|
||||
}
|
||||
@@ -453,8 +451,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
private didLayout = false;
|
||||
private dimension: Dimension | undefined;
|
||||
|
||||
protected actionRunner: IActionRunner | undefined;
|
||||
|
||||
private readonly visibleViewsCountFromCache: number | undefined;
|
||||
private readonly visibleViewsStorageId: string;
|
||||
protected readonly viewsModel: PersistentContributableViewsModel;
|
||||
@@ -800,7 +796,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
{
|
||||
id: viewDescriptor.id,
|
||||
title: viewDescriptor.name,
|
||||
actionRunner: this.getActionRunner(),
|
||||
expanded: !collapsed,
|
||||
minimumBodySize: this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Panel ? 0 : 120
|
||||
});
|
||||
@@ -831,14 +826,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
return panes;
|
||||
}
|
||||
|
||||
getActionRunner(): IActionRunner {
|
||||
if (!this.actionRunner) {
|
||||
this.actionRunner = new ActionRunner();
|
||||
}
|
||||
|
||||
return this.actionRunner;
|
||||
}
|
||||
|
||||
private onDidRemoveViewDescriptors(removed: IViewDescriptorRef[]): void {
|
||||
removed = removed.sort((a, b) => b.index - a.index);
|
||||
const panesToRemove: ViewPane[] = [];
|
||||
|
||||
@@ -118,7 +118,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry
|
||||
try {
|
||||
instantiationService.createInstance(ctor);
|
||||
} catch (error) {
|
||||
console.error(`Unable to instantiate workbench contribution ${(ctor as any).name}.`, error);
|
||||
console.error(`Unable to instantiate workbench contribution ${ctor.name}.`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ export interface IFileInputFactory {
|
||||
|
||||
createFileInput(resource: URI, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput;
|
||||
|
||||
isFileInput(obj: any): obj is IFileEditorInput;
|
||||
isFileInput(obj: unknown): obj is IFileEditorInput;
|
||||
}
|
||||
|
||||
export interface IEditorInputFactoryRegistry {
|
||||
|
||||
@@ -46,8 +46,8 @@ export interface ISerializedEditorGroup {
|
||||
preview?: number;
|
||||
}
|
||||
|
||||
export function isSerializedEditorGroup(obj?: any): obj is ISerializedEditorGroup {
|
||||
const group: ISerializedEditorGroup = obj;
|
||||
export function isSerializedEditorGroup(obj?: unknown): obj is ISerializedEditorGroup {
|
||||
const group = obj as ISerializedEditorGroup;
|
||||
|
||||
return obj && typeof obj === 'object' && Array.isArray(group.editors) && Array.isArray(group.mru);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { basename } from 'vs/base/common/resources';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { WorkspaceFileEdit } from 'vs/editor/common/modes';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
// --- VIEW MODEL
|
||||
|
||||
@@ -420,6 +421,12 @@ export class CategoryElementRenderer implements ITreeRenderer<CategoryElement, F
|
||||
const className = ThemeIcon.asClassName(metadata.iconPath);
|
||||
template.icon.className = className ? `theme-icon ${className}` : '';
|
||||
|
||||
} else if (URI.isUri(metadata.iconPath)) {
|
||||
// background-image
|
||||
template.icon.className = 'uri-icon';
|
||||
template.icon.style.setProperty('--background-dark', `url("${metadata.iconPath.toString(true)}")`);
|
||||
template.icon.style.setProperty('--background-light', `url("${metadata.iconPath.toString(true)}")`);
|
||||
|
||||
} else if (metadata.iconPath) {
|
||||
// background-image
|
||||
template.icon.className = 'uri-icon';
|
||||
|
||||
@@ -7,7 +7,7 @@ import { coalesce } from 'vs/base/common/arrays';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { basename, isEqual } from 'vs/base/common/resources';
|
||||
import { basename, isEqual, extname } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as nls from 'vs/nls';
|
||||
@@ -180,21 +180,62 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
|
||||
}
|
||||
}
|
||||
|
||||
const resourceExt = extname(resource);
|
||||
|
||||
const items = customEditors.allEditors.map((editorDescriptor): IQuickPickItem => ({
|
||||
label: editorDescriptor.displayName,
|
||||
id: editorDescriptor.id,
|
||||
description: editorDescriptor.id === currentlyOpenedEditorType
|
||||
? nls.localize('openWithCurrentlyActive', "Currently Active")
|
||||
: undefined
|
||||
: undefined,
|
||||
buttons: resourceExt ? [{
|
||||
iconClass: 'codicon-settings-gear',
|
||||
tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt)
|
||||
}] : undefined
|
||||
}));
|
||||
const pick = await this.quickInputService.pick(items, {
|
||||
placeHolder: nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", basename(resource)),
|
||||
|
||||
const picker = this.quickInputService.createQuickPick();
|
||||
picker.items = items;
|
||||
picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", basename(resource));
|
||||
|
||||
const pick = await new Promise<string | undefined>(resolve => {
|
||||
picker.onDidAccept(() => {
|
||||
resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0].id : undefined);
|
||||
picker.dispose();
|
||||
});
|
||||
picker.onDidTriggerItemButton(e => {
|
||||
const pick = e.item.id;
|
||||
resolve(pick); // open the view
|
||||
picker.dispose();
|
||||
|
||||
// And persist the setting
|
||||
if (pick) {
|
||||
const newAssociation: CustomEditorAssociation = { viewType: pick, filenamePattern: '*' + resourceExt };
|
||||
const currentAssociations = [...this.configurationService.getValue<CustomEditorsAssociations>(customEditorsAssociationsKey)] || [];
|
||||
|
||||
// First try updating existing association
|
||||
for (let i = 0; i < currentAssociations.length; ++i) {
|
||||
const existing = currentAssociations[i];
|
||||
if (existing.filenamePattern === newAssociation.filenamePattern) {
|
||||
currentAssociations.splice(i, 1, newAssociation);
|
||||
this.configurationService.updateValue(customEditorsAssociationsKey, currentAssociations);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, create a new one
|
||||
currentAssociations.unshift(newAssociation);
|
||||
this.configurationService.updateValue(customEditorsAssociationsKey, currentAssociations);
|
||||
}
|
||||
});
|
||||
picker.show();
|
||||
});
|
||||
|
||||
if (!pick || !pick.id) {
|
||||
if (!pick) {
|
||||
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
return this.openWith(resource, pick.id, options, group);
|
||||
|
||||
return this.openWith(resource, pick, options, group);
|
||||
}
|
||||
|
||||
public openWith(
|
||||
@@ -312,7 +353,11 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
|
||||
|
||||
export const customEditorsAssociationsKey = 'workbench.experimental.editorAssociations';
|
||||
|
||||
export type CustomEditorsAssociations = readonly (CustomEditorSelector & { readonly viewType: string; })[];
|
||||
export type CustomEditorAssociation = CustomEditorSelector & {
|
||||
readonly viewType: string;
|
||||
};
|
||||
|
||||
export type CustomEditorsAssociations = readonly CustomEditorAssociation[];
|
||||
|
||||
export class CustomEditorContribution extends Disposable implements IWorkbenchContribution {
|
||||
constructor(
|
||||
|
||||
@@ -915,14 +915,13 @@ export class DebugSession implements IDebugSession {
|
||||
// Disconnects and clears state. Session can be initialized again for a new connection.
|
||||
private shutdown(): void {
|
||||
dispose(this.rawListeners);
|
||||
if (this.raw) {
|
||||
this.raw.disconnect();
|
||||
this.raw.dispose();
|
||||
this.raw = undefined;
|
||||
}
|
||||
this.fetchThreadsScheduler = undefined;
|
||||
this.model.clearThreads(this.getId(), true);
|
||||
if (this.raw) {
|
||||
const raw = this.raw;
|
||||
this.raw = undefined;
|
||||
raw.disconnect();
|
||||
raw.dispose();
|
||||
}
|
||||
this._onDidChangeState.fire();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,31 +13,6 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.debug-pane .debug-start-view {
|
||||
padding: 0 20px 0 20px;
|
||||
}
|
||||
|
||||
.debug-pane .debug-start-view .monaco-button,
|
||||
.debug-pane .debug-start-view .section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.debug-pane .debug-start-view .top-section {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.debug-pane .debug-start-view .monaco-button {
|
||||
max-width: 260px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.debug-pane .debug-start-view .click {
|
||||
cursor: pointer;
|
||||
color: #007ACC;
|
||||
}
|
||||
|
||||
.monaco-workbench .debug-action.notification:after {
|
||||
content: '';
|
||||
width: 6px;
|
||||
|
||||
@@ -3,64 +3,33 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { StartAction, ConfigureAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IViewDescriptorService, IViewsRegistry, Extensions } from 'vs/workbench/common/views';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
const $ = dom.$;
|
||||
import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { OpenFolderAction, OpenFileAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
interface DebugStartMetrics {
|
||||
debuggers?: string[];
|
||||
}
|
||||
type DebugStartMetricsClassification = {
|
||||
debuggers?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
|
||||
function createClickElement(textContent: string, action: () => any): HTMLSpanElement {
|
||||
const clickElement = $('span.click');
|
||||
clickElement.textContent = textContent;
|
||||
clickElement.onclick = action;
|
||||
clickElement.tabIndex = 0;
|
||||
clickElement.onkeyup = (e) => {
|
||||
const keyboardEvent = new StandardKeyboardEvent(e);
|
||||
if (keyboardEvent.keyCode === KeyCode.Enter || (keyboardEvent.keyCode === KeyCode.Space)) {
|
||||
action();
|
||||
}
|
||||
};
|
||||
|
||||
return clickElement;
|
||||
}
|
||||
const CONTEXT_DEBUGGER_INTERESTED = new RawContextKey<boolean>('debuggerInterested', false);
|
||||
|
||||
export class StartView extends ViewPane {
|
||||
|
||||
static ID = 'workbench.debug.startView';
|
||||
static LABEL = localize('start', "Start");
|
||||
|
||||
private debugButton!: Button;
|
||||
private firstMessageContainer!: HTMLElement;
|
||||
private secondMessageContainer!: HTMLElement;
|
||||
private clickElement: HTMLElement | undefined;
|
||||
private debuggerLabels: string[] | undefined = undefined;
|
||||
private debuggerInterestedContext: IContextKey<boolean>;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@@ -69,125 +38,45 @@ export class StartView extends ViewPane {
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@IFileDialogService private readonly dialogService: IFileDialogService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
) {
|
||||
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService);
|
||||
this._register(editorService.onDidActiveEditorChange(() => this.updateView()));
|
||||
this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(() => this.updateView()));
|
||||
|
||||
this.debuggerInterestedContext = CONTEXT_DEBUGGER_INTERESTED.bindTo(contextKeyService);
|
||||
const setContextKey = () => {
|
||||
const activeEditor = this.editorService.activeTextEditorWidget;
|
||||
const debuggerLabels = this.debugService.getConfigurationManager().getDebuggerLabelsForEditor(activeEditor);
|
||||
this.debuggerInterestedContext.set(debuggerLabels.length > 0);
|
||||
};
|
||||
this._register(editorService.onDidActiveEditorChange(setContextKey));
|
||||
this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(setContextKey));
|
||||
}
|
||||
|
||||
private updateView(): void {
|
||||
const activeEditor = this.editorService.activeTextEditorWidget;
|
||||
const debuggerLabels = this.debugService.getConfigurationManager().getDebuggerLabelsForEditor(activeEditor);
|
||||
if (!equals(this.debuggerLabels, debuggerLabels)) {
|
||||
this.debuggerLabels = debuggerLabels;
|
||||
const enabled = this.debuggerLabels.length > 0;
|
||||
|
||||
this.debugButton.enabled = enabled;
|
||||
const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID);
|
||||
let debugLabel = this.debuggerLabels.length !== 1 ? localize('debug', "Run and Debug") : localize('debugWith', "Run and Debug {0}", this.debuggerLabels[0]);
|
||||
if (debugKeybinding) {
|
||||
debugLabel += ` (${debugKeybinding.getLabel()})`;
|
||||
}
|
||||
this.debugButton.label = debugLabel;
|
||||
|
||||
const emptyWorkbench = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY;
|
||||
this.firstMessageContainer.innerHTML = '';
|
||||
this.secondMessageContainer.innerHTML = '';
|
||||
const secondMessageElement = $('span');
|
||||
this.secondMessageContainer.appendChild(secondMessageElement);
|
||||
|
||||
const setSecondMessage = () => {
|
||||
secondMessageElement.textContent = localize('specifyHowToRun', "To customize Run and Debug");
|
||||
this.clickElement = createClickElement(localize('configure', " create a launch.json file."), () => {
|
||||
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.configure', { debuggers: this.debuggerLabels });
|
||||
this.commandService.executeCommand(ConfigureAction.ID);
|
||||
});
|
||||
this.secondMessageContainer.appendChild(this.clickElement);
|
||||
};
|
||||
const setSecondMessageWithFolder = () => {
|
||||
secondMessageElement.textContent = localize('noLaunchConfiguration', "To customize Run and Debug, ");
|
||||
this.clickElement = createClickElement(localize('openFolder', " open a folder"), () => {
|
||||
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.openFolder', { debuggers: this.debuggerLabels });
|
||||
this.dialogService.pickFolderAndOpen({ forceNewWindow: false });
|
||||
});
|
||||
this.secondMessageContainer.appendChild(this.clickElement);
|
||||
|
||||
const moreText = $('span.moreText');
|
||||
moreText.textContent = localize('andconfigure', " and create a launch.json file.");
|
||||
this.secondMessageContainer.appendChild(moreText);
|
||||
};
|
||||
|
||||
if (enabled && !emptyWorkbench) {
|
||||
setSecondMessage();
|
||||
}
|
||||
|
||||
if (enabled && emptyWorkbench) {
|
||||
setSecondMessageWithFolder();
|
||||
}
|
||||
|
||||
if (!enabled && !emptyWorkbench) {
|
||||
const firstMessageElement = $('span');
|
||||
this.firstMessageContainer.appendChild(firstMessageElement);
|
||||
firstMessageElement.textContent = localize('simplyDebugAndRun', "Open a file which can be debugged or run.");
|
||||
|
||||
setSecondMessage();
|
||||
}
|
||||
|
||||
if (!enabled && emptyWorkbench) {
|
||||
this.clickElement = createClickElement(localize('openFile', "Open a file"), () => {
|
||||
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.openFile');
|
||||
this.dialogService.pickFileAndOpen({ forceNewWindow: false });
|
||||
});
|
||||
this.firstMessageContainer.appendChild(this.clickElement);
|
||||
const firstMessageElement = $('span');
|
||||
this.firstMessageContainer.appendChild(firstMessageElement);
|
||||
firstMessageElement.textContent = localize('canBeDebuggedOrRun', " which can be debugged or run.");
|
||||
|
||||
setSecondMessageWithFolder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
this.firstMessageContainer = $('.top-section');
|
||||
container.appendChild(this.firstMessageContainer);
|
||||
|
||||
this.debugButton = new Button(container);
|
||||
this._register(this.debugButton.onDidClick(() => {
|
||||
this.commandService.executeCommand(StartAction.ID);
|
||||
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.runAndDebug', { debuggers: this.debuggerLabels });
|
||||
}));
|
||||
attachButtonStyler(this.debugButton, this.themeService);
|
||||
|
||||
dom.addClass(this.element, 'debug-pane');
|
||||
dom.addClass(container, 'debug-start-view');
|
||||
|
||||
this.secondMessageContainer = $('.section');
|
||||
container.appendChild(this.secondMessageContainer);
|
||||
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
protected layoutBody(_: number, __: number): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
if (this.debugButton.enabled) {
|
||||
this.debugButton.focus();
|
||||
} else if (this.clickElement) {
|
||||
this.clickElement.focus();
|
||||
}
|
||||
shouldShowWelcome(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID),
|
||||
when: CONTEXT_DEBUGGER_INTERESTED.toNegated()
|
||||
});
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('runAndDebugAction', "[Run and Debug](command:{0})", StartAction.ID)
|
||||
});
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID),
|
||||
when: WorkbenchStateContext.notEqualsTo('empty')
|
||||
});
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID),
|
||||
when: WorkbenchStateContext.isEqualTo('empty')
|
||||
});
|
||||
|
||||
@@ -1773,6 +1773,16 @@ declare module DebugProtocol {
|
||||
If missing the value 0 is assumed which results in the completion text being inserted.
|
||||
*/
|
||||
length?: number;
|
||||
/** Determines the start of the new selection after the text has been inserted (or replaced).
|
||||
The start position must in the range 0 and length of the completion text.
|
||||
If omitted the selection starts at the end of the completion text.
|
||||
*/
|
||||
selectionStart?: number;
|
||||
/** Determines the length of the new selection after the text has been inserted (or replaced).
|
||||
The selection can not extend beyond the bounds of the completion text.
|
||||
If omitted the length is assumed to be 0.
|
||||
*/
|
||||
selectionLength?: number;
|
||||
}
|
||||
|
||||
/** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. */
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
|
||||
import {
|
||||
OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction,
|
||||
@@ -48,6 +48,8 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
|
||||
// Singletons
|
||||
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
|
||||
@@ -443,6 +445,33 @@ registerAction2(class extends Action2 {
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: TOGGLE_IGNORE_EXTENSION_ACTION_ID,
|
||||
title: { value: localize('workbench.extensions.action.toggleIgnoreExtension', "Don't Sync This Extension"), original: `Don't Sync This Extension` },
|
||||
menu: {
|
||||
id: MenuId.ExtensionContext,
|
||||
group: '2_configure',
|
||||
when: CONTEXT_SYNC_ENABLEMENT
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, id: string) {
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const ignoredExtensions = [...configurationService.getValue<string[]>('sync.ignoredExtensions')];
|
||||
const index = ignoredExtensions.findIndex(ignoredExtension => areSameExtensions({ id: ignoredExtension }, { id }));
|
||||
if (index !== -1) {
|
||||
ignoredExtensions.splice(index, 1);
|
||||
} else {
|
||||
ignoredExtensions.push(id);
|
||||
}
|
||||
return configurationService.updateValue('sync.ignoredExtensions', ignoredExtensions.length ? ignoredExtensions : undefined, ConfigurationTarget.USER);
|
||||
}
|
||||
});
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
|
||||
class ExtensionsContributions implements IWorkbenchContribution {
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as json from 'vs/base/common/json';
|
||||
import { ActionViewItem, Separator, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
|
||||
import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
@@ -679,7 +679,7 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, extension: IExtension | undefined | null): ExtensionAction[][] {
|
||||
export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, instantiationService: IInstantiationService, extension: IExtension | undefined | null): ExtensionAction[][] {
|
||||
const scopedContextKeyService = contextKeyService.createScoped();
|
||||
if (extension) {
|
||||
scopedContextKeyService.createKey<boolean>('isBuiltinExtension', extension.type === ExtensionType.System);
|
||||
@@ -691,7 +691,7 @@ export function getContextMenuActions(menuService: IMenuService, contextKeyServi
|
||||
|
||||
const groups: ExtensionAction[][] = [];
|
||||
const menu = menuService.createMenu(MenuId.ExtensionContext, scopedContextKeyService);
|
||||
menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => new MenuItemExtensionAction(action))));
|
||||
menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => instantiationService.createInstance(MenuItemExtensionAction, action))));
|
||||
menu.dispose();
|
||||
|
||||
return groups;
|
||||
@@ -745,7 +745,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction {
|
||||
groups.push([this.instantiationService.createInstance(UninstallAction)]);
|
||||
groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]);
|
||||
|
||||
getContextMenuActions(this.menuService, this.contextKeyService, this.extension).forEach(actions => groups.push(actions));
|
||||
getContextMenuActions(this.menuService, this.contextKeyService, this.instantiationService, this.extension).forEach(actions => groups.push(actions));
|
||||
|
||||
groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension));
|
||||
|
||||
@@ -773,11 +773,21 @@ export class ManageExtensionAction extends ExtensionDropDownAction {
|
||||
|
||||
export class MenuItemExtensionAction extends ExtensionAction {
|
||||
|
||||
constructor(private readonly action: IAction) {
|
||||
constructor(
|
||||
private readonly action: IAction,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super(action.id, action.label);
|
||||
}
|
||||
|
||||
update() { }
|
||||
update() {
|
||||
if (!this.extension) {
|
||||
return;
|
||||
}
|
||||
if (this.action.id === TOGGLE_IGNORE_EXTENSION_ACTION_ID) {
|
||||
this.checked = this.configurationService.getValue<string[]>('sync.ignoredExtensions').some(id => areSameExtensions({ id }, this.extension!.identifier));
|
||||
}
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (this.extension) {
|
||||
|
||||
@@ -247,7 +247,7 @@ export class ExtensionsListView extends ViewPane {
|
||||
getActions: () => actions.slice(0, actions.length - 1)
|
||||
});
|
||||
} else if (e.element) {
|
||||
const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), e.element);
|
||||
const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), this.instantiationService, e.element);
|
||||
groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = e.element!));
|
||||
let actions: IAction[] = [];
|
||||
for (const menuActions of groups) {
|
||||
|
||||
@@ -143,3 +143,5 @@ export class ExtensionContainers extends Disposable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_IGNORE_EXTENSION_ACTION_ID = 'workbench.extensions.action.toggleIgnoreExtension';
|
||||
|
||||
@@ -13,13 +13,15 @@ export class ExtensionsInput extends EditorInput {
|
||||
static readonly ID = 'workbench.extensions.input2';
|
||||
get extension(): IExtension { return this._extension; }
|
||||
|
||||
readonly resource = URI.from({
|
||||
scheme: 'extension',
|
||||
path: this.extension.identifier.id
|
||||
});
|
||||
get resource() {
|
||||
return URI.from({
|
||||
scheme: 'extension',
|
||||
path: this.extension.identifier.id
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _extension: IExtension,
|
||||
private readonly _extension: IExtension
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -16,10 +16,6 @@ export class RuntimeExtensionsInput extends EditorInput {
|
||||
path: 'default'
|
||||
});
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
return RuntimeExtensionsInput.ID;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEditorViewState } from 'vs/editor/common/editorCommon';
|
||||
import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor';
|
||||
import { ITextFileService, ModelState } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, TextFileEditorModelState } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
@@ -62,9 +62,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
|
||||
this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e)));
|
||||
|
||||
// Ensure dirty text file and untitled models are always opened as editors
|
||||
this._register(this.textFileService.files.onDidChangeDirty(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource)));
|
||||
this._register(this.textFileService.files.onDidSaveError(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource)));
|
||||
this._register(this.textFileService.untitled.onDidChangeDirty(r => this.ensureDirtyFilesAreOpenedWorker.work(r)));
|
||||
this._register(this.textFileService.files.onDidChangeDirty(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource)));
|
||||
this._register(this.textFileService.files.onDidSaveError(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource)));
|
||||
this._register(this.textFileService.untitled.onDidChangeDirty(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource)));
|
||||
|
||||
// Out of workspace file watchers
|
||||
this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange()));
|
||||
@@ -290,7 +290,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
|
||||
}
|
||||
|
||||
const model = this.textFileService.files.get(resource);
|
||||
if (model?.hasState(ModelState.PENDING_SAVE)) {
|
||||
if (model?.hasState(TextFileEditorModelState.PENDING_SAVE)) {
|
||||
return false; // resource must not be pending to save
|
||||
}
|
||||
|
||||
@@ -369,18 +369,14 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
|
||||
}
|
||||
|
||||
const model = this.textFileService.files.get(resource);
|
||||
if (!model) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (model.isDirty()) {
|
||||
if (!model || model.isDirty() || !model.isResolved()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return model;
|
||||
})),
|
||||
model => model.resource.toString()
|
||||
).forEach(model => model.load());
|
||||
).forEach(model => this.textFileService.files.resolve(model.resource, { reload: { async: true } }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { isValidBasename } from 'vs/base/common/extpath';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files';
|
||||
import { ITextFileEditorModel, ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
|
||||
import { EditorOptions, TextEditorOptions, IEditorCloseEvent } from 'vs/workbench/common/editor';
|
||||
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
|
||||
@@ -135,7 +135,7 @@ export class TextFileEditor extends BaseTextEditor {
|
||||
return this.openAsBinary(input, options);
|
||||
}
|
||||
|
||||
const textFileModel = <ITextFileEditorModel>resolvedModel;
|
||||
const textFileModel = resolvedModel;
|
||||
|
||||
// Editor
|
||||
const textEditor = assertIsDefined(this.getControl());
|
||||
|
||||
@@ -221,13 +221,11 @@ class DoNotShowResolveConflictLearnMoreAction extends Action {
|
||||
super('workbench.files.action.resolveConflictLearnMoreDoNotShowAgain', nls.localize('dontShowAgain', "Don't Show Again"));
|
||||
}
|
||||
|
||||
run(notification: IDisposable): Promise<any> {
|
||||
async run(notification: IDisposable): Promise<any> {
|
||||
this.storageService.store(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, true, StorageScope.GLOBAL);
|
||||
|
||||
// Hide notification
|
||||
notification.dispose();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,8 +260,6 @@ class ResolveSaveConflictAction extends Action {
|
||||
Event.once(handle.onDidClose)(() => dispose(actions.primary!));
|
||||
pendingResolveSaveConflictMessages.push(handle);
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +272,7 @@ class SaveElevatedAction extends Action {
|
||||
super('workbench.files.action.saveElevated', triedToMakeWriteable ? isWindows ? nls.localize('overwriteElevated', "Overwrite as Admin...") : nls.localize('overwriteElevatedSudo', "Overwrite as Sudo...") : isWindows ? nls.localize('saveElevated', "Retry as Admin...") : nls.localize('saveElevatedSudo', "Retry as Sudo..."));
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
if (!this.model.isDisposed()) {
|
||||
this.model.save({
|
||||
writeElevated: true,
|
||||
@@ -284,8 +280,6 @@ class SaveElevatedAction extends Action {
|
||||
reason: SaveReason.EXPLICIT
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,12 +291,10 @@ class OverwriteReadonlyAction extends Action {
|
||||
super('workbench.files.action.overwrite', nls.localize('overwrite', "Overwrite"));
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
if (!this.model.isDisposed()) {
|
||||
this.model.save({ overwriteReadonly: true, reason: SaveReason.EXPLICIT });
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,12 +306,10 @@ class SaveIgnoreModifiedSinceAction extends Action {
|
||||
super('workbench.files.action.saveIgnoreModifiedSince', nls.localize('overwrite', "Overwrite"));
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
if (!this.model.isDisposed()) {
|
||||
this.model.save({ ignoreModifiedSince: true, reason: SaveReason.EXPLICIT });
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,10 +321,8 @@ class ConfigureSaveConflictAction extends Action {
|
||||
super('workbench.files.action.configureSaveConflict', nls.localize('configure', "Configure"));
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.preferencesService.openSettings(undefined, 'files.saveConflictResolution');
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -34,6 +34,9 @@ import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { WorkbenchStateContext, RemoteNameContext, IsWebContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { AddRootFolderAction, OpenFolderAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
@@ -61,6 +64,23 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor
|
||||
|
||||
private registerViews(): void {
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
|
||||
content: localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID),
|
||||
when: WorkbenchStateContext.isEqualTo('workspace')
|
||||
});
|
||||
|
||||
const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID;
|
||||
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
|
||||
content: localize('remoteNoFolderHelp', "Connected to remote.\n[Open Folder](command:{0})", commandId),
|
||||
when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated())
|
||||
});
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
|
||||
content: localize('noFolderHelp', "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId),
|
||||
when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext))
|
||||
});
|
||||
|
||||
const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER);
|
||||
|
||||
let viewDescriptorsToRegister: IViewDescriptor[] = [];
|
||||
|
||||
@@ -166,7 +166,7 @@ export class GlobalNewUntitledFileAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
|
||||
async function deleteFiles(workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
|
||||
let primaryButton: string;
|
||||
if (useTrash) {
|
||||
primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash");
|
||||
@@ -174,20 +174,24 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyF
|
||||
primaryButton = nls.localize({ key: 'deleteButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete");
|
||||
}
|
||||
|
||||
const distinctElements = resources.distinctParents(elements, e => e.resource);
|
||||
|
||||
// Handle dirty
|
||||
const distinctElements = resources.distinctParents(elements, e => e.resource);
|
||||
const dirtyWorkingCopies = new Set<IWorkingCopy>();
|
||||
for (const distinctElement of distinctElements) {
|
||||
for (const dirtyWorkingCopy of workingCopyFileService.getDirty(distinctElement.resource)) {
|
||||
dirtyWorkingCopies.add(dirtyWorkingCopy);
|
||||
}
|
||||
}
|
||||
let confirmed = true;
|
||||
const dirtyWorkingCopies = workingCopyService.dirtyWorkingCopies.filter(workingCopy => distinctElements.some(e => resources.isEqualOrParent(workingCopy.resource, e.resource)));
|
||||
if (dirtyWorkingCopies.length) {
|
||||
if (dirtyWorkingCopies.size) {
|
||||
let message: string;
|
||||
if (distinctElements.length > 1) {
|
||||
message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?");
|
||||
} else if (distinctElements[0].isDirectory) {
|
||||
if (dirtyWorkingCopies.length === 1) {
|
||||
if (dirtyWorkingCopies.size === 1) {
|
||||
message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder {0} with unsaved changes in 1 file. Do you want to continue?", distinctElements[0].name);
|
||||
} else {
|
||||
message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder {0} with unsaved changes in {1} files. Do you want to continue?", distinctElements[0].name, dirtyWorkingCopies.length);
|
||||
message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder {0} with unsaved changes in {1} files. Do you want to continue?", distinctElements[0].name, dirtyWorkingCopies.size);
|
||||
}
|
||||
} else {
|
||||
message = nls.localize('dirtyMessageFileDelete', "You are deleting {0} with unsaved changes. Do you want to continue?", distinctElements[0].name);
|
||||
@@ -204,7 +208,6 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyF
|
||||
confirmed = false;
|
||||
} else {
|
||||
skipConfirm = true;
|
||||
await Promise.all(dirtyWorkingCopies.map(dirty => dirty.revert()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +299,7 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyF
|
||||
|
||||
skipConfirm = true;
|
||||
|
||||
return deleteFiles(workingCopyService, workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm);
|
||||
return deleteFiles(workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -989,7 +992,7 @@ export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => {
|
||||
const explorerService = accessor.get(IExplorerService);
|
||||
const stats = explorerService.getContext(true).filter(s => !s.isRoot);
|
||||
if (stats.length) {
|
||||
await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
|
||||
await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -998,7 +1001,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => {
|
||||
const stats = explorerService.getContext(true).filter(s => !s.isRoot);
|
||||
|
||||
if (stats.length) {
|
||||
await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
|
||||
await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -66,17 +66,6 @@
|
||||
border-radius: 0; /* goes better when ellipsis shows up on narrow sidebar */
|
||||
}
|
||||
|
||||
.explorer-viewlet .explorer-empty-view {
|
||||
padding: 0 20px 0 20px;
|
||||
}
|
||||
|
||||
.explorer-viewlet .explorer-empty-view .monaco-button {
|
||||
max-width: 260px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.explorer-viewlet .explorer-item.nonexistent-root {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -4,13 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { OpenFolderAction, AddRootFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -20,10 +15,7 @@ import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/vie
|
||||
import { ResourcesDropHandler, DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { listDropBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
@@ -33,9 +25,6 @@ export class EmptyView extends ViewPane {
|
||||
static readonly ID: string = 'workbench.explorer.emptyView';
|
||||
static readonly NAME = nls.localize('noWorkspace', "No Folder Opened");
|
||||
|
||||
private button!: Button;
|
||||
private messageElement!: HTMLElement;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@@ -45,55 +34,31 @@ export class EmptyView extends ViewPane {
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,
|
||||
@ILabelService private labelService: ILabelService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IOpenerService openerService: IOpenerService
|
||||
) {
|
||||
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: No Folder Opened") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService);
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(() => this.setLabels()));
|
||||
this._register(this.labelService.onDidChangeFormatters(() => this.setLabels()));
|
||||
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(() => this.refreshTitle()));
|
||||
this._register(this.labelService.onDidChangeFormatters(() => this.refreshTitle()));
|
||||
}
|
||||
|
||||
shouldShowWelcome(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
DOM.addClass(container, 'explorer-empty-view');
|
||||
container.tabIndex = 0;
|
||||
|
||||
const messageContainer = document.createElement('div');
|
||||
DOM.addClass(messageContainer, 'section');
|
||||
container.appendChild(messageContainer);
|
||||
|
||||
this.messageElement = document.createElement('p');
|
||||
messageContainer.appendChild(this.messageElement);
|
||||
|
||||
this.button = new Button(messageContainer);
|
||||
attachButtonStyler(this.button, this.themeService);
|
||||
|
||||
this._register(this.button.onDidClick(() => {
|
||||
if (!this.actionRunner) {
|
||||
return;
|
||||
}
|
||||
const action = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE
|
||||
? this.instantiationService.createInstance(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL)
|
||||
: this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL);
|
||||
this.actionRunner.run(action).then(() => {
|
||||
action.dispose();
|
||||
}, err => {
|
||||
action.dispose();
|
||||
errors.onUnexpectedError(err);
|
||||
});
|
||||
}));
|
||||
|
||||
this._register(new DragAndDropObserver(container, {
|
||||
onDrop: e => {
|
||||
const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND);
|
||||
container.style.backgroundColor = color ? color.toString() : '';
|
||||
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true });
|
||||
dropHandler.handleDrop(e, () => undefined, targetGroup => undefined);
|
||||
dropHandler.handleDrop(e, () => undefined, () => undefined);
|
||||
},
|
||||
onDragEnter: (e) => {
|
||||
onDragEnter: () => {
|
||||
const color = this.themeService.getTheme().getColor(listDropBackground);
|
||||
container.style.backgroundColor = color ? color.toString() : '';
|
||||
},
|
||||
@@ -112,26 +77,13 @@ export class EmptyView extends ViewPane {
|
||||
}
|
||||
}));
|
||||
|
||||
this.setLabels();
|
||||
this.refreshTitle();
|
||||
}
|
||||
|
||||
private setLabels(): void {
|
||||
private refreshTitle(): void {
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
this.messageElement.textContent = nls.localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.");
|
||||
if (this.button) {
|
||||
this.button.label = nls.localize('addFolder', "Add Folder");
|
||||
}
|
||||
this.updateTitle(EmptyView.NAME);
|
||||
} else {
|
||||
if (this.environmentService.configuration.remoteAuthority && !isWeb) {
|
||||
const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.configuration.remoteAuthority);
|
||||
this.messageElement.textContent = hostLabel ? nls.localize('remoteNoFolderHelp', "Connected to {0}", hostLabel) : nls.localize('connecting', "Connecting...");
|
||||
} else {
|
||||
this.messageElement.textContent = nls.localize('noFolderHelp', "You have not yet opened a folder.");
|
||||
}
|
||||
if (this.button) {
|
||||
this.button.label = nls.localize('openFolder', "Open Folder");
|
||||
}
|
||||
this.updateTitle(this.title);
|
||||
}
|
||||
}
|
||||
@@ -139,9 +91,4 @@ export class EmptyView extends ViewPane {
|
||||
layoutBody(_size: number): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.button.element.focus();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { isNumber } from 'vs/base/common/types';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IEditableData } from 'vs/workbench/common/views';
|
||||
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
|
||||
export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> {
|
||||
|
||||
@@ -643,8 +642,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IWorkingCopyFileService private workingCopyFileService: IWorkingCopyFileService,
|
||||
@IHostService private hostService: IHostService,
|
||||
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
|
||||
@IWorkingCopyService private workingCopyService: IWorkingCopyService
|
||||
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService
|
||||
) {
|
||||
this.toDispose = [];
|
||||
|
||||
@@ -945,15 +943,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||
const sourceFile = resource;
|
||||
const targetFile = joinPath(target.resource, basename(sourceFile));
|
||||
|
||||
// if the target exists and is dirty, make sure to revert it. otherwise the dirty contents
|
||||
// of the target file would replace the contents of the added file. since we already
|
||||
// confirmed the overwrite before, this is OK.
|
||||
if (this.workingCopyService.isDirty(targetFile)) {
|
||||
await Promise.all(this.workingCopyService.getWorkingCopies(targetFile).map(workingCopy => workingCopy.revert({ soft: true })));
|
||||
}
|
||||
|
||||
const copyTarget = joinPath(target.resource, basename(sourceFile));
|
||||
const stat = await this.workingCopyFileService.copy(sourceFile, copyTarget, true);
|
||||
const stat = await this.workingCopyFileService.copy(sourceFile, targetFile, true);
|
||||
// if we only add one file, just open it directly
|
||||
if (resources.length === 1 && !stat.isDirectory) {
|
||||
this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
|
||||
|
||||
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { EncodingMode, IFileEditorInput, Verbosity, TextResourceEditorInput } from 'vs/workbench/common/editor';
|
||||
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
|
||||
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
|
||||
import { ITextFileService, ModelState, LoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, TextFileEditorModelState, TextFileLoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IReference, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
@@ -56,6 +56,8 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
|
||||
) {
|
||||
super(resource, editorService, editorGroupService, textFileService, labelService, fileService, filesConfigurationService);
|
||||
|
||||
this.model = this.textFileService.files.get(resource);
|
||||
|
||||
if (preferredEncoding) {
|
||||
this.setPreferredEncoding(preferredEncoding);
|
||||
}
|
||||
@@ -63,6 +65,11 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
|
||||
if (preferredMode) {
|
||||
this.setPreferredMode(preferredMode);
|
||||
}
|
||||
|
||||
// If a file model already exists, make sure to wire it in
|
||||
if (this.model) {
|
||||
this.registerModelListeners(this.model);
|
||||
}
|
||||
}
|
||||
|
||||
protected registerListeners(): void {
|
||||
@@ -98,10 +105,10 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
|
||||
this.modelListeners.add(model.onDidSaveError(() => this._onDidChangeDirty.fire()));
|
||||
|
||||
// remove model association once it gets disposed
|
||||
Event.once(model.onDispose)(() => {
|
||||
this.modelListeners.add(Event.once(model.onDispose)(() => {
|
||||
this.modelListeners.clear();
|
||||
this.model = undefined;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
getEncoding(): string | undefined {
|
||||
@@ -167,7 +174,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
|
||||
}
|
||||
|
||||
private decorateLabel(label: string): string {
|
||||
const orphaned = this.model?.hasState(ModelState.ORPHAN);
|
||||
const orphaned = this.model?.hasState(TextFileEditorModelState.ORPHAN);
|
||||
const readonly = this.isReadonly();
|
||||
|
||||
if (orphaned && readonly) {
|
||||
@@ -198,7 +205,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
|
||||
}
|
||||
|
||||
isSaving(): boolean {
|
||||
if (this.model?.hasState(ModelState.SAVED) || this.model?.hasState(ModelState.CONFLICT) || this.model?.hasState(ModelState.ERROR)) {
|
||||
if (this.model?.hasState(TextFileEditorModelState.SAVED) || this.model?.hasState(TextFileEditorModelState.CONFLICT) || this.model?.hasState(TextFileEditorModelState.ERROR)) {
|
||||
return false; // require the model to be dirty and not in conflict or error state
|
||||
}
|
||||
|
||||
@@ -234,7 +241,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
|
||||
encoding: this.preferredEncoding,
|
||||
reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model
|
||||
allowBinary: this.forceOpenAs === ForceOpenAs.Text,
|
||||
reason: LoadReason.EDITOR
|
||||
reason: TextFileLoadReason.EDITOR
|
||||
});
|
||||
|
||||
// This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary
|
||||
|
||||
@@ -28,10 +28,8 @@ export class LogViewerInput extends ResourceEditorInput {
|
||||
|
||||
static readonly ID = 'workbench.editorinputs.output';
|
||||
|
||||
readonly resource = this.outputChannelDescriptor.file;
|
||||
|
||||
constructor(
|
||||
private readonly outputChannelDescriptor: IFileOutputChannelDescriptor,
|
||||
outputChannelDescriptor: IFileOutputChannelDescriptor,
|
||||
@ITextModelService textModelResolverService: ITextModelService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
}
|
||||
|
||||
/* Deal with overflow */
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-value,
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-sibling {
|
||||
white-space: nowrap;
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-value,
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-sibling {
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-value {
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-value {
|
||||
max-width: 90%;
|
||||
}
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-sibling {
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-sibling {
|
||||
max-width: 10%;
|
||||
}
|
||||
|
||||
|
||||
@@ -1640,12 +1640,13 @@ class StopSyncingSettingAction extends Action {
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const currentValue = this.configService.getValue<string[]>('sync.ignoredSettings');
|
||||
let currentValue = [...this.configService.getValue<string[]>('sync.ignoredSettings')];
|
||||
if (this.checked) {
|
||||
this.configService.updateValue('sync.ignoredSettings', currentValue.filter(v => v !== this.setting.key));
|
||||
currentValue = currentValue.filter(v => v !== this.setting.key);
|
||||
} else {
|
||||
this.configService.updateValue('sync.ignoredSettings', [...currentValue, this.setting.key]);
|
||||
currentValue.push(this.setting.key);
|
||||
}
|
||||
this.configService.updateValue('sync.ignoredSettings', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER);
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
@@ -443,12 +443,12 @@ export class ListSettingWidget extends Disposable {
|
||||
|
||||
const onSubmit = (edited: boolean) => {
|
||||
this.model.setEditKey('none');
|
||||
const value = valueInput.value.trim();
|
||||
const value = valueInput.value;
|
||||
if (edited && !isUndefinedOrNull(value)) {
|
||||
this._onDidChangeList.fire({
|
||||
originalValue: item.value,
|
||||
value: value,
|
||||
sibling: siblingInput && siblingInput.value.trim(),
|
||||
sibling: siblingInput && siblingInput.value,
|
||||
targetIndex: idx
|
||||
});
|
||||
}
|
||||
|
||||
@@ -146,13 +146,16 @@ interface ResourceTemplate {
|
||||
disposables: IDisposable;
|
||||
}
|
||||
|
||||
class MultipleSelectionActionRunner extends ActionRunner {
|
||||
class RepositoryPaneActionRunner extends ActionRunner {
|
||||
|
||||
constructor(private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[]) {
|
||||
constructor(
|
||||
private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[],
|
||||
private focus: () => void
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
runAction(action: IAction, context: ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>): Promise<any> {
|
||||
async runAction(action: IAction, context: ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>): Promise<any> {
|
||||
if (!(action instanceof MenuItemAction)) {
|
||||
return super.runAction(action, context);
|
||||
}
|
||||
@@ -161,7 +164,8 @@ class MultipleSelectionActionRunner extends ActionRunner {
|
||||
const contextIsSelected = selection.some(s => s === context);
|
||||
const actualContext = contextIsSelected ? selection : [context];
|
||||
const args = flatten(actualContext.map(e => ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e]));
|
||||
return action.run(...args);
|
||||
await action.run(...args);
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +179,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
|
||||
private labels: ResourceLabels,
|
||||
private actionViewItemProvider: IActionViewItemProvider,
|
||||
private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[],
|
||||
private focus: () => void,
|
||||
private themeService: IThemeService,
|
||||
private menus: SCMMenus
|
||||
) { }
|
||||
@@ -186,7 +191,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
|
||||
const actionsContainer = append(fileLabel.element, $('.actions'));
|
||||
const actionBar = new ActionBar(actionsContainer, {
|
||||
actionViewItemProvider: this.actionViewItemProvider,
|
||||
actionRunner: new MultipleSelectionActionRunner(this.getSelectedResources)
|
||||
actionRunner: new RepositoryPaneActionRunner(this.getSelectedResources, this.focus)
|
||||
});
|
||||
|
||||
const decorationIcon = append(element, $('.decoration-icon'));
|
||||
@@ -730,7 +735,8 @@ export class RepositoryPane extends ViewPane {
|
||||
wrappingStrategy: 'advanced',
|
||||
wrappingIndent: 'none',
|
||||
padding: { top: 3, bottom: 3 },
|
||||
suggest: { showWords: false }
|
||||
suggest: { showWords: false },
|
||||
quickSuggestions: false
|
||||
};
|
||||
|
||||
const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
|
||||
@@ -820,7 +826,7 @@ export class RepositoryPane extends ViewPane {
|
||||
|
||||
const renderers = [
|
||||
new ResourceGroupRenderer(actionViewItemProvider, this.themeService, this.menus),
|
||||
new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), this.themeService, this.menus)
|
||||
new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), () => this.tree.domFocus(), this.themeService, this.menus)
|
||||
];
|
||||
|
||||
const filter = new SCMTreeFilter();
|
||||
@@ -1024,7 +1030,7 @@ export class RepositoryPane extends ViewPane {
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => actions,
|
||||
getActionsContext: () => element,
|
||||
actionRunner: new MultipleSelectionActionRunner(() => this.getSelectedResources())
|
||||
actionRunner: new RepositoryPaneActionRunner(() => this.getSelectedResources(), () => this.tree.domFocus())
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -104,14 +104,7 @@ export class ReplaceService implements IReplaceService {
|
||||
const edits: WorkspaceTextEdit[] = this.createEdits(arg, resource);
|
||||
await this.bulkEditorService.apply({ edits }, { progress });
|
||||
|
||||
return Promise.all(edits.map(e => {
|
||||
const model = this.textFileService.files.get(e.resource);
|
||||
if (model) {
|
||||
return model.save();
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}));
|
||||
return Promise.all(edits.map(e => this.textFileService.files.get(e.resource)?.save()));
|
||||
}
|
||||
|
||||
async openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<any> {
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as aria from 'vs/base/browser/ui/aria/aria';
|
||||
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IAction, ActionRunner } from 'vs/base/common/actions';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
@@ -213,7 +213,7 @@ export class SearchView extends ViewPane {
|
||||
this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE);
|
||||
|
||||
this._register(this.fileService.onDidFilesChange(e => this.onFilesChanged(e)));
|
||||
this._register(this.textFileService.untitled.onDidDisposeModel(e => this.onUntitledDidDispose(e)));
|
||||
this._register(this.textFileService.untitled.onDidDispose(model => this.onUntitledDidDispose(model.resource)));
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(() => this.onDidChangeWorkbenchState()));
|
||||
this._register(this.searchHistoryService.onDidClearHistory(() => this.clearHistory()));
|
||||
|
||||
@@ -1584,6 +1584,7 @@ export class SearchView extends ViewPane {
|
||||
const openFolderLink = dom.append(textEl,
|
||||
$('a.pointer.prominent', { tabindex: 0 }, nls.localize('openFolder', "Open Folder")));
|
||||
|
||||
const actionRunner = new ActionRunner();
|
||||
this.messageDisposables.push(dom.addDisposableListener(openFolderLink, dom.EventType.CLICK, (e: MouseEvent) => {
|
||||
dom.EventHelper.stop(e, false);
|
||||
|
||||
@@ -1591,7 +1592,7 @@ export class SearchView extends ViewPane {
|
||||
this.instantiationService.createInstance(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL) :
|
||||
this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL);
|
||||
|
||||
this.actionRunner!.run(action).then(() => {
|
||||
actionRunner.run(action).then(() => {
|
||||
action.dispose();
|
||||
}, err => {
|
||||
action.dispose();
|
||||
|
||||
@@ -53,8 +53,7 @@ class LanguageSurvey extends Disposable {
|
||||
// Process model-save event every 250ms to reduce load
|
||||
const onModelsSavedWorker = this._register(new RunOnceWorker<ITextFileEditorModel>(models => {
|
||||
models.forEach(m => {
|
||||
const model = modelService.getModel(m.resource);
|
||||
if (model && model.getModeId() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) {
|
||||
if (m.getMode() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) {
|
||||
const editedCount = storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) + 1;
|
||||
storageService.store(EDITED_LANGUAGE_COUNT_KEY, editedCount, StorageScope.GLOBAL);
|
||||
storageService.store(EDITED_LANGUAGE_DATE_KEY, date, StorageScope.GLOBAL);
|
||||
|
||||
@@ -19,7 +19,7 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry';
|
||||
import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { ITextFileService, ITextFileModelSaveEvent, ITextFileModelLoadEvent } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, ITextFileSaveEvent, ITextFileLoadEvent } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { extname, basename, isEqual, isEqualOrParent, joinPath } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
@@ -58,7 +58,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@ITextFileService textFileService: ITextFileService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -131,7 +131,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
|
||||
this._register(lifecycleService.onShutdown(() => this.dispose()));
|
||||
}
|
||||
|
||||
private onTextFileModelLoaded(e: ITextFileModelLoadEvent): void {
|
||||
private onTextFileModelLoaded(e: ITextFileLoadEvent): void {
|
||||
const settingsType = this.getTypeIfSettings(e.model.resource);
|
||||
if (settingsType) {
|
||||
type SettingsReadClassification = {
|
||||
@@ -146,7 +146,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
|
||||
}
|
||||
}
|
||||
|
||||
private onTextFileModelSaved(e: ITextFileModelSaveEvent): void {
|
||||
private onTextFileModelSaved(e: ITextFileSaveEvent): void {
|
||||
const settingsType = this.getTypeIfSettings(e.model.resource);
|
||||
if (settingsType) {
|
||||
type SettingsWrittenClassification = {
|
||||
|
||||
@@ -690,6 +690,8 @@ export class RunActiveFileInTerminalAction extends Action {
|
||||
if (!instance) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
await instance.processReady;
|
||||
|
||||
const editor = this.codeEditorService.getActiveCodeEditor();
|
||||
if (!editor || !editor.hasModel()) {
|
||||
return Promise.resolve(undefined);
|
||||
|
||||
@@ -21,7 +21,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IMenuItem, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey, ContextKeyRegexExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
@@ -31,7 +31,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { CONTEXT_SYNC_STATE, getSyncSourceFromRemoteContentResource, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { CONTEXT_SYNC_STATE, getSyncSourceFromRemoteContentResource, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
|
||||
import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
@@ -52,7 +52,6 @@ const enum AuthStatus {
|
||||
SignedOut = 'SignedOut',
|
||||
Unavailable = 'Unavailable'
|
||||
}
|
||||
const CONTEXT_SYNC_ENABLEMENT = new RawContextKey<boolean>('syncEnabled', false);
|
||||
const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey<string>('authTokenStatus', AuthStatus.Initializing);
|
||||
const CONTEXT_CONFLICTS_SOURCES = new RawContextKey<string>('conflictsSources', '');
|
||||
|
||||
@@ -547,7 +546,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
} else {
|
||||
await this.userDataSyncService.resetLocal();
|
||||
}
|
||||
await this.signOut();
|
||||
this.disableSync();
|
||||
}
|
||||
}
|
||||
@@ -574,13 +572,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
}
|
||||
|
||||
private async signOut(): Promise<void> {
|
||||
if (this.activeAccount) {
|
||||
await this.authenticationService.logout(this.userDataSyncStore!.authenticationProviderId, this.activeAccount.id);
|
||||
await this.setActiveAccount(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private getConflictsEditorInput(source: SyncSource): IEditorInput | undefined {
|
||||
const previewResource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource
|
||||
: source === SyncSource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource
|
||||
@@ -652,6 +643,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
},
|
||||
when: turnOnSyncWhenContext,
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
|
||||
group: '5_sync',
|
||||
command: {
|
||||
id: turnOnSyncCommandId,
|
||||
title: localize('global activity turn on sync', "Turn on Sync...")
|
||||
},
|
||||
when: turnOnSyncWhenContext,
|
||||
});
|
||||
|
||||
const signInCommandId = 'workbench.userData.actions.signin';
|
||||
const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut));
|
||||
@@ -697,6 +696,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
},
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT),
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
|
||||
group: '5_sync',
|
||||
command: {
|
||||
id: stopSyncCommandId,
|
||||
title: localize('global activity stop sync', "Turn off Sync")
|
||||
},
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT),
|
||||
});
|
||||
|
||||
const resolveSettingsConflictsCommandId = 'workbench.userData.actions.resolveSettingsConflicts';
|
||||
const resolveSettingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i);
|
||||
@@ -736,17 +743,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
when: resolveKeybindingsConflictsWhenContext,
|
||||
});
|
||||
|
||||
const signOutMenuItem: IMenuItem = {
|
||||
group: '5_sync',
|
||||
command: {
|
||||
id: 'workbench.userData.actions.signout',
|
||||
title: localize('sign out', "Sync: Sign out")
|
||||
},
|
||||
when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn)),
|
||||
};
|
||||
CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut());
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem);
|
||||
|
||||
const configureSyncCommandId = 'workbench.userData.actions.configureSync';
|
||||
CommandsRegistry.registerCommand(configureSyncCommandId, () => this.configureSyncOptions());
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
@@ -767,15 +763,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)),
|
||||
});
|
||||
|
||||
const resetLocalCommandId = 'workbench.userData.actions.resetLocal';
|
||||
CommandsRegistry.registerCommand(resetLocalCommandId, () => this.userDataSyncService.resetLocal());
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: resetLocalCommandId,
|
||||
title: localize('reset local', "Developer: Reset Local (Sync)")
|
||||
},
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,12 @@ export class WebviewInput extends EditorInput {
|
||||
private readonly _onDisposeWebview = this._register(new Emitter<void>());
|
||||
readonly onDisposeWebview = this._onDisposeWebview.event;
|
||||
|
||||
readonly resource = URI.from({
|
||||
scheme: WebviewPanelResourceScheme,
|
||||
path: `webview-panel/webview-${this.id}`
|
||||
});
|
||||
get resource() {
|
||||
return URI.from({
|
||||
scheme: WebviewPanelResourceScheme,
|
||||
path: `webview-panel/webview-${this.id}`
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
|
||||
@@ -53,10 +53,10 @@ export class WalkThroughInput extends EditorInput {
|
||||
private maxTopScroll = 0;
|
||||
private maxBottomScroll = 0;
|
||||
|
||||
readonly resource = this.options.resource;
|
||||
get resource() { return this.options.resource; }
|
||||
|
||||
constructor(
|
||||
private options: WalkThroughInputOptions,
|
||||
private readonly options: WalkThroughInputOptions,
|
||||
@ITextModelService private readonly textModelResolverService: ITextModelService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -289,10 +289,9 @@ export class ElectronWindow extends Disposable {
|
||||
|
||||
private updateDocumentEdited(isDirty = this.workingCopyService.hasDirty): void {
|
||||
if ((!this.isDocumentedEdited && isDirty) || (this.isDocumentedEdited && !isDirty)) {
|
||||
const hasDirtyFiles = this.workingCopyService.hasDirty;
|
||||
this.isDocumentedEdited = hasDirtyFiles;
|
||||
this.isDocumentedEdited = isDirty;
|
||||
|
||||
this.electronService.setDocumentEdited(hasDirtyFiles);
|
||||
this.electronService.setDocumentEdited(isDirty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,73 +5,6 @@
|
||||
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export class BrowserClipboardService implements IClipboardService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private _internalResourcesClipboard: URI[] | undefined;
|
||||
|
||||
async writeText(text: string, type?: string): Promise<void> {
|
||||
if (type) {
|
||||
return; // TODO@sbatten
|
||||
}
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
return navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
const activeElement = <HTMLElement>document.activeElement;
|
||||
const newTextarea = document.createElement('textarea');
|
||||
newTextarea.className = 'clipboard-copy';
|
||||
newTextarea.style.visibility = 'false';
|
||||
newTextarea.style.height = '1px';
|
||||
newTextarea.style.width = '1px';
|
||||
newTextarea.setAttribute('aria-hidden', 'true');
|
||||
newTextarea.style.position = 'absolute';
|
||||
newTextarea.style.top = '-1000';
|
||||
newTextarea.style.left = '-1000';
|
||||
document.body.appendChild(newTextarea);
|
||||
newTextarea.value = text;
|
||||
newTextarea.focus();
|
||||
newTextarea.select();
|
||||
document.execCommand('copy');
|
||||
activeElement.focus();
|
||||
document.body.removeChild(newTextarea);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async readText(type?: string): Promise<string> {
|
||||
if (type) {
|
||||
return ''; // TODO@sbatten
|
||||
}
|
||||
|
||||
return navigator.clipboard.readText();
|
||||
}
|
||||
|
||||
readTextSync(): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
readFindText(): string {
|
||||
// @ts-ignore
|
||||
return undefined;
|
||||
}
|
||||
|
||||
writeFindText(text: string): void { }
|
||||
|
||||
writeResources(resources: URI[]): void {
|
||||
this._internalResourcesClipboard = resources;
|
||||
}
|
||||
|
||||
readResources(): URI[] {
|
||||
return this._internalResourcesClipboard || [];
|
||||
}
|
||||
|
||||
hasResources(): boolean {
|
||||
return this._internalResourcesClipboard !== undefined && this._internalResourcesClipboard.length > 0;
|
||||
}
|
||||
}
|
||||
import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService';
|
||||
|
||||
registerSingleton(IClipboardService, BrowserClipboardService, true);
|
||||
|
||||
@@ -191,22 +191,6 @@ export class ProgressService extends Disposable implements IProgressService {
|
||||
}
|
||||
};
|
||||
|
||||
const createWindowProgress = () => {
|
||||
this.withWindowProgress<R>({
|
||||
location: ProgressLocation.Window,
|
||||
title: options.title
|
||||
}, progress => {
|
||||
if (progressStateModel.step) {
|
||||
progress.report(progressStateModel.step);
|
||||
}
|
||||
|
||||
const disposable = progressStateModel.onDidReport(step => progress.report(step));
|
||||
Event.once(progressStateModel.onDispose)(() => disposable.dispose());
|
||||
|
||||
return progressStateModel.promise;
|
||||
});
|
||||
};
|
||||
|
||||
const createNotification = (message: string, increment?: number): INotificationHandle => {
|
||||
const notificationDisposables = new DisposableStore();
|
||||
|
||||
@@ -254,18 +238,8 @@ export class ProgressService extends Disposable implements IProgressService {
|
||||
|
||||
updateProgress(handle, increment);
|
||||
|
||||
Event.once(handle.onDidClose)(() => {
|
||||
|
||||
// Switch to window based progress once the notification
|
||||
// is being closed even though still running and not
|
||||
// cancelled.
|
||||
if (!progressStateModel.done) {
|
||||
createWindowProgress();
|
||||
}
|
||||
|
||||
// Clear disposables
|
||||
notificationDisposables.dispose();
|
||||
});
|
||||
// Clear upon dispose
|
||||
Event.once(handle.onDidClose)(() => notificationDisposables.dispose());
|
||||
|
||||
return handle;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService';
|
||||
import { ITextFileService, IResourceEncodings, IResourceEncoding, ModelState } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, IResourceEncodings, IResourceEncoding, TextFileEditorModelState } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
@@ -24,7 +24,7 @@ export class BrowserTextFileService extends AbstractTextFileService {
|
||||
}
|
||||
|
||||
protected onBeforeShutdown(reason: ShutdownReason): boolean {
|
||||
if (this.files.getAll().some(model => model.hasState(ModelState.PENDING_SAVE))) {
|
||||
if (this.files.models.some(model => model.hasState(TextFileEditorModelState.PENDING_SAVE))) {
|
||||
console.warn('Unload prevented: pending file saves');
|
||||
|
||||
return true; // files are pending to be saved: veto
|
||||
|
||||
@@ -204,11 +204,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
||||
else {
|
||||
const model = this.files.get(resource);
|
||||
if (model) {
|
||||
|
||||
// Save with options
|
||||
await model.save(options);
|
||||
|
||||
return !model.isDirty() ? resource : undefined;
|
||||
return await model.save(options) ? resource : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,9 +378,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
||||
}
|
||||
|
||||
// save model
|
||||
await targetModel.save(options);
|
||||
|
||||
return true;
|
||||
return await targetModel.save(options);
|
||||
}
|
||||
|
||||
private async confirmOverwrite(resource: URI): Promise<boolean> {
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { ITextFileService, ModelState, ITextFileEditorModel, ITextFileStreamContent, ILoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, LoadReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileLoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, TextFileLoadReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/editor';
|
||||
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
|
||||
import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup';
|
||||
@@ -43,7 +43,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
private readonly _onDidChangeContent = this._register(new Emitter<void>());
|
||||
readonly onDidChangeContent = this._onDidChangeContent.event;
|
||||
|
||||
private readonly _onDidLoad = this._register(new Emitter<LoadReason>());
|
||||
private readonly _onDidLoad = this._register(new Emitter<TextFileLoadReason>());
|
||||
readonly onDidLoad = this._onDidLoad.event;
|
||||
|
||||
private readonly _onDidChangeDirty = this._register(new Emitter<void>());
|
||||
@@ -248,7 +248,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
|
||||
//#region Load
|
||||
|
||||
async load(options?: ILoadOptions): Promise<ITextFileEditorModel> {
|
||||
async load(options?: ITextFileLoadOptions): Promise<TextFileEditorModel> {
|
||||
this.logService.trace('[text file model] load() - enter', this.resource.toString());
|
||||
|
||||
// It is very important to not reload the model when the model is dirty.
|
||||
@@ -281,7 +281,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
return this.loadFromFile(options);
|
||||
}
|
||||
|
||||
private async loadFromBackup(backup: IResolvedBackup<IBackupMetaData>, options?: ILoadOptions): Promise<TextFileEditorModel> {
|
||||
private async loadFromBackup(backup: IResolvedBackup<IBackupMetaData>, options?: ITextFileLoadOptions): Promise<TextFileEditorModel> {
|
||||
|
||||
// Load with backup
|
||||
this.loadFromContent({
|
||||
@@ -303,7 +303,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
return this;
|
||||
}
|
||||
|
||||
private async loadFromFile(options?: ILoadOptions): Promise<TextFileEditorModel> {
|
||||
private async loadFromFile(options?: ITextFileLoadOptions): Promise<TextFileEditorModel> {
|
||||
const forceReadFromDisk = options?.forceReadFromDisk;
|
||||
const allowBinary = this.isResolved() /* always allow if we resolved previously */ || options?.allowBinary;
|
||||
|
||||
@@ -358,7 +358,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
}
|
||||
|
||||
private loadFromContent(content: ITextFileStreamContent, options?: ILoadOptions, fromBackup?: boolean): TextFileEditorModel {
|
||||
private loadFromContent(content: ITextFileStreamContent, options?: ITextFileLoadOptions, fromBackup?: boolean): TextFileEditorModel {
|
||||
this.logService.trace('[text file model] load() - resolved content', this.resource.toString());
|
||||
|
||||
// Update our resolved disk stat model
|
||||
@@ -396,7 +396,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
// Emit as event
|
||||
this._onDidLoad.fire(options?.reason ?? LoadReason.OTHER);
|
||||
this._onDidLoad.fire(options?.reason ?? TextFileLoadReason.OTHER);
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -544,7 +544,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
if (
|
||||
(this.hasState(ModelState.CONFLICT) || this.hasState(ModelState.ERROR)) &&
|
||||
(this.hasState(TextFileEditorModelState.CONFLICT) || this.hasState(TextFileEditorModelState.ERROR)) &&
|
||||
(options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)
|
||||
) {
|
||||
this.logService.trace('[text file model] save() - ignoring auto save request for model that is in conflict or error', this.resource.toString());
|
||||
@@ -794,19 +794,19 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
|
||||
//#endregion
|
||||
|
||||
hasState(state: ModelState): boolean {
|
||||
hasState(state: TextFileEditorModelState): boolean {
|
||||
switch (state) {
|
||||
case ModelState.CONFLICT:
|
||||
case TextFileEditorModelState.CONFLICT:
|
||||
return this.inConflictMode;
|
||||
case ModelState.DIRTY:
|
||||
case TextFileEditorModelState.DIRTY:
|
||||
return this.dirty;
|
||||
case ModelState.ERROR:
|
||||
case TextFileEditorModelState.ERROR:
|
||||
return this.inErrorMode;
|
||||
case ModelState.ORPHAN:
|
||||
case TextFileEditorModelState.ORPHAN:
|
||||
return this.inOrphanMode;
|
||||
case ModelState.PENDING_SAVE:
|
||||
case TextFileEditorModelState.PENDING_SAVE:
|
||||
return this.saveSequentializer.hasPending();
|
||||
case ModelState.SAVED:
|
||||
case TextFileEditorModelState.SAVED:
|
||||
return !this.dirty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
|
||||
import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ITextFileEditorModel, ITextFileEditorModelManager, IModelLoadOrCreateOptions, ITextFileModelLoadEvent, ITextFileModelSaveEvent, ITextFileSaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileEditorModel, ITextFileEditorModelManager, ITextFileEditorModelLoadOrCreateOptions, ITextFileLoadEvent, ITextFileSaveEvent, ITextFileSaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
@@ -29,27 +29,34 @@ import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
||||
|
||||
export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager {
|
||||
|
||||
private readonly _onDidCreate = this._register(new Emitter<ITextFileEditorModel>());
|
||||
private readonly _onDidCreate = this._register(new Emitter<TextFileEditorModel>());
|
||||
readonly onDidCreate = this._onDidCreate.event;
|
||||
|
||||
private readonly _onDidLoad = this._register(new Emitter<ITextFileModelLoadEvent>());
|
||||
private readonly _onDidLoad = this._register(new Emitter<ITextFileLoadEvent>());
|
||||
readonly onDidLoad = this._onDidLoad.event;
|
||||
|
||||
private readonly _onDidChangeDirty = this._register(new Emitter<ITextFileEditorModel>());
|
||||
private readonly _onDidChangeDirty = this._register(new Emitter<TextFileEditorModel>());
|
||||
readonly onDidChangeDirty = this._onDidChangeDirty.event;
|
||||
|
||||
private readonly _onDidSaveError = this._register(new Emitter<ITextFileEditorModel>());
|
||||
private readonly _onDidSaveError = this._register(new Emitter<TextFileEditorModel>());
|
||||
readonly onDidSaveError = this._onDidSaveError.event;
|
||||
|
||||
private readonly _onDidSave = this._register(new Emitter<ITextFileModelSaveEvent>());
|
||||
private readonly _onDidSave = this._register(new Emitter<ITextFileSaveEvent>());
|
||||
readonly onDidSave = this._onDidSave.event;
|
||||
|
||||
private readonly _onDidRevert = this._register(new Emitter<ITextFileEditorModel>());
|
||||
private readonly _onDidRevert = this._register(new Emitter<TextFileEditorModel>());
|
||||
readonly onDidRevert = this._onDidRevert.event;
|
||||
|
||||
private readonly _onDidChangeEncoding = this._register(new Emitter<ITextFileEditorModel>());
|
||||
private readonly _onDidChangeEncoding = this._register(new Emitter<TextFileEditorModel>());
|
||||
readonly onDidChangeEncoding = this._onDidChangeEncoding.event;
|
||||
|
||||
private readonly mapResourceToModel = new ResourceMap<TextFileEditorModel>();
|
||||
private readonly mapResourceToModelListeners = new ResourceMap<IDisposable>();
|
||||
private readonly mapResourceToDisposeListener = new ResourceMap<IDisposable>();
|
||||
private readonly mapResourceToPendingModelLoaders = new ResourceMap<Promise<TextFileEditorModel>>();
|
||||
|
||||
private readonly modelLoadQueue = this._register(new ResourceQueue());
|
||||
|
||||
saveErrorHandler = (() => {
|
||||
const notificationService = this.notificationService;
|
||||
|
||||
@@ -60,12 +67,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
};
|
||||
})();
|
||||
|
||||
private readonly mapResourceToModel = new ResourceMap<ITextFileEditorModel>();
|
||||
private readonly mapResourceToModelListeners = new ResourceMap<IDisposable>();
|
||||
private readonly mapResourceToDisposeListener = new ResourceMap<IDisposable>();
|
||||
private readonly mapResourceToPendingModelLoaders = new ResourceMap<Promise<ITextFileEditorModel>>();
|
||||
|
||||
private readonly modelLoadQueue = this._register(new ResourceQueue());
|
||||
get models(): TextFileEditorModel[] {
|
||||
return this.mapResourceToModel.values();
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
@@ -99,13 +103,15 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
//
|
||||
// Note: we also consider the added event because it could be that a file was added
|
||||
// and updated right after.
|
||||
distinct(coalesce([...e.getUpdated(), ...e.getAdded()]
|
||||
.map(({ resource }) => this.get(resource)))
|
||||
.filter(model => model && !model.isDirty()), model => model.resource.toString())
|
||||
.forEach(model => this.queueModelLoad(model));
|
||||
distinct(
|
||||
coalesce(
|
||||
[...e.getUpdated(), ...e.getAdded()].map(({ resource }) => this.get(resource))
|
||||
).filter(model => model && model.isResolved() && !model.isDirty()),
|
||||
model => model.resource.toString()
|
||||
).forEach(model => this.queueModelLoad(model));
|
||||
}
|
||||
|
||||
private queueModelLoad(model: ITextFileEditorModel): void {
|
||||
private queueModelLoad(model: TextFileEditorModel): void {
|
||||
|
||||
// Load model to update (use a queue to prevent accumulation of loads
|
||||
// when the load actually takes long. At most we only want the queue
|
||||
@@ -131,9 +137,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
if (source && (e.operation === FileOperation.COPY || e.operation === FileOperation.MOVE)) {
|
||||
|
||||
// find all models that related to either source or target (can be many if resource is a folder)
|
||||
const sourceModels: ITextFileEditorModel[] = [];
|
||||
const targetModels: ITextFileEditorModel[] = [];
|
||||
for (const model of this.getAll()) {
|
||||
const sourceModels: TextFileEditorModel[] = [];
|
||||
const targetModels: TextFileEditorModel[] = [];
|
||||
for (const model of this.models) {
|
||||
const resource = model.resource;
|
||||
|
||||
if (isEqualOrParent(resource, e.target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) {
|
||||
@@ -232,11 +238,11 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
}
|
||||
}
|
||||
|
||||
get(resource: URI): ITextFileEditorModel | undefined {
|
||||
get(resource: URI): TextFileEditorModel | undefined {
|
||||
return this.mapResourceToModel.get(resource);
|
||||
}
|
||||
|
||||
async resolve(resource: URI, options?: IModelLoadOrCreateOptions): Promise<ITextFileEditorModel> {
|
||||
async resolve(resource: URI, options?: ITextFileEditorModelLoadOrCreateOptions): Promise<TextFileEditorModel> {
|
||||
|
||||
// Return early if model is currently being loaded
|
||||
const pendingLoad = this.mapResourceToPendingModelLoaders.get(resource);
|
||||
@@ -244,7 +250,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
return pendingLoad;
|
||||
}
|
||||
|
||||
let modelPromise: Promise<ITextFileEditorModel>;
|
||||
let modelPromise: Promise<TextFileEditorModel>;
|
||||
let model = this.get(resource);
|
||||
let didCreateModel = false;
|
||||
|
||||
@@ -275,20 +281,23 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
modelPromise = model.load(options);
|
||||
|
||||
// Install model listeners
|
||||
const listeners = new DisposableStore();
|
||||
listeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model: newModel, reason })));
|
||||
listeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(newModel)));
|
||||
listeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(newModel)));
|
||||
listeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: newModel, reason })));
|
||||
listeners.add(model.onDidRevert(() => this._onDidRevert.fire(newModel)));
|
||||
listeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(newModel)));
|
||||
const modelListeners = new DisposableStore();
|
||||
modelListeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model: newModel, reason })));
|
||||
modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(newModel)));
|
||||
modelListeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(newModel)));
|
||||
modelListeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: newModel, reason })));
|
||||
modelListeners.add(model.onDidRevert(() => this._onDidRevert.fire(newModel)));
|
||||
modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(newModel)));
|
||||
|
||||
this.mapResourceToModelListeners.set(resource, listeners);
|
||||
this.mapResourceToModelListeners.set(resource, modelListeners);
|
||||
}
|
||||
|
||||
// Store pending loads to avoid race conditions
|
||||
this.mapResourceToPendingModelLoaders.set(resource, modelPromise);
|
||||
|
||||
// Make known to manager (if not already known)
|
||||
this.add(resource, model);
|
||||
|
||||
// Signal as event if we created the model
|
||||
if (didCreateModel) {
|
||||
this._onDidCreate.fire(model);
|
||||
@@ -297,9 +306,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
try {
|
||||
const resolvedModel = await modelPromise;
|
||||
|
||||
// Make known to manager (if not already known)
|
||||
this.add(resource, resolvedModel);
|
||||
|
||||
// Remove from pending loads
|
||||
this.mapResourceToPendingModelLoaders.delete(resource);
|
||||
|
||||
@@ -308,8 +314,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
resolvedModel.setMode(options.mode);
|
||||
}
|
||||
|
||||
// Model can be dirty if a backup was restored, so we make sure to have this event delivered
|
||||
if (resolvedModel.isDirty()) {
|
||||
// Model can be dirty if a backup was restored, so we make sure to
|
||||
// have this event delivered if we created the model here
|
||||
if (didCreateModel && resolvedModel.isDirty()) {
|
||||
this._onDidChangeDirty.fire(resolvedModel);
|
||||
}
|
||||
|
||||
@@ -328,18 +335,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
}
|
||||
}
|
||||
|
||||
getAll(filter?: (model: ITextFileEditorModel) => boolean): ITextFileEditorModel[] {
|
||||
const res: ITextFileEditorModel[] = [];
|
||||
this.mapResourceToModel.forEach(model => {
|
||||
if (!filter || filter(model)) {
|
||||
res.push(model);
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
add(resource: URI, model: ITextFileEditorModel): void {
|
||||
add(resource: URI, model: TextFileEditorModel): void {
|
||||
const knownModel = this.mapResourceToModel.get(resource);
|
||||
if (knownModel === model) {
|
||||
return; // already cached
|
||||
|
||||
@@ -161,13 +161,18 @@ export const enum TextFileOperationResult {
|
||||
}
|
||||
|
||||
export class TextFileOperationError extends FileOperationError {
|
||||
constructor(message: string, public textFileOperationResult: TextFileOperationResult, public options?: IReadTextFileOptions & IWriteTextFileOptions) {
|
||||
super(message, FileOperationResult.FILE_OTHER_ERROR);
|
||||
}
|
||||
|
||||
static isTextFileOperationError(obj: unknown): obj is TextFileOperationError {
|
||||
return obj instanceof Error && !isUndefinedOrNull((obj as TextFileOperationError).textFileOperationResult);
|
||||
}
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
public textFileOperationResult: TextFileOperationResult,
|
||||
public options?: IReadTextFileOptions & IWriteTextFileOptions
|
||||
) {
|
||||
super(message, FileOperationResult.FILE_OTHER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IResourceEncodings {
|
||||
@@ -193,7 +198,7 @@ export interface ISaveErrorHandler {
|
||||
/**
|
||||
* States the text file editor model can be in.
|
||||
*/
|
||||
export const enum ModelState {
|
||||
export const enum TextFileEditorModelState {
|
||||
|
||||
/**
|
||||
* A model is saved.
|
||||
@@ -228,17 +233,7 @@ export const enum ModelState {
|
||||
ERROR
|
||||
}
|
||||
|
||||
export interface ITextFileOperationResult {
|
||||
results: IResult[];
|
||||
}
|
||||
|
||||
export interface IResult {
|
||||
source: URI;
|
||||
target?: URI;
|
||||
error?: boolean;
|
||||
}
|
||||
|
||||
export const enum LoadReason {
|
||||
export const enum TextFileLoadReason {
|
||||
EDITOR = 1,
|
||||
REFERENCE = 2,
|
||||
OTHER = 3
|
||||
@@ -268,12 +263,12 @@ export interface ITextFileStreamContent extends IBaseTextFileContent {
|
||||
value: ITextBufferFactory;
|
||||
}
|
||||
|
||||
export interface IModelLoadOrCreateOptions {
|
||||
export interface ITextFileEditorModelLoadOrCreateOptions {
|
||||
|
||||
/**
|
||||
* Context why the model is being loaded or created.
|
||||
*/
|
||||
reason?: LoadReason;
|
||||
reason?: TextFileLoadReason;
|
||||
|
||||
/**
|
||||
* The language mode to use for the model text content.
|
||||
@@ -303,14 +298,14 @@ export interface IModelLoadOrCreateOptions {
|
||||
allowBinary?: boolean;
|
||||
}
|
||||
|
||||
export interface ITextFileModelSaveEvent {
|
||||
export interface ITextFileSaveEvent {
|
||||
model: ITextFileEditorModel;
|
||||
reason: SaveReason;
|
||||
}
|
||||
|
||||
export interface ITextFileModelLoadEvent {
|
||||
export interface ITextFileLoadEvent {
|
||||
model: ITextFileEditorModel;
|
||||
reason: LoadReason;
|
||||
reason: TextFileLoadReason;
|
||||
}
|
||||
|
||||
export interface ITextFileSaveParticipant {
|
||||
@@ -330,35 +325,69 @@ export interface ITextFileSaveParticipant {
|
||||
export interface ITextFileEditorModelManager {
|
||||
|
||||
readonly onDidCreate: Event<ITextFileEditorModel>;
|
||||
readonly onDidLoad: Event<ITextFileModelLoadEvent>;
|
||||
readonly onDidLoad: Event<ITextFileLoadEvent>;
|
||||
readonly onDidChangeDirty: Event<ITextFileEditorModel>;
|
||||
readonly onDidSaveError: Event<ITextFileEditorModel>;
|
||||
readonly onDidSave: Event<ITextFileModelSaveEvent>;
|
||||
readonly onDidRevert: Event<ITextFileEditorModel>;
|
||||
readonly onDidChangeEncoding: Event<ITextFileEditorModel>;
|
||||
readonly onDidSaveError: Event<ITextFileEditorModel>;
|
||||
readonly onDidSave: Event<ITextFileSaveEvent>;
|
||||
readonly onDidRevert: Event<ITextFileEditorModel>;
|
||||
|
||||
readonly models: ITextFileEditorModel[];
|
||||
|
||||
saveErrorHandler: ISaveErrorHandler;
|
||||
|
||||
/**
|
||||
* Returns the text file editor model for the provided resource
|
||||
* or undefined if none.
|
||||
*/
|
||||
get(resource: URI): ITextFileEditorModel | undefined;
|
||||
getAll(): ITextFileEditorModel[];
|
||||
|
||||
resolve(resource: URI, options?: IModelLoadOrCreateOptions): Promise<ITextFileEditorModel>;
|
||||
/**
|
||||
* Allows to load a text file model from disk.
|
||||
*/
|
||||
resolve(resource: URI, options?: ITextFileEditorModelLoadOrCreateOptions): Promise<ITextFileEditorModel>;
|
||||
|
||||
/**
|
||||
* Adds a participant for saving text file models.
|
||||
*/
|
||||
addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable;
|
||||
|
||||
runSaveParticipants(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise<void>
|
||||
|
||||
disposeModel(model: ITextFileEditorModel): void;
|
||||
}
|
||||
|
||||
export interface ITextFileSaveOptions extends ISaveOptions {
|
||||
|
||||
/**
|
||||
* Makes the file writable if it is readonly.
|
||||
*/
|
||||
overwriteReadonly?: boolean;
|
||||
|
||||
/**
|
||||
* Overwrite the encoding of the file on disk as configured.
|
||||
*/
|
||||
overwriteEncoding?: boolean;
|
||||
|
||||
/**
|
||||
* Save the file with elevated privileges.
|
||||
*
|
||||
* Note: This may not be supported in all environments.
|
||||
*/
|
||||
writeElevated?: boolean;
|
||||
|
||||
/**
|
||||
* Allows to write to a file even if it has been modified on disk.
|
||||
*/
|
||||
ignoreModifiedSince?: boolean;
|
||||
|
||||
/**
|
||||
* If set, will bubble up the error to the caller instead of handling it.
|
||||
*/
|
||||
ignoreErrorHandler?: boolean;
|
||||
}
|
||||
|
||||
export interface ILoadOptions {
|
||||
export interface ITextFileLoadOptions {
|
||||
|
||||
/**
|
||||
* Go to disk bypassing any cache of the model if any.
|
||||
@@ -373,39 +402,29 @@ export interface ILoadOptions {
|
||||
/**
|
||||
* Context why the model is being loaded.
|
||||
*/
|
||||
reason?: LoadReason;
|
||||
reason?: TextFileLoadReason;
|
||||
}
|
||||
|
||||
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport, IWorkingCopy {
|
||||
|
||||
readonly onDidChangeContent: Event<void>;
|
||||
readonly onDidLoad: Event<LoadReason>;
|
||||
readonly onDidSaveError: Event<void>;
|
||||
readonly onDidSave: Event<SaveReason>;
|
||||
readonly onDidRevert: Event<void>;
|
||||
readonly onDidChangeEncoding: Event<void>;
|
||||
readonly onDidChangeOrphaned: Event<void>;
|
||||
|
||||
hasState(state: ModelState): boolean;
|
||||
hasState(state: TextFileEditorModelState): boolean;
|
||||
|
||||
updatePreferredEncoding(encoding: string | undefined): void;
|
||||
|
||||
updateTextEditorModel(newValue?: ITextBufferFactory, preferredMode?: string): void;
|
||||
|
||||
save(options?: ITextFileSaveOptions): Promise<boolean>;
|
||||
|
||||
load(options?: ILoadOptions): Promise<ITextFileEditorModel>;
|
||||
|
||||
revert(options?: IRevertOptions): Promise<boolean>;
|
||||
|
||||
isDirty(): boolean; // {{SQL CARBON EDIT}} strict-null-check
|
||||
load(options?: ITextFileLoadOptions): Promise<ITextFileEditorModel>;
|
||||
|
||||
setDirty(dirty: boolean): void;
|
||||
isDirty(): boolean; // {{SQL CARBON EDIT}} strict-null-check
|
||||
|
||||
getMode(): string | undefined;
|
||||
|
||||
isResolved(): this is IResolvedTextFileEditorModel;
|
||||
|
||||
isDisposed(): boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as assert from 'assert';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { EncodingMode } from 'vs/workbench/common/editor';
|
||||
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
|
||||
import { ITextFileService, ModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, TextFileEditorModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { createFileInput, TestFileService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { toResource } from 'vs/base/test/common/utils';
|
||||
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
|
||||
@@ -100,7 +100,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
|
||||
model.textEditorModel!.setValue('bar');
|
||||
assert.ok(getLastModifiedTime(model) <= Date.now());
|
||||
assert.ok(model.hasState(ModelState.DIRTY));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.DIRTY));
|
||||
|
||||
assert.equal(accessor.workingCopyService.dirtyCount, 1);
|
||||
assert.equal(accessor.workingCopyService.isDirty(model.resource), true);
|
||||
@@ -116,11 +116,11 @@ suite('Files - TextFileEditorModel', () => {
|
||||
});
|
||||
|
||||
const pendingSave = model.save();
|
||||
assert.ok(model.hasState(ModelState.PENDING_SAVE));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE));
|
||||
|
||||
await pendingSave;
|
||||
|
||||
assert.ok(model.hasState(ModelState.SAVED));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.SAVED));
|
||||
assert.ok(!model.isDirty());
|
||||
assert.ok(savedEvent);
|
||||
assert.ok(workingCopyEvent);
|
||||
@@ -169,11 +169,11 @@ suite('Files - TextFileEditorModel', () => {
|
||||
accessor.fileService.writeShouldThrowError = new Error('failed to write');
|
||||
try {
|
||||
const pendingSave = model.save();
|
||||
assert.ok(model.hasState(ModelState.PENDING_SAVE));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE));
|
||||
|
||||
await pendingSave;
|
||||
|
||||
assert.ok(model.hasState(ModelState.ERROR));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.ERROR));
|
||||
assert.ok(model.isDirty());
|
||||
assert.ok(saveErrorEvent);
|
||||
|
||||
@@ -199,11 +199,11 @@ suite('Files - TextFileEditorModel', () => {
|
||||
accessor.fileService.writeShouldThrowError = new FileOperationError('save conflict', FileOperationResult.FILE_MODIFIED_SINCE);
|
||||
try {
|
||||
const pendingSave = model.save();
|
||||
assert.ok(model.hasState(ModelState.PENDING_SAVE));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE));
|
||||
|
||||
await pendingSave;
|
||||
|
||||
assert.ok(model.hasState(ModelState.CONFLICT));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.CONFLICT));
|
||||
assert.ok(model.isDirty());
|
||||
assert.ok(saveErrorEvent);
|
||||
|
||||
@@ -273,7 +273,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
|
||||
test('Load does not trigger save', async function () {
|
||||
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined);
|
||||
assert.ok(model.hasState(ModelState.SAVED));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.SAVED));
|
||||
|
||||
model.onDidSave(e => assert.fail());
|
||||
model.onDidChangeDirty(e => assert.fail());
|
||||
@@ -290,7 +290,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
await model.load();
|
||||
model.textEditorModel!.setValue('foo');
|
||||
assert.ok(model.isDirty());
|
||||
assert.ok(model.hasState(ModelState.DIRTY));
|
||||
assert.ok(model.hasState(TextFileEditorModelState.DIRTY));
|
||||
|
||||
await model.load();
|
||||
assert.ok(model.isDirty());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user