Merge from vscode 63655183ba5305b70ffaf1327b8a4708f0a79bd9 (#5221)

This commit is contained in:
Anthony Dresser
2019-04-25 19:18:04 -07:00
committed by GitHub
parent 705e7b30bc
commit 3625834028
9 changed files with 73 additions and 54 deletions

View File

@@ -34,6 +34,7 @@ namespace schema {
case 'explorer/context': return MenuId.ExplorerContext;
case 'editor/title/context': return MenuId.EditorTitleContext;
case 'debug/callstack/context': return MenuId.DebugCallStackContext;
case 'debug/toolbar': return MenuId.DebugToolBar;
case 'debug/toolBar': return MenuId.DebugToolBar;
case 'menuBar/file': return MenuId.MenubarFileMenu;
case 'scm/title': return MenuId.SCMTitle;

View File

@@ -13,8 +13,7 @@ import { IExtensionManagementServerService, IExtensionTipsService } from 'vs/pla
import { ILabelService } from 'vs/platform/label/common/label';
import { extensionButtonProminentBackground, extensionButtonProminentForeground, DisabledLabelAction, ReloadAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { Emitter, Event } from 'vs/base/common/event';
@@ -310,7 +309,6 @@ class RemoteBadge extends Disposable {
private readonly tooltip: boolean,
@ILabelService private readonly labelService: ILabelService,
@IThemeService private readonly themeService: IThemeService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
super();
@@ -326,13 +324,12 @@ class RemoteBadge extends Disposable {
return;
}
const bgColor = this.themeService.getTheme().getColor(STATUS_BAR_HOST_NAME_BACKGROUND);
const fgColor = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY ? this.themeService.getTheme().getColor(STATUS_BAR_NO_FOLDER_FOREGROUND) : this.themeService.getTheme().getColor(STATUS_BAR_FOREGROUND);
const fgColor = this.themeService.getTheme().getColor(STATUS_BAR_HOST_NAME_FOREGROUND);
this.element.style.backgroundColor = bgColor ? bgColor.toString() : '';
this.element.style.color = fgColor ? fgColor.toString() : '';
};
applyBadgeStyle();
this._register(this.themeService.onThemeChange(() => applyBadgeStyle()));
this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => applyBadgeStyle()));
if (this.tooltip) {
const updateTitle = () => {

View File

@@ -110,11 +110,9 @@
height: 22px;
line-height: 22px;
border-radius: 20px;
text-align: center;
}
.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container .extension-remote-badge > .octicon {
vertical-align: middle
display: flex;
align-items: center;
justify-content: center;
}
.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header > .extension-remote-badge-container {

View File

@@ -166,7 +166,7 @@ export class GlobalNewUntitledFileAction extends Action {
}
}
function deleteFiles(serviceAccesor: ServicesAccessor, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, fileService: IFileService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
let primaryButton: string;
if (useTrash) {
primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash");
@@ -175,10 +175,6 @@ function deleteFiles(serviceAccesor: ServicesAccessor, elements: ExplorerItem[],
}
const distinctElements = resources.distinctParents(elements, e => e.resource);
const textFileService = serviceAccesor.get(ITextFileService);
const dialogService = serviceAccesor.get(IDialogService);
const configurationService = serviceAccesor.get(IConfigurationService);
const fileService = serviceAccesor.get(IFileService);
// Handle dirty
let confirmDirtyPromise: Promise<boolean> = Promise.resolve(true);
@@ -296,7 +292,7 @@ function deleteFiles(serviceAccesor: ServicesAccessor, elements: ExplorerItem[],
skipConfirm = true;
return deleteFiles(serviceAccesor, elements, useTrash, skipConfirm);
return deleteFiles(textFileService, dialogService, configurationService, fileService, elements, useTrash, skipConfirm);
}
return Promise.resolve();
@@ -1011,7 +1007,7 @@ export const moveFileToTrashHandler = (accessor: ServicesAccessor) => {
const explorerContext = getContext(listService.lastFocusedList);
const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat!];
return deleteFiles(accessor, stats, true);
return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, true);
};
export const deleteFileHandler = (accessor: ServicesAccessor) => {
@@ -1022,7 +1018,7 @@ export const deleteFileHandler = (accessor: ServicesAccessor) => {
const explorerContext = getContext(listService.lastFocusedList);
const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat!];
return deleteFiles(accessor, stats, false);
return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, false);
};
let pasteShouldMove = false;

View File

@@ -474,12 +474,6 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
if (typesArray.indexOf(DataTransfers.FILES.toLowerCase()) === -1 && typesArray.indexOf(CodeDataTransfers.FILES.toLowerCase()) === -1) {
return false;
}
if (this.environmentService.configuration.remoteAuthority) {
const resources = extractResources(originalEvent, true);
if (resources.some(r => r.resource.authority !== this.environmentService.configuration.remoteAuthority)) {
return false;
}
}
}
// Other-Tree DND
@@ -611,6 +605,11 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
private handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
const droppedResources = extractResources(originalEvent, true);
if (this.environmentService.configuration.remoteAuthority) {
if (droppedResources.some(r => r.resource.authority !== this.environmentService.configuration.remoteAuthority)) {
return Promise.resolve();
}
}
// Check for dropped external files to be folders
return this.fileService.resolveAll(droppedResources).then(result => {

View File

@@ -155,7 +155,7 @@ export class ExplorerService implements IExplorerService {
}
// Stat needs to be resolved first and then revealed
const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: false };
const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.sortOrder === 'modified' };
const workspaceFolder = this.contextService.getWorkspaceFolder(resource);
const rootUri = workspaceFolder ? workspaceFolder.uri : this.roots[0].resource;
const root = this.roots.filter(r => r.resource.toString() === rootUri.toString()).pop()!;

