From dd06f3ca2171fc3ab47f084ae899ba2b825a8d32 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 10 Jul 2017 09:46:47 +0200 Subject: [PATCH] :lipstick: --- src/vs/code/electron-main/windows.ts | 17 ++-- src/vs/code/node/windowsFinder.ts | 110 +++++++++++++++++++++ src/vs/code/node/windowsUtils.ts | 107 -------------------- src/vs/code/test/node/windowsUtils.test.ts | 50 +++++----- 4 files changed, 144 insertions(+), 140 deletions(-) create mode 100644 src/vs/code/node/windowsFinder.ts delete mode 100644 src/vs/code/node/windowsUtils.ts diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 877a5f61e32..1e17015161e 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -21,7 +21,7 @@ import { ILifecycleService, UnloadReason } from 'vs/platform/lifecycle/electron- import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowSettings, OpenContext, IPath, IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { getLastActiveWindow, findBestWindowOrFolder } from 'vs/code/node/windowsUtils'; +import { getLastActiveWindow, findBestWindowOrFolderForFile } from 'vs/code/node/windowsFinder'; import CommonEvent, { Emitter } from 'vs/base/common/event'; import product from 'vs/platform/node/product'; import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; @@ -345,7 +345,7 @@ export class WindowsManager implements IWindowsMainService { // Open Files in last instance if any and flag tells us so const fileToCheck = filesToOpen[0] || filesToCreate[0] || filesToDiff[0]; - const windowOrFolder = findBestWindowOrFolder({ + const bestWindowOrFolder = findBestWindowOrFolderForFile({ windows: WindowsManager.WINDOWS, newWindow: openFilesInNewWindow, reuseWindow: openConfig.forceReuseWindow, @@ -354,23 +354,24 @@ export class WindowsManager implements IWindowsMainService { userHome: this.environmentService.userHome }); - if (windowOrFolder instanceof CodeWindow) { - windowOrFolder.focus(); + // We found a suitable window to open the files within + if (bestWindowOrFolder instanceof CodeWindow) { + bestWindowOrFolder.focus(); const files = { filesToOpen, filesToCreate, filesToDiff }; // copy to object because they get reset shortly after - windowOrFolder.ready().then(readyWindow => { + bestWindowOrFolder.ready().then(readyWindow => { readyWindow.send('vscode:openFiles', files); }); - usedWindows.push(windowOrFolder); + usedWindows.push(bestWindowOrFolder); } - // Otherwise open instance with files + // Otherwise open a new window with the best folder to use for the file else { const browserWindow = this.openInBrowserWindow({ userEnv: openConfig.userEnv, cli: openConfig.cli, initialStartup: openConfig.initialStartup, - workspacePath: windowOrFolder, + workspacePath: bestWindowOrFolder, filesToOpen, filesToCreate, filesToDiff, diff --git a/src/vs/code/node/windowsFinder.ts b/src/vs/code/node/windowsFinder.ts new file mode 100644 index 00000000000..5ccc728fb59 --- /dev/null +++ b/src/vs/code/node/windowsFinder.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as path from 'path'; +import * as fs from 'fs'; +import * as platform from 'vs/base/common/platform'; +import * as paths from 'vs/base/common/paths'; +import { OpenContext } from 'vs/platform/windows/common/windows'; + +/** + * Exported subset of CodeWindow for testing. + */ +export interface ISimpleWindow { + openedWorkspacePath: string; + lastFocusTime: number; +} + +/** + * Exported for testing. + */ +export interface IBestWindowOrFolderOptions { + windows: W[]; + newWindow: boolean; + reuseWindow: boolean; + context: OpenContext; + filePath?: string; + userHome?: string; + codeSettingsFolder?: string; +} + +export function findBestWindowOrFolderForFile({ windows, newWindow, reuseWindow, context, filePath, userHome, codeSettingsFolder }: IBestWindowOrFolderOptions): W | string { + if (!newWindow && filePath && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK)) { + const windowOnFilePath = findWindowOnFilePath(windows, filePath); + const folderWithCodeSettings = !reuseWindow && findFolderWithCodeSettings(filePath, userHome, codeSettingsFolder); + + // Return if we found a window that has the parent of the file path opened + if (windowOnFilePath && !(folderWithCodeSettings && folderWithCodeSettings.length > windowOnFilePath.openedWorkspacePath.length)) { + return windowOnFilePath; + } + + // Return if we found a parent folder with a code settings folder inside + if (folderWithCodeSettings) { + return folderWithCodeSettings; + } + } + + return !newWindow ? getLastActiveWindow(windows) : null; +} + +function findWindowOnFilePath(windows: W[], filePath: string): W { + + // From all windows that have the parent of the file opened, return the window + // that has the most specific folder opened ( = longest path wins) + const windowsOnFilePath = windows.filter(window => typeof window.openedWorkspacePath === 'string' && paths.isEqualOrParent(filePath, window.openedWorkspacePath, !platform.isLinux /* ignorecase */)); + if (windowsOnFilePath.length) { + return windowsOnFilePath.sort((a, b) => -(a.openedWorkspacePath.length - b.openedWorkspacePath.length))[0]; + } + + return null; +} + +function findFolderWithCodeSettings(filePath: string, userHome?: string, codeSettingsFolder?: string): string { + let folder = path.dirname(paths.normalize(filePath, true)); + let homeFolder = userHome && paths.normalize(userHome, true); + if (!platform.isLinux) { + homeFolder = homeFolder && homeFolder.toLowerCase(); + } + + let previous = null; + while (folder !== previous) { + if (hasCodeSettings(folder, homeFolder, codeSettingsFolder)) { + return folder; + } + + previous = folder; + folder = path.dirname(folder); + } + + return null; +} + +function hasCodeSettings(folder: string, normalizedUserHome?: string, codeSettingsFolder = '.vscode') { + try { + if ((platform.isLinux ? folder : folder.toLowerCase()) === normalizedUserHome) { + return fs.statSync(path.join(folder, codeSettingsFolder, 'settings.json')).isFile(); // ~/.vscode/extensions is used for extensions + } + + return fs.statSync(path.join(folder, codeSettingsFolder)).isDirectory(); + } catch (err) { + // assume impossible to access + } + + return false; +} + +export function getLastActiveWindow(windows: W[]): W { + if (windows.length) { + const lastFocussedDate = Math.max.apply(Math, windows.map(w => w.lastFocusTime)); + const res = windows.filter(w => w.lastFocusTime === lastFocussedDate); + if (res && res.length) { + return res[0]; + } + } + + return null; +} diff --git a/src/vs/code/node/windowsUtils.ts b/src/vs/code/node/windowsUtils.ts deleted file mode 100644 index 0727611a543..00000000000 --- a/src/vs/code/node/windowsUtils.ts +++ /dev/null @@ -1,107 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import * as path from 'path'; -import * as fs from 'fs'; -import * as platform from 'vs/base/common/platform'; -import * as paths from 'vs/base/common/paths'; -import { OpenContext } from 'vs/platform/windows/common/windows'; - -/** - * Exported subset of CodeWindow for testing. - */ -export interface ISimpleWindow { - openedWorkspacePath: string; - lastFocusTime: number; -} - -/** - * Exported for testing. - */ -export interface IBestWindowOrFolderOptions { - windows: SimpleWindow[]; - newWindow: boolean; - reuseWindow: boolean; - context: OpenContext; - filePath?: string; - userHome?: string; - vscodeFolder?: string; -} - -export function findBestWindowOrFolder({ windows, newWindow, reuseWindow, context, filePath, userHome, vscodeFolder }: IBestWindowOrFolderOptions): SimpleWindow | string { - // OpenContext.DOCK implies newWindow unless overwritten by settings. - const findBest = filePath && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK); - const bestWindow = !newWindow && findBest && findBestWindow(windows, filePath); - const bestFolder = !newWindow && !reuseWindow && findBest && findBestFolder(filePath, userHome, vscodeFolder); - if (bestWindow && !(bestFolder && bestFolder.length > bestWindow.openedWorkspacePath.length)) { - return bestWindow; - } else if (bestFolder) { - return bestFolder; - } - - return !newWindow ? getLastActiveWindow(windows) : null; -} - -function findBestWindow(windows: WINDOW[], filePath: string): WINDOW { - const containers = windows.filter(window => typeof window.openedWorkspacePath === 'string' && paths.isEqualOrParent(filePath, window.openedWorkspacePath, !platform.isLinux /* ignorecase */)); - if (containers.length) { - return containers.sort((a, b) => -(a.openedWorkspacePath.length - b.openedWorkspacePath.length))[0]; - } - - return null; -} - -function findBestFolder(filePath: string, userHome?: string, vscodeFolder?: string): string { - let folder = path.dirname(paths.normalize(filePath, true)); - let homeFolder = userHome && paths.normalize(userHome, true); - if (!platform.isLinux) { - homeFolder = homeFolder && homeFolder.toLowerCase(); - } - - let previous = null; - try { - while (folder !== previous) { - if (isProjectFolder(folder, homeFolder, vscodeFolder)) { - return folder; - } - previous = folder; - folder = path.dirname(folder); - } - } catch (err) { - // assume impossible to access - } - - return null; -} - -function isProjectFolder(folder: string, normalizedUserHome?: string, vscodeFolder = '.vscode') { - try { - if ((platform.isLinux ? folder : folder.toLowerCase()) === normalizedUserHome) { - return fs.statSync(path.join(folder, vscodeFolder, 'settings.json')).isFile(); // ~/.vscode/extensions is used for extensions - } - - return fs.statSync(path.join(folder, vscodeFolder)).isDirectory(); - } catch (err) { - if (!(err && err.code === 'ENOENT')) { - throw err; - } - } - - return false; -} - -export function getLastActiveWindow(windows: WINDOW[]): WINDOW { - if (windows.length) { - const lastFocussedDate = Math.max.apply(Math, windows.map(w => w.lastFocusTime)); - const res = windows.filter(w => w.lastFocusTime === lastFocussedDate); - if (res && res.length) { - return res[0]; - } - } - - return null; -} diff --git a/src/vs/code/test/node/windowsUtils.test.ts b/src/vs/code/test/node/windowsUtils.test.ts index 026ce3040d1..8e42ddd35b7 100644 --- a/src/vs/code/test/node/windowsUtils.test.ts +++ b/src/vs/code/test/node/windowsUtils.test.ts @@ -6,7 +6,7 @@ import assert = require('assert'); import path = require('path'); -import { findBestWindowOrFolder, ISimpleWindow, IBestWindowOrFolderOptions } from 'vs/code/node/windowsUtils'; +import { findBestWindowOrFolderForFile, ISimpleWindow, IBestWindowOrFolderOptions } from 'vs/code/node/windowsFinder'; import { OpenContext } from 'vs/platform/windows/common/windows'; const fixturesFolder = require.toUrl('./fixtures'); @@ -17,7 +17,7 @@ function options(custom?: Partial>): I newWindow: false, reuseWindow: false, context: OpenContext.CLI, - vscodeFolder: '_vscode', + codeSettingsFolder: '_vscode', ...custom }; } @@ -31,38 +31,38 @@ const windows = [ noVscodeFolderWindow, ]; -suite('WindowsUtils', () => { +suite('WindowsFinder', () => { test('New window without folder when no windows exist', () => { - assert.equal(findBestWindowOrFolder(options()), null); - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options()), null); + assert.equal(findBestWindowOrFolderForFile(options({ filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt') })), null); - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'), newWindow: true // We assume this implies 'editor' work mode, might need separate CLI option later. })), null); - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'), reuseWindow: true // We assume this implies 'editor' work mode, might need separate CLI option later. })), null); - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'), context: OpenContext.API })), null); }); test('New window with folder when no windows exist', () => { - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt') })), path.join(fixturesFolder, 'vscode_folder')); - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ filePath: path.join(fixturesFolder, 'vscode_folder', 'new_folder', 'new_file.txt') })), path.join(fixturesFolder, 'vscode_folder')); }); test('New window without folder when windows exist', () => { - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ windows, filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'), newWindow: true @@ -70,19 +70,19 @@ suite('WindowsUtils', () => { }); test('Last active window', () => { - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ windows })), lastActiveWindow); - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ windows, filePath: path.join(fixturesFolder, 'no_vscode_folder2', 'file.txt') })), lastActiveWindow); - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ windows: [lastActiveWindow, noVscodeFolderWindow], filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'), reuseWindow: true })), lastActiveWindow); - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ windows, filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'), context: OpenContext.API @@ -90,11 +90,11 @@ suite('WindowsUtils', () => { }); test('Existing window with folder', () => { - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ windows, filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt') })), noVscodeFolderWindow); - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ windows, filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt') })), vscodeFolderWindow); @@ -102,12 +102,12 @@ suite('WindowsUtils', () => { test('Existing window wins over vscode folder if more specific', () => { const window = { lastFocusTime: 1, openedWorkspacePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder') }; - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ windows: [window], filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt') })), window); // check - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ windows: [window], filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder2', 'subfolder', 'file.txt') })), path.join(fixturesFolder, 'vscode_folder')); @@ -116,7 +116,7 @@ suite('WindowsUtils', () => { test('More specific existing window wins', () => { const window = { lastFocusTime: 2, openedWorkspacePath: path.join(fixturesFolder, 'no_vscode_folder') }; const nestedFolderWindow = { lastFocusTime: 1, openedWorkspacePath: path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder') }; - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ windows: [window, nestedFolderWindow], filePath: path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt') })), nestedFolderWindow); @@ -124,30 +124,30 @@ suite('WindowsUtils', () => { test('VSCode folder wins over existing window if more specific', () => { const window = { lastFocusTime: 1, openedWorkspacePath: path.join(fixturesFolder, 'vscode_folder') }; - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ windows: [window], filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder', 'subfolder', 'file.txt') })), path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder')); // check - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ windows: [window], filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt') })), window); }); test('More specific VSCode folder wins', () => { - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder', 'subfolder', 'file.txt') })), path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder')); }); test('VSCode folder in home folder needs settings.json', () => { // Because ~/.vscode/extensions is used for extensions, ~/.vscode is not enough as a hint. - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'), userHome: path.join(fixturesFolder, 'vscode_folder') })), null); - assert.equal(findBestWindowOrFolder(options({ + assert.equal(findBestWindowOrFolderForFile(options({ filePath: path.join(fixturesFolder, 'vscode_home_folder', 'file.txt'), userHome: path.join(fixturesFolder, 'vscode_home_folder') })), path.join(fixturesFolder, 'vscode_home_folder')); -- GitLab