Merge remote-tracking branch 'origin/master' into alex/node-modules-types

This commit is contained in:
Alexandru Dima 2020-12-18 15:59:00 +01:00
commit b0af35c810
No known key found for this signature in database
GPG key ID: 6E58D7B045760DA0
13 changed files with 186 additions and 129 deletions

View file

@ -28,8 +28,8 @@ jobs:
uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules4-
key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules5-
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -71,8 +71,8 @@ jobs:
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules4-
# key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules5-
# - name: Get yarn cache directory path
# id: yarnCacheDirPath
# if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -125,8 +125,8 @@ jobs:
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules4-
# key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules5-
# - name: Get yarn cache directory path
# id: yarnCacheDirPath
# if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -184,8 +184,8 @@ jobs:
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules4-
# key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules5-
# - name: Restore compiled core code
# id: cacheCompiledCoreCode
@ -246,8 +246,8 @@ jobs:
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules4-
# key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules5-
# - name: Restore compiled core code
# id: cacheCompiledCoreCode
@ -291,8 +291,8 @@ jobs:
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules4-
# key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules5-
# - name: Get yarn cache directory path
# id: yarnCacheDirPath
# if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -329,8 +329,8 @@ jobs:
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules4-
# key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules5-
# - name: Restore compiled core code
# id: cacheCompiledCoreCode
@ -381,8 +381,8 @@ jobs:
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules4-
# key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules5-
# - name: Restore compiled core code
# id: cacheCompiledCoreCode
@ -427,8 +427,8 @@ jobs:
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules4-
# key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules5-
# - name: Restore compiled core code
# id: cacheCompiledCoreCode
@ -484,8 +484,8 @@ jobs:
uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules4-
key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules5-
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -542,11 +542,12 @@ jobs:
with:
path: |
**/node_modules
!**/Release/*.pdb
!**/Release/*.ilk
!**/Release/obj/**
key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules4-
!**/Release/**/*.pdb
!**/Release/**/*.ilk
!**/Release/**/*.obj
!**/Release/**/*.tlog
key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules5-
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -594,8 +595,8 @@ jobs:
uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules4-
key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules5-
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -643,8 +644,8 @@ jobs:
uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules4-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules4-
key: ${{ runner.os }}-cacheNodeModules5-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules5-
- name: Execute yarn
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
env:

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.53.0",
"distro": "c89ced323fffc6ffd5caa6e9bb6662b8e26b5fb4",
"distro": "252d98105e15a5ba22421ff5aef8d1aa7c3b69fa",
"author": {
"name": "Microsoft Corporation"
},

View file

@ -1014,12 +1014,11 @@ export class MouseTargetFactory {
}
private static _snapToSoftTabBoundary(position: Position, viewModel: IViewModel): Position {
const minColumn = viewModel.getLineMinColumn(position.lineNumber);
const lineContent = viewModel.getLineContent(position.lineNumber);
const { tabSize } = viewModel.getTextModelOptions();
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, position.column - minColumn, tabSize, Direction.Nearest);
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, position.column - 1, tabSize, Direction.Nearest);
if (newPosition !== -1) {
return new Position(position.lineNumber, newPosition + minColumn);
return new Position(position.lineNumber, newPosition + 1);
}
return position;
}

View file

