diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 9c7dc2dc68d..b268681d0c3 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -50,7 +50,7 @@ function _matchesPrefix(ignoreCase: boolean, word: string, wordToMatchAgainst: s let matches: boolean; if (ignoreCase) { - matches = strings.beginsWithIgnoreCase(wordToMatchAgainst, word); + matches = strings.startsWithIgnoreCase(wordToMatchAgainst, word); } else { matches = wordToMatchAgainst.indexOf(word) === 0; } diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index 10e06ed599d..ade3020fcdc 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -5,8 +5,8 @@ 'use strict'; import URI from 'vs/base/common/uri'; -import { nativeSep, normalize, isEqualOrParent, basename as pathsBasename, join } from 'vs/base/common/paths'; -import { endsWith, ltrim, equalsIgnoreCase } from 'vs/base/common/strings'; +import { nativeSep, normalize, basename as pathsBasename, join, sep } from 'vs/base/common/paths'; +import { endsWith, ltrim, equalsIgnoreCase, startsWithIgnoreCase, rtrim, startsWith } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform'; @@ -100,13 +100,22 @@ export function normalizeDriveLetter(path: string): string { return path; } +let normalizedUserHomeCached: { original: string; normalized: string } = Object.create(null); export function tildify(path: string, userHome: string): string { - if (isWindows || !path) { - return path; // unsupported on Windows + if (isWindows || !path || !userHome) { + return path; // unsupported } - if (isEqualOrParent(path, userHome, !isLinux /* ignorecase */)) { - path = `~${path.substr(userHome.length)}`; + // Keep a normalized user home path as cache to prevent accumulated string creation + let normalizedUserHome = normalizedUserHomeCached.original === userHome ? normalizedUserHomeCached.normalized : void 0; + if (!normalizedUserHome) { + normalizedUserHome = `${rtrim(userHome, sep)}${sep}`; + normalizedUserHomeCached = { original: userHome, normalized: normalizedUserHome }; + } + + // Linux: case sensitive, macOS: case insensitive + if (isLinux ? startsWith(path, normalizedUserHome) : startsWithIgnoreCase(path, normalizedUserHome)) { + path = `~/${path.substr(normalizedUserHome.length)}`; } return path; diff --git a/src/vs/base/common/paths.ts b/src/vs/base/common/paths.ts index e0e8f7c05ae..70c4b3ae231 100644 --- a/src/vs/base/common/paths.ts +++ b/src/vs/base/common/paths.ts @@ -5,7 +5,7 @@ 'use strict'; import { isWindows } from 'vs/base/common/platform'; -import { beginsWithIgnoreCase, equalsIgnoreCase } from 'vs/base/common/strings'; +import { startsWithIgnoreCase, equalsIgnoreCase } from 'vs/base/common/strings'; import { CharCode } from 'vs/base/common/charCode'; /** @@ -340,7 +340,7 @@ export function isEqualOrParent(path: string, candidate: string, ignoreCase?: bo } if (ignoreCase) { - const beginsWith = beginsWithIgnoreCase(path, candidate); + const beginsWith = startsWithIgnoreCase(path, candidate); if (!beginsWith) { return false; } diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index f1eef60ca00..deeb3ae1f99 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -159,6 +159,10 @@ export function startsWith(haystack: string, needle: string): boolean { return false; } + if (haystack === needle) { + return true; + } + for (let i = 0; i < needle.length; i++) { if (haystack[i] !== needle[i]) { return false; @@ -427,7 +431,7 @@ function doEqualsIgnoreCase(a: string, b: string, stopAt = a.length): boolean { return true; } -export function beginsWithIgnoreCase(str: string, candidate: string): boolean { +export function startsWithIgnoreCase(str: string, candidate: string): boolean { const candidateLength = candidate.length; if (candidate.length > str.length) { return false; diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts index 4db726aae72..295c3adb50a 100644 --- a/src/vs/base/test/common/strings.test.ts +++ b/src/vs/base/test/common/strings.test.ts @@ -21,29 +21,29 @@ suite('Strings', () => { }); test('beginsWithIgnoreCase', function () { - assert(strings.beginsWithIgnoreCase('', '')); - assert(!strings.beginsWithIgnoreCase('', '1')); - assert(strings.beginsWithIgnoreCase('1', '')); + assert(strings.startsWithIgnoreCase('', '')); + assert(!strings.startsWithIgnoreCase('', '1')); + assert(strings.startsWithIgnoreCase('1', '')); - assert(strings.beginsWithIgnoreCase('a', 'a')); - assert(strings.beginsWithIgnoreCase('abc', 'Abc')); - assert(strings.beginsWithIgnoreCase('abc', 'ABC')); - assert(strings.beginsWithIgnoreCase('Höhenmeter', 'HÖhenmeter')); - assert(strings.beginsWithIgnoreCase('ÖL', 'Öl')); + assert(strings.startsWithIgnoreCase('a', 'a')); + assert(strings.startsWithIgnoreCase('abc', 'Abc')); + assert(strings.startsWithIgnoreCase('abc', 'ABC')); + assert(strings.startsWithIgnoreCase('Höhenmeter', 'HÖhenmeter')); + assert(strings.startsWithIgnoreCase('ÖL', 'Öl')); - assert(strings.beginsWithIgnoreCase('alles klar', 'a')); - assert(strings.beginsWithIgnoreCase('alles klar', 'A')); - assert(strings.beginsWithIgnoreCase('alles klar', 'alles k')); - assert(strings.beginsWithIgnoreCase('alles klar', 'alles K')); - assert(strings.beginsWithIgnoreCase('alles klar', 'ALLES K')); - assert(strings.beginsWithIgnoreCase('alles klar', 'alles klar')); - assert(strings.beginsWithIgnoreCase('alles klar', 'ALLES KLAR')); + assert(strings.startsWithIgnoreCase('alles klar', 'a')); + assert(strings.startsWithIgnoreCase('alles klar', 'A')); + assert(strings.startsWithIgnoreCase('alles klar', 'alles k')); + assert(strings.startsWithIgnoreCase('alles klar', 'alles K')); + assert(strings.startsWithIgnoreCase('alles klar', 'ALLES K')); + assert(strings.startsWithIgnoreCase('alles klar', 'alles klar')); + assert(strings.startsWithIgnoreCase('alles klar', 'ALLES KLAR')); - assert(!strings.beginsWithIgnoreCase('alles klar', ' ALLES K')); - assert(!strings.beginsWithIgnoreCase('alles klar', 'ALLES K ')); - assert(!strings.beginsWithIgnoreCase('alles klar', 'öALLES K ')); - assert(!strings.beginsWithIgnoreCase('alles klar', ' ')); - assert(!strings.beginsWithIgnoreCase('alles klar', 'ö')); + assert(!strings.startsWithIgnoreCase('alles klar', ' ALLES K')); + assert(!strings.startsWithIgnoreCase('alles klar', 'ALLES K ')); + assert(!strings.startsWithIgnoreCase('alles klar', 'öALLES K ')); + assert(!strings.startsWithIgnoreCase('alles klar', ' ')); + assert(!strings.startsWithIgnoreCase('alles klar', 'ö')); }); test('compareIgnoreCase', function () { diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index f3f03d75732..c9d7d12260d 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -11,7 +11,7 @@ import * as glob from 'vs/base/common/glob'; import { isLinux } from 'vs/base/common/platform'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; -import { beginsWithIgnoreCase } from 'vs/base/common/strings'; +import { startsWithIgnoreCase } from 'vs/base/common/strings'; import { IProgress } from 'vs/platform/progress/common/progress'; import { IDisposable } from 'vs/base/common/lifecycle'; import { isEqualOrParent, isEqual } from 'vs/base/common/resources'; @@ -348,29 +348,12 @@ export function isParent(path: string, candidate: string, ignoreCase?: boolean): } if (ignoreCase) { - return beginsWithIgnoreCase(path, candidate); + return startsWithIgnoreCase(path, candidate); } return path.indexOf(candidate) === 0; } -export function indexOf(path: string, candidate: string, ignoreCase?: boolean): number { - if (candidate.length > path.length) { - return -1; - } - - if (path === candidate) { - return 0; - } - - if (ignoreCase) { - path = path.toLowerCase(); - candidate = candidate.toLowerCase(); - } - - return path.indexOf(candidate); -} - export interface IBaseStat { /** diff --git a/src/vs/platform/files/test/files.test.ts b/src/vs/platform/files/test/files.test.ts index 305db3647cd..aba5148b53d 100644 --- a/src/vs/platform/files/test/files.test.ts +++ b/src/vs/platform/files/test/files.test.ts @@ -8,7 +8,7 @@ import * as assert from 'assert'; import URI from 'vs/base/common/uri'; import { join, isEqual, isEqualOrParent } from 'vs/base/common/paths'; -import { FileChangeType, FileChangesEvent, isParent, indexOf } from 'vs/platform/files/common/files'; +import { FileChangeType, FileChangesEvent, isParent } from 'vs/platform/files/common/files'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; suite('Files', () => { @@ -187,16 +187,4 @@ suite('Files', () => { assert(!isEqualOrParent('foo/bar/test.ts', 'foo/BAR/test.', true)); } }); - - test('indexOf (ignorecase)', function () { - assert.equal(indexOf('/some/path', '/some/path', true), 0); - assert.equal(indexOf('/some/path/more', '/some/path', true), 0); - - assert.equal(indexOf('c:\\some\\path', 'c:\\some\\path', true), 0); - assert.equal(indexOf('c:\\some\\path\\more', 'c:\\some\\path', true), 0); - - assert.equal(indexOf('/some/path', '/some/other/path', true), -1); - - assert.equal(indexOf('/some/path', '/some/PATH', true), 0); - }); }); \ No newline at end of file diff --git a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts index 899757c4082..fe2905822fd 100644 --- a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts @@ -13,7 +13,7 @@ import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IEditorGroup, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/parts/files/common/files'; import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent, indexOf } from 'vs/platform/files/common/files'; +import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -208,7 +208,7 @@ export class FileEditorTracker implements IWorkbenchContribution { if (oldResource.toString() === resource.toString()) { reopenFileResource = newResource; // file got moved } else { - const index = indexOf(resource.path, oldResource.path, !isLinux /* ignorecase */); + const index = this.getIndexOfPath(resource.path, oldResource.path); reopenFileResource = newResource.with({ path: paths.join(newResource.path, resource.path.substr(index + oldResource.path.length + 1)) }); // parent folder got moved } @@ -229,6 +229,23 @@ export class FileEditorTracker implements IWorkbenchContribution { }); } + private getIndexOfPath(path: string, candidate: string): number { + if (candidate.length > path.length) { + return -1; + } + + if (path === candidate) { + return 0; + } + + if (!isLinux /* ignore case */) { + path = path.toLowerCase(); + candidate = candidate.toLowerCase(); + } + + return path.indexOf(candidate); + } + private getViewStateFor(resource: URI, group: IEditorGroup): IEditorViewState | undefined { const stacks = this.editorGroupService.getStacksModel(); const editors = this.editorService.getVisibleEditors(); diff --git a/src/vs/workbench/parts/files/common/explorerModel.ts b/src/vs/workbench/parts/files/common/explorerModel.ts index b6aa416ef81..cac3eaf31c9 100644 --- a/src/vs/workbench/parts/files/common/explorerModel.ts +++ b/src/vs/workbench/parts/files/common/explorerModel.ts @@ -17,7 +17,7 @@ import { IEditorGroup, toResource, IEditorIdentifier } from 'vs/workbench/common import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { getPathLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; -import { startsWith, beginsWithIgnoreCase, equalsIgnoreCase } from 'vs/base/common/strings'; +import { startsWith, startsWithIgnoreCase, equalsIgnoreCase } from 'vs/base/common/strings'; export class Model { @@ -316,7 +316,7 @@ export class ExplorerItem { public find(resource: URI): ExplorerItem { // Return if path found if (resource && this.resource.scheme === resource.scheme && this.resource.authority === resource.authority && - (isLinux ? startsWith(resource.path, this.resource.path) : beginsWithIgnoreCase(resource.path, this.resource.path)) + (isLinux ? startsWith(resource.path, this.resource.path) : startsWithIgnoreCase(resource.path, this.resource.path)) ) { return this.findByPath(resource.path, this.resource.path.length); }