From c40a2fd096973b87e4738c2c71145fecfda73d99 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 5 Jun 2018 15:10:44 -0700 Subject: [PATCH] Use case-insensitive synced buffer map on case-insensitive file systems Fixes #51183 Fixes #50505 --- .../src/features/bufferSyncSupport.ts | 61 ++++++++++++++++++- .../src/typescriptServiceClient.ts | 6 +- .../src/utils/electron.ts | 22 ++----- .../src/utils/temp.ts | 21 +++++++ 4 files changed, 87 insertions(+), 23 deletions(-) create mode 100644 extensions/typescript-language-features/src/utils/temp.ts diff --git a/extensions/typescript-language-features/src/features/bufferSyncSupport.ts b/extensions/typescript-language-features/src/features/bufferSyncSupport.ts index 0e704f60a59..9dbdee511f2 100644 --- a/extensions/typescript-language-features/src/features/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/features/bufferSyncSupport.ts @@ -11,6 +11,8 @@ import { Delayer } from '../utils/async'; import { disposeAll } from '../utils/dispose'; import * as languageModeIds from '../utils/languageModeIds'; import API from '../utils/api'; +import { memoize } from '../utils/memoize'; +import { getTempFile } from '../utils/temp'; enum BufferKind { TypeScript = 1, @@ -69,6 +71,10 @@ class SyncedBuffer { this.client.execute('open', args, false); } + public get resource(): Uri { + return this.document.uri; + } + public get lineCount(): number { return this.document.lineCount; } @@ -126,6 +132,10 @@ class SyncedBufferMap { return file ? this._map.get(file) : undefined; } + public getForPath(filePath: string): SyncedBuffer | undefined { + return this.get(Uri.file(filePath)); + } + public set(resource: Uri, buffer: SyncedBuffer) { const file = this.toKey(resource); if (file) { @@ -149,7 +159,8 @@ class SyncedBufferMap { } private toKey(resource: Uri): string | null { - return this._normalizePath(resource); + const key = this._normalizePath(resource); + return key ? key.toLowerCase() : key; } } @@ -178,7 +189,7 @@ export default class BufferSyncSupport { this.diagnosticDelayer = new Delayer(300); - this.syncedBuffers = new SyncedBufferMap(path => this.client.normalizedPath(path)); + this.syncedBuffers = new SyncedBufferMap(path => this.normalizePath(path)); this.updateConfiguration(); workspace.onDidChangeConfiguration(() => this.updateConfiguration(), null); @@ -202,6 +213,14 @@ export default class BufferSyncSupport { return this.syncedBuffers.has(resource); } + public toResource(filePath: string): Uri { + const buffer = this.syncedBuffers.getForPath(filePath); + if (buffer) { + return buffer.resource; + } + return Uri.file(filePath); + } + public reOpenDocuments(): void { for (const buffer of this.syncedBuffers.allBuffers) { buffer.open(); @@ -376,4 +395,40 @@ export default class BufferSyncSupport { return this._validateTypeScript; } } -} \ No newline at end of file + + private normalizePath(path: Uri): string | null { + const key = this.client.normalizedPath(path); + if (!key) { + return key; + } + + return this.isCaseInsensitivePath(key) ? key.toLowerCase() : key; + } + + private isCaseInsensitivePath(path: string) { + if (isWindowsPath(path)) { + return true; + } + + return path[0] === '/' && this.onIsCaseInsenitiveFileSystem; + } + + @memoize + private get onIsCaseInsenitiveFileSystem() { + if (process.platform === 'win32') { + return true; + } + + if (process.platform !== 'darwin') { + return false; + } + + const temp = getTempFile('typescript-case-check'); + fs.writeFileSync(temp, ''); + return fs.existsSync(temp.toUpperCase()); + } +} + +function isWindowsPath(path: string): boolean { + return /^[a-zA-Z]:\\/.test(path); +} diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index ebe7d35da73..e152f0f6ca9 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -663,9 +663,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient return resource; } } - const resource = Uri.file(filepath); - - return resource; + return this.bufferSyncSupport.toResource(filepath); } public getWorkspaceRootForResource(resource: Uri): string | undefined { @@ -976,7 +974,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient } if (this.apiVersion.gte(API.v222)) { - this.cancellationPipeName = electron.getTempFile(`tscancellation-${electron.makeRandomHexString(20)}`); + this.cancellationPipeName = electron.getTempSock('tscancellation'); args.push('--cancellationPipeName', this.cancellationPipeName + '*'); } diff --git a/extensions/typescript-language-features/src/utils/electron.ts b/extensions/typescript-language-features/src/utils/electron.ts index 527046465aa..34a6534f8ec 100644 --- a/extensions/typescript-language-features/src/utils/electron.ts +++ b/extensions/typescript-language-features/src/utils/electron.ts @@ -3,25 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import Logger from './logger'; +import { getTempFile, makeRandomHexString } from './temp'; import path = require('path'); import os = require('os'); import net = require('net'); import cp = require('child_process'); -import Logger from './logger'; export interface IForkOptions { cwd?: string; execArgv?: string[]; } -export function makeRandomHexString(length: number): string { - let chars = ['0', '1', '2', '3', '4', '5', '6', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; - let result = ''; - for (let i = 0; i < length; i++) { - const idx = Math.floor(chars.length * Math.random()); - result += chars[idx]; - } - return result; +export function getTempSock(prefix: string): string { + const fullName = `vscode-${prefix}-${makeRandomHexString(20)}`; + return getTempFile(fullName + '.sock'); } function generatePipeName(): string { @@ -38,12 +34,6 @@ function getPipeName(name: string): string { return path.join(os.tmpdir(), fullName + '.sock'); } -export function getTempFile(name: string): string { - const fullName = 'vscode-' + name; - return path.join(os.tmpdir(), fullName + '.sock'); -} - - function generatePatchedEnv( env: any, stdInPipeName: string, @@ -131,7 +121,7 @@ export function fork( }; // Create the process - logger.info('Forking TSServer', `PATH: ${newEnv['PATH']}`); + logger.info('Forking TSServer', `PATH: ${newEnv['PATH']} `); const bootstrapperPath = require.resolve('./electronForkStart'); childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), { diff --git a/extensions/typescript-language-features/src/utils/temp.ts b/extensions/typescript-language-features/src/utils/temp.ts new file mode 100644 index 00000000000..79c5cf751d0 --- /dev/null +++ b/extensions/typescript-language-features/src/utils/temp.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import path = require('path'); +import os = require('os'); + +export function makeRandomHexString(length: number): string { + let chars = ['0', '1', '2', '3', '4', '5', '6', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + let result = ''; + for (let i = 0; i < length; i++) { + const idx = Math.floor(chars.length * Math.random()); + result += chars[idx]; + } + return result; +} + +export function getTempFile(name: string): string { + return path.join(os.tmpdir(), name); +} \ No newline at end of file