Merge from vscode 3bd60b2ba753e7fe39b42f99184bc6c5881d3551 (#4712)

This commit is contained in:
Anthony Dresser
2019-03-27 11:36:01 -07:00
committed by GitHub
parent eac3420583
commit 46b7afe558
36 changed files with 303 additions and 137 deletions

View File

@@ -16,6 +16,9 @@ steps:
- script: |
set -e
# Get snapcraft version
snapcraft --version
# Make sure we get latest packages
sudo apt-get update
sudo apt-get upgrade -y
@@ -38,7 +41,7 @@ steps:
PACKAGEJSON="$(ls $SNAP_ROOT/code*/usr/share/code*/resources/app/package.json)"
VERSION=$(node -p "require(\"$PACKAGEJSON\").version")
SNAP_PATH="$SNAP_ROOT/$SNAP_FILENAME"
(cd $SNAP_ROOT/code-* && snapcraft snap --output "$SNAP_PATH")
(cd $SNAP_ROOT/code-* && sudo snapcraft snap --output "$SNAP_PATH")
# Publish snap package
AZURE_DOCUMENTDB_MASTERKEY="$(AZURE_DOCUMENTDB_MASTERKEY)" \

View File

@@ -203,11 +203,17 @@ function prepareSnapPackage(arch) {
return function () {
const desktop = gulp.src('resources/linux/code.desktop', { base: '.' })
.pipe(rename(`usr/share/applications/${product.applicationName}.desktop`));
const desktopUrlHandler = gulp.src('resources/linux/code-url-handler.desktop', { base: '.' })
.pipe(rename(`usr/share/applications/${product.applicationName}-url-handler.desktop`));
const desktops = es.merge(desktop, desktopUrlHandler)
.pipe(replace('@@NAME_LONG@@', product.nameLong))
.pipe(replace('@@NAME_SHORT@@', product.nameShort))
.pipe(replace('@@NAME@@', product.applicationName))
.pipe(replace('@@ICON@@', `/usr/share/pixmaps/${product.linuxIconName}.png`))
.pipe(rename(`usr/share/applications/${product.applicationName}.desktop`));
.pipe(replace('@@URLPROTOCOL@@', product.urlProtocol));
const icon = gulp.src('resources/linux/code.png', { base: '.' })
.pipe(rename(`usr/share/pixmaps/${product.linuxIconName}.png`));
@@ -223,7 +229,7 @@ function prepareSnapPackage(arch) {
const electronLaunch = gulp.src('resources/linux/snap/electron-launch', { base: '.' })
.pipe(rename('electron-launch'));
const all = es.merge(desktop, icon, code, snapcraft, electronLaunch);
const all = es.merge(desktops, icon, code, snapcraft, electronLaunch);
return all.pipe(vfs.dest(destination));
};

View File

@@ -45,6 +45,7 @@ export interface IPickAndOpenOptions {
forceNewWindow?: boolean;
defaultUri?: URI;
telemetryExtraData?: ITelemetryData;
availableFileSystems?: string[];
}
export interface ISaveDialogOptions {

View File

@@ -285,30 +285,31 @@ export enum FileSystemProviderErrorCode {
FileNotADirectory = 'EntryNotADirectory',
FileIsADirectory = 'EntryIsADirectory',
NoPermissions = 'NoPermissions',
Unavailable = 'Unavailable'
Unavailable = 'Unavailable',
Unknown = 'Unknown'
}
export class FileSystemProviderError extends Error {
constructor(message: string, public readonly code?: FileSystemProviderErrorCode) {
constructor(message: string, public readonly code: FileSystemProviderErrorCode) {
super(message);
}
}
export function createFileSystemProviderError(error: Error, code?: FileSystemProviderErrorCode): FileSystemProviderError {
export function createFileSystemProviderError(error: Error, code: FileSystemProviderErrorCode): FileSystemProviderError {
const providerError = new FileSystemProviderError(error.toString(), code);
markAsFileSystemProviderError(providerError);
markAsFileSystemProviderError(providerError, code);
return providerError;
}
export function markAsFileSystemProviderError(error: Error, code?: FileSystemProviderErrorCode): Error {
export function markAsFileSystemProviderError(error: Error, code: FileSystemProviderErrorCode): Error {
error.name = code ? `${code} (FileSystemError)` : `FileSystemError`;
return error;
}
export function toFileSystemProviderErrorCode(error: Error): FileSystemProviderErrorCode | undefined {
export function toFileSystemProviderErrorCode(error: Error): FileSystemProviderErrorCode {
// FileSystemProviderError comes with the code
if (error instanceof FileSystemProviderError) {
@@ -319,7 +320,7 @@ export function toFileSystemProviderErrorCode(error: Error): FileSystemProviderE
// went through the markAsFileSystemProviderError() method
const match = /^(.+) \(FileSystemError\)$/.exec(error.name);
if (!match) {
return undefined;
return FileSystemProviderErrorCode.Unknown;
}
switch (match[1]) {
@@ -331,7 +332,7 @@ export function toFileSystemProviderErrorCode(error: Error): FileSystemProviderE
case FileSystemProviderErrorCode.Unavailable: return FileSystemProviderErrorCode.Unavailable;
}
return undefined;
return FileSystemProviderErrorCode.Unknown;
}
export function toFileOperationResult(error: Error): FileOperationResult {

5
src/vs/vscode.d.ts vendored
View File

@@ -6599,10 +6599,11 @@ declare module 'vscode' {
*
* @param name Optional human-readable string which will be used to represent the terminal in the UI.
* @param shellPath Optional path to a custom shell executable to be used in the terminal.
* @param shellArgs Optional args for the custom shell executable, this does not work on Windows (see #8429)
* @param shellArgs Optional args for the custom shell executable. A string can be used on Windows only which
* allows specifying shell args in [command-line format](https://msdn.microsoft.com/en-au/08dfcab2-eb6e-49a4-80eb-87d4076c98c6).
* @return A new Terminal.
*/
export function createTerminal(name?: string, shellPath?: string, shellArgs?: string[]): Terminal;
export function createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): Terminal;
/**
* Creates a [Terminal](#Terminal). The cwd of the terminal will be the workspace directory

View File

@@ -294,6 +294,9 @@ jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', {
default: {},
description: nls.localize('workspaceConfig.extensions.description', "Workspace extensions"),
$ref: 'vscode://schemas/extensions'
},
'remoteAuthority': {
type: 'string'
}
},
additionalProperties: false,

View File

@@ -479,7 +479,7 @@ export function createApiFactory(
createWebviewPanel(viewType: string, title: string, showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn, preserveFocus?: boolean }, options: vscode.WebviewPanelOptions & vscode.WebviewOptions): vscode.WebviewPanel {
return extHostWebviews.createWebviewPanel(extension, viewType, title, showOptions, options);
},
createTerminal(nameOrOptions?: vscode.TerminalOptions | string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
createTerminal(nameOrOptions?: vscode.TerminalOptions | string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
if (typeof nameOrOptions === 'object') {
return extHostTerminalService.createTerminalFromOptions(<vscode.TerminalOptions>nameOrOptions);
}

View File

@@ -148,7 +148,7 @@ export class ExtHostCommands implements ExtHostCommandsShape {
try {
validateConstraint(args[i], description.args[i].constraint);
} catch (err) {
return Promise.reject(new Error(`Running the contributed command:'${id}' failed. Illegal argument '${description.args[i].name}' - ${description.args[i].description}`));
return Promise.reject(new Error(`Running the contributed command: '${id}' failed. Illegal argument '${description.args[i].name}' - ${description.args[i].description}`));
}
}
}
@@ -158,7 +158,7 @@ export class ExtHostCommands implements ExtHostCommandsShape {
return Promise.resolve(result);
} catch (err) {
this._logService.error(err, id);
return Promise.reject(new Error(`Running the contributed command:'${id}' failed.`));
return Promise.reject(new Error(`Running the contributed command: '${id}' failed.`));
}
}

View File

@@ -651,7 +651,7 @@ function convertToModeComment(commentController: ExtHostCommentController, vscod
isDraft: vscodeComment.isDraft,
selectCommand: vscodeComment.selectCommand ? commandsConverter.toInternal(vscodeComment.selectCommand) : undefined,
editCommand: vscodeComment.editCommand ? commandsConverter.toInternal(vscodeComment.editCommand) : undefined,
deleteCommand: vscodeComment.editCommand ? commandsConverter.toInternal(vscodeComment.deleteCommand) : undefined,
deleteCommand: vscodeComment.deleteCommand ? commandsConverter.toInternal(vscodeComment.deleteCommand) : undefined,
label: vscodeComment.label,
commentReactions: vscodeComment.commentReactions ? vscodeComment.commentReactions.map(reaction => convertToReaction2(commentController.reactionProvider, reaction)) : undefined
};

View File

@@ -293,7 +293,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
this._proxy = mainContext.getProxy(MainContext.MainThreadTerminalService);
}
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
const terminal = new ExtHostTerminal(this._proxy, name);
terminal.create(shellPath, shellArgs);
this._terminals.push(terminal);

View File

@@ -2207,12 +2207,12 @@ export class FileSystemError extends Error {
return new FileSystemError(messageOrUri, FileSystemProviderErrorCode.Unavailable, FileSystemError.Unavailable);
}
constructor(uriOrMessage?: string | URI, code?: string, terminator?: Function) {
constructor(uriOrMessage?: string | URI, code: FileSystemProviderErrorCode = FileSystemProviderErrorCode.Unknown, terminator?: Function) {
super(URI.isUri(uriOrMessage) ? uriOrMessage.toString(true) : uriOrMessage);
// mark the error as file system provider error so that
// we can extract the error code on the receiving side
markAsFileSystemProviderError(this);
markAsFileSystemProviderError(this, code);
// workaround when extending builtin objects and when compiling to ES5, see:
// https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work

View File

@@ -15,6 +15,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Schemas } from 'vs/base/common/network';
export class OpenFileAction extends Action {
@@ -34,6 +35,24 @@ export class OpenFileAction extends Action {
}
}
export class OpenLocalFileAction extends Action {
static readonly ID = 'workbench.action.files.openLocalFile';
static LABEL = nls.localize('openLocalFile', "Open Local File...");
constructor(
id: string,
label: string,
@IFileDialogService private readonly dialogService: IFileDialogService
) {
super(id, label);
}
run(event?: any, data?: ITelemetryData): Promise<any> {
return this.dialogService.pickFileAndOpen({ forceNewWindow: false, telemetryExtraData: data, availableFileSystems: [Schemas.file] });
}
}
export class OpenFolderAction extends Action {
static readonly ID = 'workbench.action.files.openFolder';
@@ -52,6 +71,25 @@ export class OpenFolderAction extends Action {
}
}
export class OpenLocalFolderAction extends Action {
static readonly ID = 'workbench.action.files.openLocalFolder';
static LABEL = nls.localize('openLocalFolder', "Open Local Folder...");
constructor(
id: string,
label: string,
@IFileDialogService private readonly dialogService: IFileDialogService
) {
super(id, label);
}
run(event?: any, data?: ITelemetryData): Promise<any> {
return this.dialogService.pickFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data, availableFileSystems: [Schemas.file] });
}
}
export class OpenFileFolderAction extends Action {
static readonly ID = 'workbench.action.files.openFileFolder';
@@ -70,6 +108,24 @@ export class OpenFileFolderAction extends Action {
}
}
export class OpenLocalFileFolderAction extends Action {
static readonly ID = 'workbench.action.files.openLocalFileFolder';
static LABEL = nls.localize('openLocalFileFolder', "Open Local...");
constructor(
id: string,
label: string,
@IFileDialogService private readonly dialogService: IFileDialogService
) {
super(id, label);
}
run(event?: any, data?: ITelemetryData): Promise<any> {
return this.dialogService.pickFileFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data, availableFileSystems: [Schemas.file] });
}
}
export class AddRootFolderAction extends Action {
static readonly ID = 'workbench.action.addRootFolder';

View File

@@ -70,7 +70,7 @@ CommandsRegistry.registerCommand({
}
// Add and show Files Explorer viewlet
return workspaceEditingService.addFolders(folders.map(folder => ({ uri: folder })))
return workspaceEditingService.addFolders(folders.map(folder => ({ uri: resources.removeTrailingPathSeparator(folder) })))
.then(() => viewletService.openViewlet(viewletService.getDefaultViewletId(), true))
.then(() => undefined);
});

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { normalize } from 'vs/base/common/path';
import { basename } from 'vs/base/common/resources';
import { IFileService } from 'vs/platform/files/common/files';
@@ -29,6 +29,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IRecentFile } from 'vs/platform/history/common/history';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
export interface IDraggedResource {
resource: URI;
@@ -154,12 +155,12 @@ export class ResourcesDropHandler {
@IFileService private readonly fileService: IFileService,
@IWindowsService private readonly windowsService: IWindowsService,
@IWindowService private readonly windowService: IWindowService,
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
@ITextFileService private readonly textFileService: ITextFileService,
@IBackupFileService private readonly backupFileService: IBackupFileService,
@IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService
) {
}
@@ -284,26 +285,13 @@ export class ResourcesDropHandler {
// Pass focus to window
this.windowService.focusWindow();
let workspacesToOpen: Promise<IURIToOpen[]> | undefined;
// Open in separate windows if we drop workspaces or just one folder
if (workspaces.length > 0 || folders.length === 1) {
workspacesToOpen = Promise.resolve([...workspaces, ...folders]);
return this.windowService.openWindow([...workspaces, ...folders], { forceReuseWindow: true }).then(_ => true);
}
// Multiple folders: Create new workspace with folders and open
else if (folders.length > 1) {
workspacesToOpen = this.workspacesService.createUntitledWorkspace(folders).then(workspace => [<IURIToOpen>{ uri: workspace.configPath, typeHint: 'file' }]);
}
// Open
if (workspacesToOpen) {
workspacesToOpen.then(workspaces => {
this.windowService.openWindow(workspaces, { forceReuseWindow: true });
});
}
return true;
// folders.length > 1: Multiple folders: Create new workspace with folders and open
return this.workspaceEditingService.createAndEnterWorkspace(folders).then(_ => true);
});
}
}

View File

@@ -1810,7 +1810,7 @@ export class SimpleWorkspacesService implements IWorkspacesService {
_serviceBrand: any;
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[]): Promise<IWorkspaceIdentifier> {
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier> {
// @ts-ignore
return Promise.resolve(undefined);
}

View File

@@ -21,3 +21,5 @@ export const IsDevelopmentContext = new RawContextKey<boolean>('isDevelopment',
export const WorkbenchStateContext = new RawContextKey<string>('workbenchState', undefined);
export const WorkspaceFolderCountContext = new RawContextKey<number>('workspaceFolderCount', 0);
export const RemoteFileDialogContext = new RawContextKey<boolean>('remoteFileDialogVisible', false);

View File

@@ -119,8 +119,8 @@ registerEditorAction(class extends EditorAction {
constructor() {
super({
id: 'editor.showCallHierarchy',
label: localize('title', "Call Hierarchy"),
alias: 'Call Hierarchy',
label: localize('title', "Peek Call Hierarchy"),
alias: 'Peek Call Hierarchy',
menuOpts: {
group: 'navigation',
order: 1.48

View File

@@ -144,7 +144,7 @@ export class CommentNode extends Disposable {
let reactionGroup = this.commentService.getReactionGroup(this.owner);
if (reactionGroup && reactionGroup.length) {
let commentThread = this.commentThread as modes.CommentThread2;
if (commentThread.commentThreadHandle) {
if (commentThread.commentThreadHandle !== undefined) {
let toggleReactionAction = this.createReactionPicker2();
actions.push(toggleReactionAction);
} else {
@@ -327,7 +327,7 @@ export class CommentNode extends Disposable {
let action = new ReactionAction(`reaction.${reaction.label}`, `${reaction.label}`, reaction.hasReacted && reaction.canEdit ? 'active' : '', reaction.canEdit, async () => {
try {
let commentThread = this.commentThread as modes.CommentThread2;
if (commentThread.commentThreadHandle) {
if (commentThread.commentThreadHandle !== undefined) {
await this.commentService.toggleReaction(this.owner, this.resource, this.commentThread as modes.CommentThread2, this.comment, reaction);
} else {
if (reaction.hasReacted) {
@@ -360,7 +360,7 @@ export class CommentNode extends Disposable {
let reactionGroup = this.commentService.getReactionGroup(this.owner);
if (reactionGroup && reactionGroup.length) {
let commentThread = this.commentThread as modes.CommentThread2;
if (commentThread.commentThreadHandle) {
if (commentThread.commentThreadHandle !== undefined) {
let toggleReactionAction = this.createReactionPicker2();
this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true });
} else {
@@ -386,7 +386,7 @@ export class CommentNode extends Disposable {
this._commentEditor.setSelection(new Selection(lastLine, lastColumn, lastLine, lastColumn));
let commentThread = this.commentThread as modes.CommentThread2;
if (commentThread.commentThreadHandle) {
if (commentThread.commentThreadHandle !== undefined) {
commentThread.input = {
uri: this._commentEditor.getModel()!.uri,
value: this.comment.body.value

View File

@@ -689,7 +689,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
async submitComment(): Promise<void> {
const activeComment = this.getActiveComment();
if (activeComment instanceof ReviewZoneWidget) {
if ((this._commentThread as modes.CommentThread2).commentThreadHandle) {
if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
let commentThread = this._commentThread as modes.CommentThread2;
if (commentThread.acceptInputCommand) {

View File

@@ -21,7 +21,7 @@ export class DebugStatus extends Themable implements IStatusbarItem {
private statusBarItem: HTMLElement;
private label: HTMLElement;
private icon: HTMLElement;
private showInStatusBar: string;
private showInStatusBar: 'never' | 'always' | 'onFirstSessionStart';
constructor(
@IQuickOpenService private readonly quickOpenService: IQuickOpenService,
@@ -36,6 +36,10 @@ export class DebugStatus extends Themable implements IStatusbarItem {
this._register(this.debugService.onDidChangeState(state => {
if (state !== State.Inactive && this.showInStatusBar === 'onFirstSessionStart') {
this.doRender();
} else {
if (this.showInStatusBar !== 'never') {
this.updateStyles();
}
}
}));
this.showInStatusBar = configurationService.getValue<IDebugConfiguration>('debug').showInStatusBar;
@@ -53,7 +57,6 @@ export class DebugStatus extends Themable implements IStatusbarItem {
}
protected updateStyles(): void {
super.updateStyles();
if (this.icon) {
if (isStatusbarInDebugMode(this.debugService)) {
this.icon.style.backgroundColor = this.getColor(STATUS_BAR_DEBUGGING_FOREGROUND);

View File

@@ -11,7 +11,7 @@ import { isObject } from 'vs/base/common/types';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IConfig, IDebuggerContribution, IDebugAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession, IAdapterDescriptor, IDebugAdapterServer } from 'vs/workbench/contrib/debug/common/debug';
import { IConfig, IDebuggerContribution, IDebugAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession, IAdapterDescriptor, IDebugAdapterServer, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
@@ -187,8 +187,11 @@ export class Debugger implements IDebugger {
}
private inExtHost(): boolean {
/*
const debugConfigs = this.configurationService.getValue<IDebugConfiguration>('debug');
if (typeof debugConfigs.extensionHostDebugAdapter === 'boolean') {
return debugConfigs.extensionHostDebugAdapter;
}
/*
return !!debugConfigs.extensionHostDebugAdapter
|| this.configurationManager.needsToRunInExtHost(this.type)
|| (!!this.mainExtensionDescription && this.mainExtensionDescription.extensionLocation.scheme !== 'file');

View File

@@ -429,6 +429,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
private toDispose: IDisposable[];
private dropEnabled: boolean;
private isCopy: boolean;
constructor(
@INotificationService private notificationService: INotificationService,
@@ -549,7 +550,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
}
getDragURI(element: ExplorerItem): string | null {
if (this.explorerService.isEditable(element)) {
if (this.explorerService.isEditable(element) || (!this.isCopy && element.isReadonly)) {
return null;
}
@@ -565,6 +566,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
this.isCopy = (originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh);
const items = (data as ElementsDragAndDropData<ExplorerItem>).elements;
if (items && items.length && originalEvent.dataTransfer) {
// Apply some datatransfer types to allow for dragging the element outside of the application
@@ -597,7 +599,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
}
// In-Explorer DND (Move/Copy file)
else {
this.handleExplorerDrop(data, target, originalEvent);
this.handleExplorerDrop(data, target);
}
}
@@ -711,15 +713,14 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
return Promise.resolve(undefined);
}
private handleExplorerDrop(data: IDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
private handleExplorerDrop(data: IDragAndDropData, target: ExplorerItem): Promise<void> {
const elementsData = (data as ElementsDragAndDropData<ExplorerItem>).elements;
const items = distinctParents(elementsData, s => s.resource);
const isCopy = (originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh);
let confirmPromise: Promise<IConfirmationResult>;
// Handle confirm setting
const confirmDragAndDrop = !isCopy && this.configurationService.getValue<boolean>(FileDragAndDrop.CONFIRM_DND_SETTING_KEY);
const confirmDragAndDrop = !this.isCopy && this.configurationService.getValue<boolean>(FileDragAndDrop.CONFIRM_DND_SETTING_KEY);
if (confirmDragAndDrop) {
confirmPromise = this.dialogService.confirm({
message: items.length > 1 && items.every(s => s.isRoot) ? localize('confirmRootsMove', "Are you sure you want to change the order of multiple root folders in your workspace?")
@@ -747,7 +748,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
return updateConfirmSettingsPromise.then(() => {
if (res.confirmed) {
const rootDropPromise = this.doHandleRootDrop(items.filter(s => s.isRoot), target);
return Promise.all(items.filter(s => !s.isRoot).map(source => this.doHandleExplorerDrop(source, target, isCopy)).concat(rootDropPromise)).then(() => undefined);
return Promise.all(items.filter(s => !s.isRoot).map(source => this.doHandleExplorerDrop(source, target, this.isCopy)).concat(rootDropPromise)).then(() => undefined);
}
return Promise.resolve(undefined);

View File

@@ -40,6 +40,7 @@ export class ExplorerService implements IExplorerService {
private editable: { stat: ExplorerItem, data: IEditableData } | undefined;
private _sortOrder: SortOrder;
private cutItems: ExplorerItem[] | undefined;
private fileSystemProviderSchemes = new Set<string>();
constructor(
@IFileService private fileService: IFileService,
@@ -98,7 +99,14 @@ export class ExplorerService implements IExplorerService {
this.disposables.push(this.fileService.onAfterOperation(e => this.onFileOperation(e)));
this.disposables.push(this.fileService.onFileChanges(e => this.onFileChanges(e)));
this.disposables.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue<IFilesConfiguration>())));
this.disposables.push(this.fileService.onDidChangeFileSystemProviderRegistrations(() => this._onDidChangeItem.fire(undefined)));
this.disposables.push(this.fileService.onDidChangeFileSystemProviderRegistrations(e => {
if (e.added && this.fileSystemProviderSchemes.has(e.scheme)) {
// A file system provider got re-registered, we should update all file stats since they might change (got read-only)
this._onDidChangeItem.fire(undefined);
} else {
this.fileSystemProviderSchemes.add(e.scheme);
}
}));
this.disposables.push(model.onDidChangeRoots(() => this._onDidChangeRoots.fire()));
return model;

View File

@@ -61,15 +61,16 @@ export class ActivityUpdater extends Disposable implements IWorkbenchContributio
constructor(
@IActivityService private readonly activityService: IActivityService,
@IMarkersWorkbenchService private readonly markersWorkbenchService: IMarkersWorkbenchService
@IMarkerService private readonly markerService: IMarkerService
) {
super();
this._register(this.markersWorkbenchService.markersModel.onDidChange(() => this.updateBadge()));
this._register(this.markerService.onMarkerChanged(() => this.updateBadge()));
this.updateBadge();
}
private updateBadge(): void {
const total = this.markersWorkbenchService.markersModel.resourceMarkers.reduce((r, rm) => r + rm.markers.length, 0);
const { errors, warnings, infos, unknowns } = this.markerService.getStatistics();
const total = errors + warnings + infos + unknowns;
const message = localize('totalProblems', 'Total {0} Problems', total);
this.activityService.showActivity(Constants.MARKERS_PANEL_ID, new NumberBadge(total, () => message));
}

View File

@@ -160,7 +160,7 @@
.markers-panel .monaco-tl-contents .marker-icon {
height: 22px;
flex: 0 0 16px;
width: 16px;
}
.markers-panel .marker-icon.warning {

View File

@@ -943,7 +943,9 @@ export namespace KeyedTaskIdentifier {
}
export function create(value: TaskIdentifier): KeyedTaskIdentifier {
const resultKey = sortedStringify(value);
return { _key: resultKey, type: value.taskType };
let result = { _key: resultKey, type: value.taskType };
Objects.assign(result, value);
return result;
}
}

View File

@@ -890,7 +890,8 @@ export class TerminalTaskSystem implements ITaskSystem {
this.currentTask.shellLaunchConfig = {
isRendererOnly: true,
waitOnExit,
name: this.createTerminalName(task)
name: this.createTerminalName(task),
initialText: task.command.presentation && task.command.presentation.echo ? `\x1b[1m> Executing task: ${task._label} <\x1b[0m\n` : undefined
};
} else {
let resolvedResult: { command: CommandString, args: CommandString[] } = this.resolveCommandAndArgs(resolver, task.command);

View File

@@ -955,7 +955,7 @@ export class TerminalInstance implements ITerminalInstance {
if (typeof this._shellLaunchConfig.waitOnExit === 'string') {
let message = this._shellLaunchConfig.waitOnExit;
// Bold the message and add an extra new line to make it stand out from the rest of the output
message = `\n\x1b[1m${message}\x1b[0m`;
message = `\r\n\x1b[1m${message}\x1b[0m`;
this._xterm.writeln(message);
}
// Disable all input if the terminal is exiting and listen for next keypress

View File

@@ -14,14 +14,14 @@ import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform';
import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction } from 'vs/workbench/electron-browser/actions/helpActions';
import { ToggleSharedProcessAction, InspectContextKeysAction, ToggleScreencastModeAction, ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions';
import { ShowAboutDialogAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, OpenRecentAction, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ReloadWindowAction, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions';
import { AddRootFolderAction, GlobalRemoveRootFolderAction, OpenWorkspaceAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, OpenFileFolderAction, OpenFileAction, OpenFolderAction, CloseWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions';
import { AddRootFolderAction, GlobalRemoveRootFolderAction, OpenWorkspaceAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, OpenFileFolderAction, OpenFileAction, OpenFolderAction, CloseWorkspaceAction, OpenLocalFileAction, OpenLocalFolderAction, OpenLocalFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { inQuickOpenContext, getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ADD_ROOT_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/common/contextkeys';
import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext, RemoteFileDialogContext } from 'vs/workbench/common/contextkeys';
import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { LogStorageAction } from 'vs/platform/storage/node/storageService';
@@ -40,9 +40,12 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/electron-brow
if (isMacintosh) {
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLocalFileFolderAction, OpenLocalFileFolderAction.ID, OpenLocalFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, RemoteFileDialogContext), 'File: Open Local...', fileCategory, RemoteFileDialogContext);
} else {
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLocalFileAction, OpenLocalFileAction.ID, OpenLocalFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, RemoteFileDialogContext), 'File: Open Local File...', fileCategory, RemoteFileDialogContext);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLocalFolderAction, OpenLocalFolderAction.ID, OpenLocalFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }, RemoteFileDialogContext), 'File: Open Local Folder...', fileCategory, RemoteFileDialogContext);
}
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory);

View File

@@ -181,7 +181,7 @@ class CodeRendererMain extends Disposable {
const fileService = new FileService2(logService);
serviceCollection.set(IFileService, fileService);
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider());
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(logService));
// Remote
const remoteAuthorityResolverService = new RemoteAuthorityResolverService();

View File

@@ -22,6 +22,8 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { RemoteFileDialogContext } from 'vs/workbench/common/contextkeys';
interface FileQuickPickItem extends IQuickPickItem {
uri: URI;
@@ -48,6 +50,7 @@ export class RemoteFileDialog {
private scheme: string = REMOTE_HOST_SCHEME;
private shouldOverwriteFile: boolean = false;
private autoComplete: string;
private contextKey: IContextKey<boolean>;
constructor(
@IFileService private readonly fileService: IFileService,
@@ -61,9 +64,11 @@ export class RemoteFileDialog {
@IModeService private readonly modeService: IModeService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IContextKeyService contextKeyService: IContextKeyService
) {
this.remoteAuthority = this.windowService.getConfiguration().remoteAuthority;
this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService);
}
public async showOpenDialog(options: IOpenDialogOptions = {}): Promise<IURIToOpen[] | undefined> {
@@ -245,10 +250,12 @@ export class RemoteFileDialog {
if (!isResolving) {
resolve(undefined);
}
this.contextKey.set(false);
this.filePickBox.dispose();
});
this.filePickBox.show();
this.contextKey.set(true);
this.updateItems(homedir, trailing);
if (trailing) {
this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - ext.length];

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { IFileService, IResolveFileOptions, IResourceEncodings, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, IResolveContentOptions, IContent, IStreamContent, ITextSnapshot, IUpdateContentOptions, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files';
import { IFileService, IResolveFileOptions, IResourceEncodings, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, IResolveContentOptions, IContent, IStreamContent, ITextSnapshot, IUpdateContentOptions, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
@@ -252,7 +252,7 @@ export class FileService2 extends Disposable implements IFileService {
}
async resolveFiles(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise<IResolveFileResult[]>;
async resolveFiles(toResolve: { resource: URI, options: IResolveMetadataFileOptions }[]): Promise<IResolveFileResult[]>;
async resolveFiles(toResolve: { resource: URI, options: IResolveMetadataFileOptions }[]): Promise<IResolveFileResultWithMetadata[]>;
async resolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): Promise<IResolveFileResult[]> {
return Promise.all(toResolve.map(async entry => {
try {
@@ -600,14 +600,17 @@ export class FileService2 extends Disposable implements IFileService {
private async doWriteBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, buffer: Uint8Array): Promise<void> {
// Open handle
// open handle
const handle = await provider.open(resource, { create: true });
// write into handle until all bytes from buffer have been written
await this.doWriteBuffer(provider, handle, buffer, buffer.byteLength, 0, 0);
// Close handle
return provider.close(handle);
try {
await this.doWriteBuffer(provider, handle, buffer, buffer.byteLength, 0, 0);
} catch (error) {
throw error;
} finally {
await provider.close(handle);
}
}
private async doWriteBuffer(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, handle: number, buffer: Uint8Array, length: number, posInFile: number, posInBuffer: number): Promise<void> {
@@ -623,39 +626,45 @@ export class FileService2 extends Disposable implements IFileService {
}
private async doPipeBuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise<void> {
let sourceHandle: number | undefined = undefined;
let targetHandle: number | undefined = undefined;
// Open handles
const sourceHandle = await sourceProvider.open(source, { create: false });
const targetHandle = await targetProvider.open(target, { create: true });
try {
const buffer = new Uint8Array(8 * 1024);
// Open handles
sourceHandle = await sourceProvider.open(source, { create: false });
targetHandle = await targetProvider.open(target, { create: true });
let posInFile = 0;
let posInBuffer = 0;
let bytesRead = 0;
do {
// read from source (sourceHandle) at current position (posInFile) into buffer (buffer) at
// buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength).
bytesRead = await sourceProvider.read(sourceHandle, posInFile, buffer, posInBuffer, buffer.byteLength - posInBuffer);
const buffer = new Uint8Array(16 * 1024);
// write into target (targetHandle) at current position (posInFile) from buffer (buffer) at
// buffer position (posInBuffer) all bytes we read (bytesRead).
await this.doWriteBuffer(targetProvider, targetHandle, buffer, bytesRead, posInFile, posInBuffer);
let posInFile = 0;
let posInBuffer = 0;
let bytesRead = 0;
do {
// read from source (sourceHandle) at current position (posInFile) into buffer (buffer) at
// buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength).
bytesRead = await sourceProvider.read(sourceHandle, posInFile, buffer, posInBuffer, buffer.byteLength - posInBuffer);
posInFile += bytesRead;
posInBuffer += bytesRead;
// write into target (targetHandle) at current position (posInFile) from buffer (buffer) at
// buffer position (posInBuffer) all bytes we read (bytesRead).
await this.doWriteBuffer(targetProvider, targetHandle, buffer, bytesRead, posInFile, posInBuffer);
// when buffer full, fill it again from the beginning
if (posInBuffer === buffer.length) {
posInBuffer = 0;
}
} while (bytesRead > 0);
posInFile += bytesRead;
posInBuffer += bytesRead;
// Close handles
return Promise.all([
sourceProvider.close(sourceHandle),
targetProvider.close(targetHandle)
]).then(() => undefined);
// when buffer full, fill it again from the beginning
if (posInBuffer === buffer.length) {
posInBuffer = 0;
}
} while (bytesRead > 0);
} catch (error) {
throw error;
} finally {
await Promise.all([
typeof sourceHandle === 'number' ? sourceProvider.close(sourceHandle) : Promise.resolve(),
typeof targetHandle === 'number' ? targetProvider.close(targetHandle) : Promise.resolve(),
]);
}
}
private async doPipeUnbuffered(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI, overwrite: boolean): Promise<void> {
@@ -668,11 +677,14 @@ export class FileService2 extends Disposable implements IFileService {
const targetHandle = await targetProvider.open(target, { create: true });
// Read entire buffer from source and write buffered
const buffer = await sourceProvider.readFile(source);
await this.doWriteBuffer(targetProvider, targetHandle, buffer, buffer.byteLength, 0, 0);
// Close handle
return targetProvider.close(targetHandle);
try {
const buffer = await sourceProvider.readFile(source);
await this.doWriteBuffer(targetProvider, targetHandle, buffer, buffer.byteLength, 0, 0);
} catch (error) {
throw error;
} finally {
await targetProvider.close(targetHandle);
}
}
private async doPipeBufferedToUnbuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI, overwrite: boolean): Promise<void> {
@@ -683,23 +695,26 @@ export class FileService2 extends Disposable implements IFileService {
// Open handle
const sourceHandle = await sourceProvider.open(source, { create: false });
const buffer = new Uint8Array(size);
try {
const buffer = new Uint8Array(size);
let pos = 0;
let bytesRead = 0;
do {
// read from source (sourceHandle) at current position (posInFile) into buffer (buffer) at
// buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength).
bytesRead = await sourceProvider.read(sourceHandle, pos, buffer, pos, buffer.byteLength - pos);
let pos = 0;
let bytesRead = 0;
do {
// read from source (sourceHandle) at current position (posInFile) into buffer (buffer) at
// buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength).
bytesRead = await sourceProvider.read(sourceHandle, pos, buffer, pos, buffer.byteLength - pos);
pos += bytesRead;
} while (bytesRead > 0);
pos += bytesRead;
} while (bytesRead > 0 && pos < size);
// Write buffer into target at once
await this.doWriteUnbuffered(targetProvider, target, buffer, overwrite);
// Close handle
return sourceProvider.close(sourceHandle);
// Write buffer into target at once
await this.doWriteUnbuffered(targetProvider, target, buffer, overwrite);
} catch (error) {
throw error;
} finally {
await sourceProvider.close(sourceHandle);
}
}
private throwIfFileSystemIsReadonly(provider: IFileSystemProvider): IFileSystemProvider {

View File

@@ -9,9 +9,14 @@ import { FileDeleteOptions, FileSystemProviderCapabilities } from 'vs/platform/f
import { isWindows } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { basename } from 'vs/base/common/path';
import { ILogService } from 'vs/platform/log/common/log';
export class DiskFileSystemProvider extends NodeDiskFileSystemProvider {
constructor(logService: ILogService) {
super(logService);
}
get capabilities(): FileSystemProviderCapabilities {
if (!this._capabilities) {
this._capabilities = super.capabilities | FileSystemProviderCapabilities.Trash;

View File

@@ -16,9 +16,14 @@ import { normalize } from 'vs/base/common/path';
import { joinPath } from 'vs/base/common/resources';
import { isEqual } from 'vs/base/common/extpath';
import { retry } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
export class DiskFileSystemProvider extends Disposable implements IFileSystemProvider {
constructor(private logService: ILogService) {
super();
}
//#region File Capabilities
onDidChangeCapabilities: Event<void> = Event.None;
@@ -73,8 +78,12 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
for (let i = 0; i < children.length; i++) {
const child = children[i];
const stat = await this.stat(joinPath(resource, child));
result.push([child, stat.type]);
try {
const stat = await this.stat(joinPath(resource, child));
result.push([child, stat.type]);
} catch (error) {
this.logService.trace(error); // ignore errors for individual entries that can arise from permission denied
}
}
return result;
@@ -112,7 +121,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
if (exists && isWindows) {
try {
// On Windows and if the file exists, we use a different strategy of saving the file
// by first truncating the file and then writing with r+ mode. This helps to save hidden files on Windows
// by first truncating the file and then writing with r+ flag. This helps to save hidden files on Windows
// (see https://github.com/Microsoft/vscode/issues/931) and prevent removing alternate data streams
// (see https://github.com/Microsoft/vscode/issues/6363)
await truncate(filePath, 0);
@@ -123,6 +132,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
// short timeout, assuming that the file is free to write then.
await retry(() => writeFile(filePath, content, { flag: 'r+' }), 100 /* ms delay */, 3 /* retries */);
} catch (error) {
this.logService.trace(error);
// we heard from users that fs.truncate() fails (https://github.com/Microsoft/vscode/issues/59561)
// in that case we simply save the file without truncating first (same as macOS and Linux)
await writeFile(filePath, content);
@@ -142,20 +153,20 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
try {
const filePath = this.toFilePath(resource);
let mode: string;
let flags: string;
if (opts.create) {
// we take this as a hint that the file is opened for writing
// as such we use 'w' to truncate an existing or create the
// file otherwise. we do not allow reading.
mode = 'w';
flags = 'w';
} else {
// otherwise we assume the file is opened for reading
// as such we use 'r' to neither truncate, nor create
// the file.
mode = 'r';
flags = 'r';
}
return await promisify(open)(filePath, mode);
return await promisify(open)(filePath, flags);
} catch (error) {
throw this.toFileSystemProviderError(error);
}
@@ -300,7 +311,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
return error; // avoid double conversion
}
let code: FileSystemProviderErrorCode | undefined = undefined;
let code: FileSystemProviderErrorCode;
switch (error.code) {
case 'ENOENT':
code = FileSystemProviderErrorCode.FileNotFound;
@@ -315,6 +326,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
case 'EACCESS':
code = FileSystemProviderErrorCode.NoPermissions;
break;
default:
code = FileSystemProviderErrorCode.Unknown;
}
return createFileSystemProviderError(error, code);

View File

@@ -17,8 +17,10 @@ import { URI } from 'vs/base/common/uri';
import { existsSync, statSync, readdirSync, readFileSync } from 'fs';
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { NullLogService } from 'vs/platform/log/common/log';
import { isLinux } from 'vs/base/common/platform';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { promisify } from 'util';
import { exec } from 'child_process';
function getByName(root: IFileStat, name: string): IFileStat | null {
if (root.children === undefined) {
@@ -70,13 +72,15 @@ suite('Disk File Service', () => {
let disposables: IDisposable[] = [];
setup(async () => {
service = new FileService2(new NullLogService());
const logService = new NullLogService();
service = new FileService2(logService);
disposables.push(service);
fileProvider = new TestDiskFileSystemProvider();
fileProvider = new TestDiskFileSystemProvider(logService);
service.registerProvider(Schemas.file, fileProvider);
testProvider = new TestDiskFileSystemProvider();
testProvider = new TestDiskFileSystemProvider(logService);
service.registerProvider(testSchema, testProvider);
const id = generateUuid();
@@ -307,6 +311,45 @@ suite('Disk File Service', () => {
assert.equal(r2.name, 'deep');
});
test('resolveFile - folder symbolic link', async () => {
if (isWindows) {
return; // only for unix systems
}
const link = URI.file(join(testDir, 'deep-link'));
await promisify(exec)(`ln -s deep ${basename(link.fsPath)}`, { cwd: testDir });
const resolved = await service.resolveFile(link);
assert.equal(resolved.children!.length, 4);
assert.equal(resolved.isDirectory, true);
assert.equal(resolved.isSymbolicLink, true);
});
test('resolveFile - file symbolic link', async () => {
if (isWindows) {
return; // only for unix systems
}
const link = URI.file(join(testDir, 'lorem.txt-linked'));
await promisify(exec)(`ln -s lorem.txt ${basename(link.fsPath)}`, { cwd: testDir });
const resolved = await service.resolveFile(link);
assert.equal(resolved.isDirectory, false);
assert.equal(resolved.isSymbolicLink, true);
});
test('resolveFile - invalid symbolic link does not break', async () => {
if (isWindows) {
return; // only for unix systems
}
await promisify(exec)('ln -s foo bar', { cwd: testDir });
const resolved = await service.resolveFile(URI.file(testDir));
assert.equal(resolved.isDirectory, true);
assert.equal(resolved.children!.length, 8);
});
test('deleteFile', async () => {
let event: FileOperationEvent;
disposables.push(service.onAfterOperation(e => event = e));

View File

@@ -21,7 +21,7 @@ import { BackupFileService } from 'vs/workbench/services/backup/node/backupFileS
import { ICommandService } from 'vs/platform/commands/common/commands';
import { distinct } from 'vs/base/common/arrays';
import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform';
import { isEqual, basename, isEqualOrParent } from 'vs/base/common/resources';
import { isEqual, basename, isEqualOrParent, getComparisonKey } from 'vs/base/common/resources';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IFileService } from 'vs/platform/files/common/files';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -205,7 +205,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
if (state !== WorkbenchState.WORKSPACE) {
let newWorkspaceFolders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri } as IWorkspaceFolderCreationData));
newWorkspaceFolders.splice(typeof index === 'number' ? index : newWorkspaceFolders.length, 0, ...foldersToAdd);
newWorkspaceFolders = distinct(newWorkspaceFolders, folder => isLinux ? folder.uri.toString() : folder.uri.toString().toLowerCase());
newWorkspaceFolders = distinct(newWorkspaceFolders, folder => getComparisonKey(folder.uri));
if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) {
return Promise.resolve(); // return if the operation is a no-op for the current state
@@ -245,8 +245,8 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
if (path && !this.isValidTargetWorkspacePath(path)) {
return Promise.reject(null);
}
const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders);
const remoteAuthority = this.windowService.getConfiguration().remoteAuthority;
const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders, remoteAuthority);
if (path) {
await this.saveWorkspaceAs(untitledWorkspace, path);
} else {