View File

@@ -9,7 +9,7 @@ import * as objects from 'vs/base/common/objects';
import { IFileService, IFileStat, FileKind } from 'vs/platform/files/common/files';
import { IQuickInputService, IQuickPickItem, IQuickPick } from 'vs/platform/quickinput/common/quickInput';
import { URI } from 'vs/base/common/uri';
import { isWindows } from 'vs/base/common/platform';
import { isWindows, OperatingSystem } from 'vs/base/common/platform';
import { ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { ILabelService } from 'vs/platform/label/common/label';
@@ -26,6 +26,7 @@ import { RemoteFileDialogContext } from 'vs/workbench/common/contextkeys';
import { equalsIgnoreCase, format } from 'vs/base/common/strings';
import { OpenLocalFileAction, OpenLocalFileFolderAction, OpenLocalFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
interface FileQuickPickItem extends IQuickPickItem {
uri: URI;
@@ -40,7 +41,8 @@ enum UpdateResult {
}
// Reference: https://en.wikipedia.org/wiki/Filename
const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g;
const WINDOWS_INVALID_FILE_CHARS = /[\\/:\*\?"<>\|]/g;
const UNIX_INVALID_FILE_CHARS = /[\\/]/g;
const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])$/i;
export class RemoteFileDialog {
@@ -60,6 +62,7 @@ export class RemoteFileDialog {
private activeItem: FileQuickPickItem;
private userHome: URI;
private badPath: string | undefined;
private remoteAgentEnvironment: IRemoteAgentEnvironment | null;
constructor(
@IFileService private readonly fileService: IFileService,
@@ -136,9 +139,16 @@ export class RemoteFileDialog {
return defaultUri ? defaultUri.scheme : (available ? available[0] : Schemas.file);
}
private async getRemoteAgentEnvironment(): Promise<IRemoteAgentEnvironment | null> {
if (this.remoteAgentEnvironment === undefined) {
this.remoteAgentEnvironment = await this.remoteAgentService.getEnvironment();
}
return this.remoteAgentEnvironment;
}
private async getUserHome(): Promise<URI> {
if (this.scheme !== Schemas.file) {
const env = await this.remoteAgentService.getEnvironment();
const env = await this.getRemoteAgentEnvironment();
if (env) {
return env.userHome;
}
@@ -291,7 +301,7 @@ export class RemoteFileDialog {
this.filePickBox.show();
this.contextKey.set(true);
await this.updateItems(homedir, this.trailing);
await this.updateItems(homedir, false, this.trailing);
if (this.trailing) {
this.filePickBox.valueSelection = [this.filePickBox.value.length - this.trailing.length, this.filePickBox.value.length - ext.length];
} else {
@@ -361,7 +371,7 @@ export class RemoteFileDialog {
}
} else if (navigateValue) {
// Try to navigate into the folder.
await this.updateItems(navigateValue, this.trailing);
await this.updateItems(navigateValue, true, this.trailing);
} else {
// validation error. Path does not exist.
}
@@ -373,8 +383,12 @@ export class RemoteFileDialog {
if (this.filePickBox.busy) {
this.badPath = undefined;
return UpdateResult.Updating;
} else if (value[value.length - 1] === '~') {
await this.updateItems(this.userHome);
} else if ((value.length > 0) && ((value[value.length - 1] === '~') || (value[0] === '~'))) {
let newDir = this.userHome;
if ((value[0] === '~') && (value.length > 1)) {
newDir = resources.joinPath(newDir, value.substring(1));
}
await this.updateItems(newDir, true);
return UpdateResult.Updated;
} else if (this.endsWithSlash(value) || (!resources.isEqual(this.currentFolder, resources.dirname(valueUri), true) && resources.isEqualOrParent(this.currentFolder, resources.dirname(valueUri), true))) {
let stat: IFileStat | undefined;
@@ -403,7 +417,7 @@ export class RemoteFileDialog {
// do nothing
}
if (statWithoutTrailing && statWithoutTrailing.isDirectory && (resources.basename(valueUri) !== '.')) {
await this.updateItems(inputUriDirname, resources.basename(valueUri));
await this.updateItems(inputUriDirname, false, resources.basename(valueUri));
this.badPath = undefined;
return UpdateResult.Updated;
}
@@ -501,18 +515,16 @@ export class RemoteFileDialog {
// Make sure that the suffix is added. If the user deleted it, we automatically add it here
let hasExt: boolean = false;
const currentExt = resources.extname(uri).substr(1);
if (currentExt !== '') {
for (let i = 0; i < this.options.filters.length; i++) {
for (let j = 0; j < this.options.filters[i].extensions.length; j++) {
if ((this.options.filters[i].extensions[j] === '*') || (this.options.filters[i].extensions[j] === currentExt)) {
hasExt = true;
break;
}
}
if (hasExt) {
for (let i = 0; i < this.options.filters.length; i++) {
for (let j = 0; j < this.options.filters[i].extensions.length; j++) {
if ((this.options.filters[i].extensions[j] === '*') || (this.options.filters[i].extensions[j] === currentExt)) {
hasExt = true;
break;
}
}
if (hasExt) {
break;
}
}
if (!hasExt) {
result = resources.joinPath(resources.dirname(uri), resources.basename(uri) + '.' + this.options.filters[0].extensions[0]);
@@ -583,7 +595,7 @@ export class RemoteFileDialog {
// Show a yes/no prompt
const message = nls.localize('remoteFileDialog.validateExisting', '{0} already exists. Are you sure you want to overwrite it?', resources.basename(uri));
return this.yesNoPrompt(uri, message);
} else if (!this.isValidBaseName(resources.basename(uri))) {
} else if (!(await this.isValidBaseName(resources.basename(uri)))) {
// Filename not allowed
this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateBadFilename', 'Please enter a valid file name.');
return Promise.resolve(false);
@@ -610,7 +622,7 @@ export class RemoteFileDialog {
return Promise.resolve(true);
}
private async updateItems(newFolder: URI, trailing?: string) {
private async updateItems(newFolder: URI, force: boolean = false, trailing?: string) {
this.filePickBox.busy = true;
this.userEnteredPathSegment = trailing ? trailing : '';
this.autoCompletePathSegment = '';
@@ -628,13 +640,18 @@ export class RemoteFileDialog {
if (!equalsIgnoreCase(this.filePickBox.value.substring(0, newValue.length), newValue)) {
this.filePickBox.valueSelection = [0, this.filePickBox.value.length];
this.insertText(newValue, newValue);
} else if (equalsIgnoreCase(this.pathFromUri(resources.dirname(oldFolder), true), newFolderPath)) {
// This is the case where the user went up one dir. We need to make sure that we remove the final dir.
} else if (force || equalsIgnoreCase(this.pathFromUri(resources.dirname(oldFolder), true), newFolderPath)) {
// This is the case where the user went up one dir or is clicking on dirs. We need to make sure that we remove the final dir.
this.filePickBox.valueSelection = [newFolderPath.length, this.filePickBox.value.length];
this.insertText(newValue, '');
}
}
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
if (force && trailing) {
// Keep the cursor position in front of the save as name.
this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - trailing.length];
} else {
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
}
this.filePickBox.busy = false;
});
}
@@ -662,17 +679,28 @@ export class RemoteFileDialog {
}
}
private isValidBaseName(name: string): boolean {
private async isWindowsOS(): Promise<boolean> {
let isWindowsOS = isWindows;
const env = await this.getRemoteAgentEnvironment();
if (env) {
isWindowsOS = env.os === OperatingSystem.Windows;
}
return isWindowsOS;
}
private async isValidBaseName(name: string): Promise<boolean> {
if (!name || name.length === 0 || /^\s+$/.test(name)) {
return false; // require a name that is not just whitespace
}
const isWindowsOS = await this.isWindowsOS();
const INVALID_FILE_CHARS = isWindowsOS ? WINDOWS_INVALID_FILE_CHARS : UNIX_INVALID_FILE_CHARS;
INVALID_FILE_CHARS.lastIndex = 0; // the holy grail of software development
if (INVALID_FILE_CHARS.test(name)) {
return false; // check for certain invalid file characters
}
if (isWindows && WINDOWS_FORBIDDEN_NAMES.test(name)) {
if (isWindowsOS && WINDOWS_FORBIDDEN_NAMES.test(name)) {
return false; // check for certain invalid file names
}
@@ -680,11 +708,11 @@ export class RemoteFileDialog {
return false; // check for reserved values
}
if (isWindows && name[name.length - 1] === '.') {
if (isWindowsOS && name[name.length - 1] === '.') {
return false; // Windows: file cannot end with a "."
}
if (isWindows && name.length !== name.trim().length) {
if (isWindowsOS && name.length !== name.trim().length) {
return false; // Windows: file cannot end with a whitespace
}

View File

@@ -244,7 +244,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
}
async createAndEnterWorkspace(folders: IWorkspaceFolderCreationData[], path?: URI): Promise<void> {
if (path && !this.isValidTargetWorkspacePath(path)) {
if (path && !await this.isValidTargetWorkspacePath(path)) {
return Promise.reject(null);
}
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
@@ -258,7 +258,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
}
async saveAndEnterWorkspace(path: URI): Promise<void> {
if (!this.isValidTargetWorkspacePath(path)) {
if (!await this.isValidTargetWorkspacePath(path)) {
return Promise.reject(null);
}
const workspaceIdentifier = this.getCurrentWorkspaceIdentifier();