@ -39,11 +39,11 @@ export class MoveOperations {
public static leftPositionAtomicSoftTabs(model: ICursorSimpleModel, lineNumber: number, column: number, tabSize: number): Position {
const minColumn = model.getLineMinColumn(lineNumber);
const lineContent = model.getLineContent(lineNumber);
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - minColumn, tabSize, Direction.Left);
if (newPosition === -1) {
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - 1, tabSize, Direction.Left);
if (newPosition === -1 || newPosition + 1 < minColumn) {
return this.leftPosition(model, lineNumber, column);
}
return new Position(lineNumber, minColumn + newPosition);
return new Position(lineNumber, newPosition + 1);
}
public static left(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number): CursorPosition {
@ -81,13 +81,12 @@ export class MoveOperations {
}
public static rightPositionAtomicSoftTabs(model: ICursorSimpleModel, lineNumber: number, column: number, tabSize: number, indentSize: number): Position {
const minColumn = model.getLineMinColumn(lineNumber);
const lineContent = model.getLineContent(lineNumber);
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - minColumn, tabSize, Direction.Right);
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - 1, tabSize, Direction.Right);
if (newPosition === -1) {
return this.rightPosition(model, lineNumber, column);
}
return new Position(lineNumber, minColumn + newPosition);
return new Position(lineNumber, newPosition + 1);
}
public static right(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number): CursorPosition {

View file

@ -2349,6 +2349,37 @@ suite('Editor Controller - Regression tests', () => {
});
});
test('issue #112301: new stickyTabStops feature interferes with word wrap', () => {
withTestCodeEditor([
[
'function hello() {',
' console.log(`this is a long console message`)',
'}',
].join('\n')
], { wordWrap: 'wordWrapColumn', wordWrapColumn: 32, stickyTabStops: true }, (editor, viewModel) => {
viewModel.setSelections('test', [
new Selection(2, 31, 2, 31)
]);
moveRight(editor, viewModel, false);
assertCursor(viewModel, new Position(2, 32));
moveRight(editor, viewModel, false);
assertCursor(viewModel, new Position(2, 33));
moveRight(editor, viewModel, false);
assertCursor(viewModel, new Position(2, 34));
moveLeft(editor, viewModel, false);
assertCursor(viewModel, new Position(2, 33));
moveLeft(editor, viewModel, false);
assertCursor(viewModel, new Position(2, 32));
moveLeft(editor, viewModel, false);
assertCursor(viewModel, new Position(2, 31));
});
});
test('issue #44805: Should not be able to undo in readonly editor', () => {
let model = createTextModel(
[

View file

@ -10,7 +10,7 @@ import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/ext
import { localize } from 'vs/nls';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkingCopyFileOperationParticipant, IWorkingCopyFileService, SourceTargetPair } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IWorkingCopyFileOperationParticipant, IWorkingCopyFileService, SourceTargetPair, IFileOperationUndoRedoInfo } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { reviveWorkspaceEditDto2 } from 'vs/workbench/api/browser/mainThreadEditors';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
@ -74,8 +74,8 @@ export class MainThreadFileSystemEventService {
const fileOperationParticipant = new class implements IWorkingCopyFileOperationParticipant {
async participate(files: SourceTargetPair[], operation: FileOperation, undoRedoGroupId: number | undefined, isUndoing: boolean | undefined, timeout: number, token: CancellationToken) {
if (isUndoing) {
async participate(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, timeout: number, token: CancellationToken) {
if (undoInfo?.isUndoing) {
return;
}
@ -171,7 +171,7 @@ export class MainThreadFileSystemEventService {
await bulkEditService.apply(
reviveWorkspaceEditDto2(data.edit),
{ undoRedoGroupId, showPreview }
{ undoRedoGroupId: undoInfo?.undoRedoGroupId, showPreview }
);
}

View file

@ -8,7 +8,7 @@ import { WorkspaceFileEditOptions } from 'vs/editor/common/modes';
import { IFileService, FileSystemProviderCapabilities, IFileContent } from 'vs/platform/files/common/files';
import { IProgress } from 'vs/platform/progress/common/progress';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IWorkingCopyFileService, IFileOperationUndoRedoInfo } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoService, UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -18,11 +18,6 @@ import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService';
import * as resources from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
interface IFileOperationUndoRedoInfo {
undoRedoGroupId?: number;
isUndoing?: boolean;
}
interface IFileOperation {
uris: URI[];
perform(token: CancellationToken): Promise<IFileOperation>;
@ -57,7 +52,7 @@ class RenameOperation implements IFileOperation {
return new Noop(); // not overwriting, but ignoring, and the target file exists
}
await this._workingCopyFileService.move([{ source: this.oldUri, target: this.newUri }], { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token);
await this._workingCopyFileService.move([{ file: { source: this.oldUri, target: this.newUri }, overwrite: this.options.overwrite }], this.undoRedoInfo, token);
return new RenameOperation(this.oldUri, this.newUri, this.options, { isUndoing: true }, this._workingCopyFileService, this._fileService);
}
@ -93,7 +88,7 @@ class CopyOperation implements IFileOperation {
return new Noop(); // not overwriting, but ignoring, and the target file exists
}
const stat = await this._workingCopyFileService.copy([{ source: this.oldUri, target: this.newUri }], { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token);
const stat = await this._workingCopyFileService.copy([{ file: { source: this.oldUri, target: this.newUri }, overwrite: this.options.overwrite }], this.undoRedoInfo, token);
const folder = this.options.folder || (stat.length === 1 && stat[0].isDirectory);
return this._instaService.createInstance(DeleteOperation, this.newUri, { recursive: true, folder, ...this.options }, { isUndoing: true }, false);
}
@ -125,9 +120,9 @@ class CreateOperation implements IFileOperation {
return new Noop(); // not overwriting, but ignoring, and the target file exists
}
if (this.options.folder) {
await this._workingCopyFileService.createFolder(this.newUri, { ...this.undoRedoInfo }, token);
await this._workingCopyFileService.createFolder({ resource: this.newUri }, this.undoRedoInfo, token);
} else {
await this._workingCopyFileService.create(this.newUri, this.contents, { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token);
await this._workingCopyFileService.create({ resource: this.newUri, contents: this.contents, overwrite: this.options.overwrite }, this.undoRedoInfo, token);
}
return this._instaService.createInstance(DeleteOperation, this.newUri, this.options, { isUndoing: true }, !this.options.folder && !this.contents);
}
@ -175,7 +170,7 @@ class DeleteOperation implements IFileOperation {
}
const useTrash = !this.options.skipTrashBin && this._fileService.hasCapability(this.oldUri, FileSystemProviderCapabilities.Trash) && this._configurationService.getValue<boolean>('files.enableTrash');
await this._workingCopyFileService.delete([this.oldUri], { useTrash, recursive: this.options.recursive, ...this.undoRedoInfo }, token);
await this._workingCopyFileService.delete([{ resource: this.oldUri, useTrash, recursive: this.options.recursive }], this.undoRedoInfo, token);
if (typeof this.options.maxSize === 'number' && fileContent && (fileContent?.size > this.options.maxSize)) {
return new Noop();

View file

@ -151,7 +151,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
const readable = await this.getEncodedReadable(resource, value);
return this.workingCopyFileService.create(resource, readable, options);
return await this.workingCopyFileService.create({ resource, contents: readable, overwrite: options?.overwrite });
}
async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata> {
@ -243,7 +243,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
// However, this will only work if the source exists
// and is not orphaned, so we need to check that too.
if (this.fileService.canHandleResource(source) && this.uriIdentityService.extUri.isEqual(source, target) && (await this.fileService.exists(source))) {
await this.workingCopyFileService.move([{ source, target }]);
await this.workingCopyFileService.move([{ file: { source, target } }]);
return this.save(target, options);
}

View file

@ -6,8 +6,7 @@
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILogService } from 'vs/platform/log/common/log';
import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IWorkingCopyFileOperationParticipant } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { URI } from 'vs/base/common/uri';
import { IWorkingCopyFileOperationParticipant, SourceTargetPair, IFileOperationUndoRedoInfo } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { FileOperation } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { LinkedList } from 'vs/base/common/linkedList';
@ -28,7 +27,7 @@ export class WorkingCopyFileOperationParticipant extends Disposable {
return toDisposable(() => remove());
}
async participate(files: { source?: URI, target: URI }[], operation: FileOperation, undoRedoGroupId: number | undefined, isUndoing: boolean | undefined, token: CancellationToken | undefined): Promise<void> {
async participate(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, token: CancellationToken | undefined): Promise<void> {
const timeout = this.configurationService.getValue<number>('files.participants.timeout');
if (timeout <= 0) {
return; // disabled
@ -37,7 +36,7 @@ export class WorkingCopyFileOperationParticipant extends Disposable {
// For each participant
for (const participant of this.participants) {
try {
await participant.participate(files, operation, undoRedoGroupId, isUndoing, timeout, token ?? CancellationToken.None);
await participant.participate(files, operation, undoInfo, timeout, token ?? CancellationToken.None);
} catch (err) {
this.logService.warn(err);
}

View file

@ -31,6 +31,19 @@ export interface SourceTargetPair {
readonly target: URI
}
export interface IFileOperationUndoRedoInfo {
/**
* Id of the undo group that the file operation belongs to.
*/
undoRedoGroupId?: number;
/**
* Flag indicates if the operation is an undo.
*/
isUndoing?: boolean
}
export interface WorkingCopyFileEvent extends IWaitUntil {
/**
@ -59,13 +72,34 @@ export interface IWorkingCopyFileOperationParticipant {
participate(
files: SourceTargetPair[],
operation: FileOperation,
undoRedoGroupId: number | undefined,
isUndoing: boolean | undefined,
undoInfo: IFileOperationUndoRedoInfo | undefined,
timeout: number,
token: CancellationToken
): Promise<void>;
}
export interface ICreateOperation {
resource: URI;
overwrite?: boolean;
}
export interface ICreateFileOperation extends ICreateOperation {
contents?: VSBuffer | VSBufferReadable | VSBufferReadableStream,
}
export interface IDeleteOperation {
resource: URI;
useTrash?: boolean;
recursive?: boolean;
}
export interface IMoveOperation {
file: Required<SourceTargetPair>;
overwrite?: boolean;
}
export interface ICopyOperation extends IMoveOperation { }
/**
* Returns the working copies for a given resource.
*/
@ -130,7 +164,7 @@ export interface IWorkingCopyFileService {
* Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and
* `onDidRunWorkingCopyFileOperation` events to participate.
*/
create(resource: URI, contents?: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise<IFileStatWithMetadata>;
create(operation: ICreateFileOperation, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata>;
/**
* Will create a folder and any parent folder that needs to be created.
@ -141,7 +175,7 @@ export interface IWorkingCopyFileService {
* Note: events will only be emitted for the provided resource, but not any
* parent folders that are being created as part of the operation.
*/
createFolder(resource: URI, options?: { undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise<IFileStatWithMetadata>;
createFolder(operation: ICreateOperation, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata>;
/**
* Will move working copies matching the provided resources and corresponding children
@ -150,7 +184,7 @@ export interface IWorkingCopyFileService {
* Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and
* `onDidRunWorkingCopyFileOperation` events to participate.
*/
move(files: Required<SourceTargetPair>[], options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise<IFileStatWithMetadata[]>;
move(operations: IMoveOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]>;
/**
* Will copy working copies matching the provided resources and corresponding children
@ -159,7 +193,7 @@ export interface IWorkingCopyFileService {
* Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and
* `onDidRunWorkingCopyFileOperation` events to participate.
*/
copy(files: Required<SourceTargetPair>[], options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise<IFileStatWithMetadata[]>;
copy(operations: ICopyOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]>;
/**
* Will delete working copies matching the provided resources and children
@ -168,7 +202,7 @@ export interface IWorkingCopyFileService {
* Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and
* `onDidRunWorkingCopyFileOperation` events to participate.
*/
delete(resources: URI[], options?: { useTrash?: boolean, recursive?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise<void>;
delete(operations: IDeleteOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<void>;
//#endregion
@ -237,38 +271,38 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
//#region File operations
create(resource: URI, contents?: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise<IFileStatWithMetadata> {
return this.doCreateFileOrFolder(resource, true, contents, options, token);
create(operation: ICreateFileOperation, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata> {
return this.doCreateFileOrFolder(operation, true, undoInfo, token);
}
createFolder(resource: URI, options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise<IFileStatWithMetadata> {
return this.doCreateFileOrFolder(resource, false, undefined, options, token);
createFolder(operation: ICreateOperation, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata> {
return this.doCreateFileOrFolder(operation, false, undoInfo, token);
}
async doCreateFileOrFolder(resource: URI, isFile: boolean, contents?: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise<IFileStatWithMetadata> {
async doCreateFileOrFolder(operation: ICreateFileOperation | ICreateOperation, isFile: boolean, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata> {
// validate create operation before starting
if (isFile) {
const validateCreate = await this.fileService.canCreateFile(resource, options);
const validateCreate = await this.fileService.canCreateFile(operation.resource, { overwrite: operation.overwrite });
if (validateCreate instanceof Error) {
throw validateCreate;
}
}
// file operation participant
await this.runFileOperationParticipants([{ target: resource }], FileOperation.CREATE, options?.undoRedoGroupId, options?.isUndoing, token);
await this.runFileOperationParticipants([{ target: operation.resource }], FileOperation.CREATE, undoInfo, token);
// before events
const event = { correlationId: this.correlationIds++, operation: FileOperation.CREATE, files: [{ target: resource }] };
const event = { correlationId: this.correlationIds++, operation: FileOperation.CREATE, files: [{ target: operation.resource }] };
await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None);
// now actually create on disk
let stat: IFileStatWithMetadata;
try {
if (isFile) {
stat = await this.fileService.createFile(resource, contents, { overwrite: options?.overwrite });
stat = await this.fileService.createFile(operation.resource, (operation as ICreateFileOperation).contents, { overwrite: operation.overwrite });
} else {
stat = await this.fileService.createFolder(resource);
stat = await this.fileService.createFolder(operation.resource);
}
} catch (error) {
@ -284,20 +318,19 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
return stat;
}
async move(files: Required<SourceTargetPair>[], options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
return this.doMoveOrCopy(files, true, options, token);
async move(operations: IMoveOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
return this.doMoveOrCopy(operations, true, undoInfo, token);
}
async copy(files: Required<SourceTargetPair>[], options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
return this.doMoveOrCopy(files, false, options, token);
async copy(operations: ICopyOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
return this.doMoveOrCopy(operations, false, undoInfo, token);
}
private async doMoveOrCopy(files: Required<SourceTargetPair>[], move: boolean, options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
const overwrite = options?.overwrite;
private async doMoveOrCopy(operations: IMoveOperation[] | ICopyOperation[], move: boolean, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> {
const stats: IFileStatWithMetadata[] = [];
// validate move/copy operation before starting
for (const { source, target } of files) {
for (const { file: { source, target }, overwrite } of operations) {
const validateMoveOrCopy = await (move ? this.fileService.canMove(source, target, overwrite) : this.fileService.canCopy(source, target, overwrite));
if (validateMoveOrCopy instanceof Error) {
throw validateMoveOrCopy;
@ -305,15 +338,15 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
}
// file operation participant
await this.runFileOperationParticipants(files, move ? FileOperation.MOVE : FileOperation.COPY, options?.undoRedoGroupId, options?.isUndoing, token);
const files = operations.map(o => o.file);
await this.runFileOperationParticipants(files, move ? FileOperation.MOVE : FileOperation.COPY, undoInfo, token);
// before event
const event = { correlationId: this.correlationIds++, operation: move ? FileOperation.MOVE : FileOperation.COPY, files };
await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None);
try {
for (const { source, target } of files) {
for (const { file: { source, target }, overwrite } of operations) {
// if source and target are not equal, handle dirty working copies
// depending on the operation:
// - move: revert both source and target (if any)
@ -344,19 +377,19 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
return stats;
}
async delete(resources: URI[], options?: { useTrash?: boolean, recursive?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise<void> {
async delete(operations: IDeleteOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<void> {
// validate delete operation before starting
for (const resource of resources) {
const validateDelete = await this.fileService.canDelete(resource, options);
for (const operation of operations) {
const validateDelete = await this.fileService.canDelete(operation.resource, { recursive: operation.recursive, useTrash: operation.useTrash });
if (validateDelete instanceof Error) {
throw validateDelete;
}
}
// file operation participant
const files = resources.map(target => ({ target }));
await this.runFileOperationParticipants(files, FileOperation.DELETE, options?.undoRedoGroupId, options?.isUndoing, token);
const files = operations.map(operation => ({ target: operation.resource }));
await this.runFileOperationParticipants(files, FileOperation.DELETE, undoInfo, token);
// before events
const event = { correlationId: this.correlationIds++, operation: FileOperation.DELETE, files };
@ -365,15 +398,15 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
// check for any existing dirty working copies for the resource
// and do a soft revert before deleting to be able to close
// any opened editor with these working copies
for (const resource of resources) {
const dirtyWorkingCopies = this.getDirty(resource);
for (const operation of operations) {
const dirtyWorkingCopies = this.getDirty(operation.resource);
await Promise.all(dirtyWorkingCopies.map(dirtyWorkingCopy => dirtyWorkingCopy.revert({ soft: true })));
}
// now actually delete from disk
try {
for (const resource of resources) {
await this.fileService.del(resource, options);
for (const operation of operations) {
await this.fileService.del(operation.resource, { recursive: operation.recursive, useTrash: operation.useTrash });
}
} catch (error) {
@ -398,8 +431,8 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
return this.fileOperationParticipants.addFileOperationParticipant(participant);
}
private runFileOperationParticipants(files: SourceTargetPair[], operation: FileOperation, undoRedoGroupId: number | undefined, isUndoing: boolean | undefined, token: CancellationToken | undefined): Promise<void> {
return this.fileOperationParticipants.participate(files, operation, undoRedoGroupId, isUndoing, token);
private runFileOperationParticipants(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, token: CancellationToken | undefined): Promise<void> {
return this.fileOperationParticipants.participate(files, operation, undoInfo, token);
}
//#endregion

View file

@ -13,6 +13,7 @@ import { URI } from 'vs/base/common/uri';
import { FileOperation } from 'vs/platform/files/common/files';
import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices';
import { VSBuffer } from 'vs/base/common/buffer';
import { ICopyOperation } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
suite('WorkingCopyFileService', () => {
@ -52,7 +53,7 @@ suite('WorkingCopyFileService', () => {
let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(sourceModel.resource, sourceModel);
const eventCounter = await testEventsMoveOrCopy([{ source: sourceModel.resource, target: sourceModel.resource }], true);
const eventCounter = await testEventsMoveOrCopy([{ file: { source: sourceModel.resource, target: sourceModel.resource }, overwrite: true }], true);
sourceModel.dispose();
assert.equal(eventCounter, 3);
@ -67,8 +68,8 @@ suite('WorkingCopyFileService', () => {
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(targetModel2.resource, targetModel2);
const eventCounter = await testEventsMoveOrCopy([
{ source: sourceModel1.resource, target: sourceModel1.resource },
{ source: sourceModel2.resource, target: targetModel2.resource }
{ file: { source: sourceModel1.resource, target: sourceModel1.resource }, overwrite: true },
{ file: { source: sourceModel2.resource, target: targetModel2.resource }, overwrite: true }
], true);
sourceModel1.dispose();
@ -96,7 +97,7 @@ suite('WorkingCopyFileService', () => {
let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(sourceModel.resource, sourceModel);
const eventCounter = await testEventsMoveOrCopy([{ source: sourceModel.resource, target: sourceModel.resource }]);
const eventCounter = await testEventsMoveOrCopy([{ file: { source: sourceModel.resource, target: sourceModel.resource }, overwrite: true }]);
sourceModel.dispose();
assert.equal(eventCounter, 3);
@ -111,8 +112,8 @@ suite('WorkingCopyFileService', () => {
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(targetModel2.resource, targetModel2);
const eventCounter = await testEventsMoveOrCopy([
{ source: sourceModel1.resource, target: sourceModel1.resource },
{ source: sourceModel2.resource, target: targetModel2.resource }
{ file: { source: sourceModel1.resource, target: sourceModel1.resource }, overwrite: true },
{ file: { source: sourceModel2.resource, target: targetModel2.resource }, overwrite: true }
]);
sourceModel1.dispose();
@ -223,7 +224,7 @@ suite('WorkingCopyFileService', () => {
eventCounter++;
});
await accessor.workingCopyFileService.createFolder(resource);
await accessor.workingCopyFileService.createFolder({ resource });
assert.equal(eventCounter, 3);
@ -232,7 +233,7 @@ suite('WorkingCopyFileService', () => {
listener2.dispose();
});
async function testEventsMoveOrCopy(files: { source: URI, target: URI }[], move?: boolean): Promise<number> {
async function testEventsMoveOrCopy(files: ICopyOperation[], move?: boolean): Promise<number> {
let eventCounter = 0;
const participant = accessor.workingCopyFileService.addFileOperationParticipant({
@ -250,9 +251,9 @@ suite('WorkingCopyFileService', () => {
});
if (move) {
await accessor.workingCopyFileService.move(files, { overwrite: true });
await accessor.workingCopyFileService.move(files);
} else {
await accessor.workingCopyFileService.copy(files, { overwrite: true });
await accessor.workingCopyFileService.copy(files);
}
participant.dispose();
@ -330,9 +331,9 @@ suite('WorkingCopyFileService', () => {
});
if (move) {
await accessor.workingCopyFileService.move(models.map(model => ({ source: model.sourceModel.resource, target: model.targetModel.resource })), { overwrite: true });
await accessor.workingCopyFileService.move(models.map(model => ({ file: { source: model.sourceModel.resource, target: model.targetModel.resource }, options: { overwrite: true } })));
} else {
await accessor.workingCopyFileService.copy(models.map(model => ({ source: model.sourceModel.resource, target: model.targetModel.resource })), { overwrite: true });
await accessor.workingCopyFileService.copy(models.map(model => ({ file: { source: model.sourceModel.resource, target: model.targetModel.resource }, options: { overwrite: true } })));
}
for (let i = 0; i < models.length; i++) {
@ -406,7 +407,7 @@ suite('WorkingCopyFileService', () => {
eventCounter++;
});
await accessor.workingCopyFileService.delete(models.map(m => m.resource));
await accessor.workingCopyFileService.delete(models.map(m => ({ resource: m.resource })));
for (const model of models) {
assert.ok(!accessor.workingCopyService.isDirty(model.resource));
model.dispose();
@ -458,7 +459,7 @@ suite('WorkingCopyFileService', () => {
eventCounter++;
});
await accessor.workingCopyFileService.create(resource, contents);
await accessor.workingCopyFileService.create({ resource, contents });
assert.ok(!accessor.workingCopyService.isDirty(model.resource));
model.dispose();

View file

@ -40,7 +40,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ILabelService } from 'vs/platform/label/common/label';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IWorkingCopyFileService, IMoveOperation, IDeleteOperation, ICopyOperation, ICreateFileOperation } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
@ -103,23 +103,23 @@ suite('MainThreadEditors', () => {
});
services.set(IWorkingCopyFileService, new class extends mock<IWorkingCopyFileService>() {
onDidRunWorkingCopyFileOperation = Event.None;
create(resource: URI) {
createdResources.add(resource);
create(operation: ICreateFileOperation) {
createdResources.add(operation.resource);
return Promise.resolve(Object.create(null));
}
move(files: { source: URI, target: URI }[]) {
const { source, target } = files[0];
move(operations: IMoveOperation[]) {
const { source, target } = operations[0].file;
movedResources.set(source, target);
return Promise.resolve(Object.create(null));
}
copy(files: { source: URI, target: URI }[]) {
const { source, target } = files[0];
copy(operations: ICopyOperation[]) {
const { source, target } = operations[0].file;
copiedResources.set(source, target);
return Promise.resolve(Object.create(null));
}
delete(resources: URI[]) {
for (const resource of resources) {
deletedResources.add(resource);
delete(operations: IDeleteOperation[]) {
for (const operation of operations) {
deletedResources.add(operation.resource);
}
return Promise.resolve(undefined);
}

View file

@ -16,10 +16,9 @@ import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { InMemoryStorageService, WillSaveStateReason } from 'vs/platform/storage/common/storage';
import { WorkingCopyService, IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { NullExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkingCopyFileService, IWorkingCopyFileOperationParticipant, WorkingCopyFileEvent } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IWorkingCopyFileService, IWorkingCopyFileOperationParticipant, WorkingCopyFileEvent, IDeleteOperation, ICopyOperation, IMoveOperation, IFileOperationUndoRedoInfo, ICreateFileOperation, ICreateOperation } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor';
import { CancellationToken } from 'vs/base/common/cancellation';
@ -196,18 +195,18 @@ export class TestWorkingCopyFileService implements IWorkingCopyFileService {
addFileOperationParticipant(participant: IWorkingCopyFileOperationParticipant): IDisposable { return Disposable.None; }
async delete(resources: URI[], options?: { useTrash?: boolean | undefined; recursive?: boolean | undefined; } | undefined): Promise<void> { }
async delete(operations: IDeleteOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<void> { }
registerWorkingCopyProvider(provider: (resourceOrFolder: URI) => IWorkingCopy[]): IDisposable { return Disposable.None; }
getDirty(resource: URI): IWorkingCopy[] { return []; }
create(resource: URI, contents?: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: { overwrite?: boolean | undefined; } | undefined): Promise<IFileStatWithMetadata> { throw new Error('Method not implemented.'); }
createFolder(resource: URI): Promise<IFileStatWithMetadata> { throw new Error('Method not implemented.'); }
create(operation: ICreateFileOperation, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata> { throw new Error('Method not implemented.'); }
createFolder(operation: ICreateOperation, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata> { throw new Error('Method not implemented.'); }
move(files: { source: URI; target: URI; }[], options?: { overwrite?: boolean }): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
move(operations: IMoveOperation[], undoInfo?: IFileOperationUndoRedoInfo): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
copy(files: { source: URI; target: URI; }[], options?: { overwrite?: boolean }): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
copy(operations: ICopyOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
}
export function mock<T>(): Ctor<T> {