Skip to content

Commit

Permalink
Case-sensitivity and file system providers
Browse files Browse the repository at this point in the history
fixes #48258
  • Loading branch information
isidorn committed Dec 17, 2019
1 parent 7a374c3 commit 7d5cd0d
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { URI } from 'vs/base/common/uri';
import * as resources from 'vs/base/common/resources';
import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor';
import { ITextFileService, TextFileModelChangeEvent, ModelState } from 'vs/workbench/services/textfile/common/textfiles';
Expand All @@ -25,6 +24,8 @@ import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor
import { timeout } from 'vs/base/common/async';
import { withNullAsUndefined } from 'vs/base/common/types';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { isEqualOrParent, joinPath } from 'vs/base/common/resources';
import { IExplorerService } from 'vs/workbench/contrib/files/common/files';

export class FileEditorTracker extends Disposable implements IWorkbenchContribution {

Expand All @@ -42,7 +43,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IHostService private readonly hostService: IHostService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
@IExplorerService private readonly explorerService: IExplorerService
) {
super();

Expand Down Expand Up @@ -101,13 +103,13 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut

// Update Editor if file (or any parent of the input) got renamed or moved
const resource = editor.getResource();
if (resources.isEqualOrParent(resource, oldResource)) {
if (isEqualOrParent(resource, oldResource)) {
let reopenFileResource: URI;
if (oldResource.toString() === resource.toString()) {
reopenFileResource = newResource; // file got moved
} else {
const index = this.getIndexOfPath(resource.path, oldResource.path, resources.hasToIgnoreCase(resource));
reopenFileResource = resources.joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved
const index = this.getIndexOfPath(resource.path, oldResource.path, this.explorerService.shouldIgnoreCase(resource));
reopenFileResource = joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved
}

let encoding: string | undefined = undefined;
Expand Down Expand Up @@ -195,15 +197,15 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
// Do NOT close any opened editor that matches the resource path (either equal or being parent) of the
// resource we move to (movedTo). Otherwise we would close a resource that has been renamed to the same
// path but different casing.
if (movedTo && resources.isEqualOrParent(resource, movedTo)) {
if (movedTo && isEqualOrParent(resource, movedTo)) {
return;
}

let matches = false;
if (arg1 instanceof FileChangesEvent) {
matches = arg1.contains(resource, FileChangeType.DELETED);
} else {
matches = resources.isEqualOrParent(resource, arg1);
matches = isEqualOrParent(resource, arg1);
}

if (!matches) {
Expand Down
8 changes: 4 additions & 4 deletions src/vs/workbench/contrib/files/browser/fileActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,12 +328,12 @@ function containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean
}


export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean, allowOverwrite: boolean }, incrementalNaming: 'simple' | 'smart'): URI {
export function findValidPasteFileTarget(explorerService: IExplorerService, targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean, allowOverwrite: boolean }, incrementalNaming: 'simple' | 'smart'): URI {
let name = resources.basenameOrAuthority(fileToPaste.resource);

let candidate = resources.joinPath(targetFolder.resource, name);
while (true && !fileToPaste.allowOverwrite) {
if (!targetFolder.root.find(candidate)) {
if (!explorerService.findClosest(candidate)) {
break;
}

Expand Down Expand Up @@ -870,7 +870,7 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
throw new Error('Parent folder is readonly.');
}

const newStat = new NewExplorerItem(folder, isFolder);
const newStat = new NewExplorerItem(explorerService, folder, isFolder);
await folder.fetchChildren(fileService, explorerService);

folder.addChild(newStat);
Expand Down Expand Up @@ -1049,7 +1049,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => {
}

const incrementalNaming = configurationService.getValue<IFilesConfiguration>().explorer.incrementalNaming;
const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming);
const targetFile = findValidPasteFileTarget(explorerService, target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming);

// Move/Copy File
if (pasteShouldMove) {
Expand Down
10 changes: 5 additions & 5 deletions src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IFilesConfiguration, IExplorerService } from 'vs/workbench/contrib/files/common/files';
import { dirname, joinPath, isEqualOrParent, basename, hasToIgnoreCase, distinctParents } from 'vs/base/common/resources';
import { dirname, joinPath, isEqualOrParent, basename, distinctParents } from 'vs/base/common/resources';
import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { localize } from 'vs/nls';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
Expand Down Expand Up @@ -95,7 +95,7 @@ export class ExplorerDataSource implements IAsyncDataSource<ExplorerItem | Explo
if (element instanceof ExplorerItem && element.isRoot) {
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
// Single folder create a dummy explorer item to show error
const placeholder = new ExplorerItem(element.resource, undefined, false);
const placeholder = new ExplorerItem(element.resource, this.explorerService, undefined, false);
placeholder.isError = true;
return [placeholder];
} else {
Expand Down Expand Up @@ -920,7 +920,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
// Check for name collisions
const targetNames = new Set<string>();
if (targetStat.children) {
const ignoreCase = hasToIgnoreCase(target.resource);
const ignoreCase = this.explorerService.shouldIgnoreCase(target.resource);
targetStat.children.forEach(child => {
targetNames.add(ignoreCase ? child.name.toLowerCase() : child.name);
});
Expand All @@ -929,7 +929,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
// Run add in sequence
const addPromisesFactory: ITask<Promise<void>>[] = [];
await Promise.all(resources.map(async resource => {
if (targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase())) {
if (targetNames.has(this.explorerService.shouldIgnoreCase(resource) ? basename(resource).toLowerCase() : basename(resource))) {
const confirmationResult = await this.dialogService.confirm(getFileOverwriteConfirm(basename(resource)));
if (!confirmationResult.confirmed) {
return;
Expand Down Expand Up @@ -1031,7 +1031,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
// Reuse duplicate action if user copies
if (isCopy) {
const incrementalNaming = this.configurationService.getValue<IFilesConfiguration>().explorer.incrementalNaming;
const stat = await this.textFileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming));
const stat = await this.textFileService.copy(source.resource, findValidPasteFileTarget(this.explorerService, target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming));
if (!stat.isDirectory) {
await this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
}
Expand Down
41 changes: 23 additions & 18 deletions src/vs/workbench/contrib/files/common/explorerModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import { URI } from 'vs/base/common/uri';
import { isEqual } from 'vs/base/common/extpath';
import { posix } from 'vs/base/common/path';
import * as resources from 'vs/base/common/resources';
import { ResourceMap } from 'vs/base/common/map';
import { IFileStat, IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { rtrim, startsWithIgnoreCase, startsWith, equalsIgnoreCase } from 'vs/base/common/strings';
Expand All @@ -16,16 +15,20 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { memoize } from 'vs/base/common/decorators';
import { Emitter, Event } from 'vs/base/common/event';
import { IExplorerService } from 'vs/workbench/contrib/files/common/files';
import { joinPath, isEqualOrParent, basenameOrAuthority } from 'vs/base/common/resources';

export class ExplorerModel implements IDisposable {

private _roots!: ExplorerItem[];
private _listener: IDisposable;
private readonly _onDidChangeRoots = new Emitter<void>();

constructor(private readonly contextService: IWorkspaceContextService) {
constructor(
private readonly contextService: IWorkspaceContextService,
explorerService: IExplorerService
) {
const setRoots = () => this._roots = this.contextService.getWorkspace().folders
.map(folder => new ExplorerItem(folder.uri, undefined, true, false, false, folder.name));
.map(folder => new ExplorerItem(folder.uri, explorerService, undefined, true, false, false, folder.name));
setRoots();

this._listener = this.contextService.onDidChangeWorkspaceFolders(() => {
Expand Down Expand Up @@ -80,11 +83,12 @@ export class ExplorerItem {

constructor(
public resource: URI,
private readonly explorerService: IExplorerService,
private _parent: ExplorerItem | undefined,
private _isDirectory?: boolean,
private _isSymbolicLink?: boolean,
private _isReadonly?: boolean,
private _name: string = resources.basenameOrAuthority(resource),
private _name: string = basenameOrAuthority(resource),
private _mtime?: number,
) {
this._isDirectoryResolved = false;
Expand Down Expand Up @@ -154,8 +158,8 @@ export class ExplorerItem {
return this === this.root;
}

static create(service: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem {
const stat = new ExplorerItem(raw.resource, parent, raw.isDirectory, raw.isSymbolicLink, service.hasCapability(raw.resource, FileSystemProviderCapabilities.Readonly), raw.name, raw.mtime);
static create(explorerService: IExplorerService, fileService: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem {
const stat = new ExplorerItem(raw.resource, explorerService, parent, raw.isDirectory, raw.isSymbolicLink, fileService.hasCapability(raw.resource, FileSystemProviderCapabilities.Readonly), raw.name, raw.mtime);

// Recursively add children if present
if (stat.isDirectory) {
Expand All @@ -164,13 +168,13 @@ export class ExplorerItem {
// the folder is fully resolved if either it has a list of children or the client requested this by using the resolveTo
// array of resource path to resolve.
stat._isDirectoryResolved = !!raw.children || (!!resolveTo && resolveTo.some((r) => {
return resources.isEqualOrParent(r, stat.resource);
return isEqualOrParent(r, stat.resource);
}));

// Recurse into children
if (raw.children) {
for (let i = 0, len = raw.children.length; i < len; i++) {
const child = ExplorerItem.create(service, raw.children[i], stat, resolveTo);
const child = ExplorerItem.create(explorerService, fileService, raw.children[i], stat, resolveTo);
stat.addChild(child);
}
}
Expand Down Expand Up @@ -262,7 +266,7 @@ export class ExplorerItem {
const resolveMetadata = explorerService.sortOrder === 'modified';
try {
const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata });
const resolved = ExplorerItem.create(fileService, stat, this);
const resolved = ExplorerItem.create(explorerService, fileService, stat, this);
ExplorerItem.mergeLocalWithDisk(resolved, this);
} catch (e) {
this.isError = true;
Expand Down Expand Up @@ -302,7 +306,7 @@ export class ExplorerItem {
}

private getPlatformAwareName(name: string): string {
return (!name || !resources.hasToIgnoreCase(this.resource)) ? name : name.toLowerCase();
return this.explorerService.shouldIgnoreCase(this.resource) ? name.toLowerCase() : name;
}

/**
Expand All @@ -319,7 +323,7 @@ export class ExplorerItem {

private updateResource(recursive: boolean): void {
if (this._parent) {
this.resource = resources.joinPath(this._parent.resource, this.name);
this.resource = joinPath(this._parent.resource, this.name);
}

if (recursive) {
Expand Down Expand Up @@ -352,16 +356,17 @@ export class ExplorerItem {
find(resource: URI): ExplorerItem | null {
// Return if path found
// For performance reasons try to do the comparison as fast as possible
const ignoreCase = this.explorerService.shouldIgnoreCase(resource);
if (resource && this.resource.scheme === resource.scheme && equalsIgnoreCase(this.resource.authority, resource.authority) &&
(resources.hasToIgnoreCase(resource) ? startsWithIgnoreCase(resource.path, this.resource.path) : startsWith(resource.path, this.resource.path))) {
return this.findByPath(rtrim(resource.path, posix.sep), this.resource.path.length);
(ignoreCase ? startsWithIgnoreCase(resource.path, this.resource.path) : startsWith(resource.path, this.resource.path))) {
return this.findByPath(rtrim(resource.path, posix.sep), this.resource.path.length, ignoreCase);
}

return null; //Unable to find
}

private findByPath(path: string, index: number): ExplorerItem | null {
if (isEqual(rtrim(this.resource.path, posix.sep), path, resources.hasToIgnoreCase(this.resource))) {
private findByPath(path: string, index: number, ignoreCase: boolean): ExplorerItem | null {
if (isEqual(rtrim(this.resource.path, posix.sep), path, ignoreCase)) {
return this;
}

Expand All @@ -383,7 +388,7 @@ export class ExplorerItem {

if (child) {
// We found a child with the given name, search inside it
return child.findByPath(path, indexOfNextSep);
return child.findByPath(path, indexOfNextSep, ignoreCase);
}
}

Expand All @@ -392,7 +397,7 @@ export class ExplorerItem {
}

export class NewExplorerItem extends ExplorerItem {
constructor(parent: ExplorerItem, isDirectory: boolean) {
super(URI.file(''), parent, isDirectory);
constructor(explorerService: IExplorerService, parent: ExplorerItem, isDirectory: boolean) {
super(URI.file(''), explorerService, parent, isDirectory);
}
}
Loading

0 comments on commit 7d5cd0d

Please sign in to comment.