提交 da9058d5 编写于 作者: C Christof Marti

Open parent folder with `.vscode` when opening files from CLI or desktop (fixes #20671)

上级 c153d54d
......@@ -26,6 +26,7 @@ import { ILogService } from 'vs/code/electron-main/log';
import { getPathLabel } from 'vs/base/common/labels';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWindowSettings } from 'vs/platform/windows/common/windows';
import { getLastActiveWindow, OpenContext as OriginalOpenContext, findBestWindowOrFolder } from 'vs/code/node/windowsUtils';
import CommonEvent, { Emitter } from 'vs/base/common/event';
import product from 'vs/platform/node/product';
......@@ -34,26 +35,8 @@ enum WindowError {
CRASHED
}
export enum OpenContext {
// opening when running from the command line
CLI,
// macOS only: opening from the dock (also when opening files to a running instance from desktop)
DOCK,
// opening from the main application window
MENU,
// opening from a file or folder dialog
DIALOG,
// opening from the OS's UI
DESKTOP,
// opening through the API
API
}
export const OpenContext = OriginalOpenContext;
export type OpenContext = OriginalOpenContext;
export interface IOpenConfiguration {
context: OpenContext;
......@@ -408,20 +391,28 @@ export class WindowsManager implements IWindowsMainService {
}
// Open Files in last instance if any and flag tells us so
let bestWindow;
if (!openFilesInNewWindow && (bestWindow = this.findBestWindow(openConfig.context, [...filesToOpen, ...filesToCreate, ...filesToDiff]))) {
bestWindow.focus();
const fileToCheck = filesToOpen[0] || filesToCreate[0] || filesToDiff[0];
const windowOrFolder = findBestWindowOrFolder({
windows: WindowsManager.WINDOWS,
newWindow: openFilesInNewWindow,
reuseWindow: openConfig.forceReuseWindow,
context: openConfig.context,
filePath: fileToCheck && fileToCheck.filePath,
userHome: this.environmentService.userHome
});
if (windowOrFolder instanceof VSCodeWindow) {
windowOrFolder.focus();
const files = { filesToOpen, filesToCreate, filesToDiff }; // copy to object because they get reset shortly after
bestWindow.ready().then(readyWindow => {
windowOrFolder.ready().then(readyWindow => {
readyWindow.send('vscode:openFiles', files);
});
usedWindows.push(bestWindow);
usedWindows.push(windowOrFolder);
}
// Otherwise open instance with files
else {
const configuration = this.toConfiguration(openConfig, null, filesToOpen, filesToCreate, filesToDiff);
const configuration = this.toConfiguration(openConfig, windowOrFolder, filesToOpen, filesToCreate, filesToDiff);
const browserWindow = this.openInBrowserWindow(configuration, true /* new window */);
usedWindows.push(browserWindow);
......@@ -1025,31 +1016,8 @@ export class WindowsManager implements IWindowsMainService {
return res && res[0];
}
private findBestWindow(context: OpenContext, filePaths: IPath[]): VSCodeWindow {
const findContainer = context === OpenContext.DESKTOP || context === OpenContext.CLI;
return (findContainer && this.findContainingWindow(filePaths)) || this.getLastActiveWindow();
}
private findContainingWindow(filePaths: IPath[]): VSCodeWindow {
for (const filePath of filePaths) {
const windows = WindowsManager.WINDOWS.filter(window => typeof window.openedWorkspacePath === 'string' && paths.isEqualOrParent(filePath.filePath, window.openedWorkspacePath));
if (windows.length) {
return windows.sort((a, b) => -(a.openedWorkspacePath.length - b.openedWorkspacePath.length))[0];
}
}
return null;
}
public getLastActiveWindow(): VSCodeWindow {
if (WindowsManager.WINDOWS.length) {
const lastFocussedDate = Math.max.apply(Math, WindowsManager.WINDOWS.map(w => w.lastFocusTime));
const res = WindowsManager.WINDOWS.filter(w => w.lastFocusTime === lastFocussedDate);
if (res && res.length) {
return res[0];
}
}
return null;
return getLastActiveWindow(WindowsManager.WINDOWS);
}
public findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow {
......
/*---------------------------------------------------------------------------------------------
* 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 'original-fs';
import * as platform from 'vs/base/common/platform';
import * as paths from 'vs/base/common/paths';
export enum OpenContext {
// opening when running from the command line
CLI,
// macOS only: opening from the dock (also when opening files to a running instance from desktop)
DOCK,
// opening from the main application window
MENU,
// opening from a file or folder dialog
DIALOG,
// opening from the OS's UI
DESKTOP,
// opening through the API
API
}
/**
* Exported subset of VSCodeWindow for testing.
*/
export interface ISimpleWindow {
openedWorkspacePath: string;
lastFocusTime: number;
}
/**
* Exported for testing.
*/
export interface IBestWindowOrFolderOptions<WINDOW> {
windows: WINDOW[];
newWindow: boolean;
reuseWindow: boolean;
context: OpenContext;
filePath?: string;
userHome?: string;
vscodeFolder?: string;
}
export function findBestWindowOrFolder<WINDOW extends ISimpleWindow>({ windows, newWindow, reuseWindow, context, filePath, userHome, vscodeFolder }: IBestWindowOrFolderOptions<WINDOW>): WINDOW | 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<WINDOW extends ISimpleWindow>(windows: WINDOW[], filePath: string): WINDOW {
const containers = windows.filter(window => typeof window.openedWorkspacePath === 'string' && paths.isEqualOrParent(filePath, window.openedWorkspacePath));
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) {
// ~/.vscode/extensions is used for extensions
return fs.statSync(path.join(folder, vscodeFolder, 'settings.json')).isFile();
} else {
return fs.statSync(path.join(folder, vscodeFolder)).isDirectory();
}
} catch (err) {
if (!(err && err.code === 'ENOENT')) {
throw err;
}
}
return false;
}
export function getLastActiveWindow<WINDOW extends ISimpleWindow>(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;
}
/*---------------------------------------------------------------------------------------------
* 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 assert = require('assert');
import path = require('path');
import { findBestWindowOrFolder, OpenContext, ISimpleWindow, IBestWindowOrFolderOptions } from 'vs/code/node/windowsUtils';
const fixturesFolder = require.toUrl('./fixtures');
function options(custom?: Partial<IBestWindowOrFolderOptions<ISimpleWindow>>): IBestWindowOrFolderOptions<ISimpleWindow> {
return {
windows: [],
newWindow: false,
reuseWindow: false,
context: OpenContext.CLI,
vscodeFolder: '_vscode',
...custom
};
}
const vscodeFolderWindow = { lastFocusTime: 1, openedWorkspacePath: path.join(fixturesFolder, 'vscode_folder') };
const lastActiveWindow = { lastFocusTime: 3, openedWorkspacePath: null };
const noVscodeFolderWindow = { lastFocusTime: 2, openedWorkspacePath: path.join(fixturesFolder, 'no_vscode_folder') };
const windows = [
vscodeFolderWindow,
lastActiveWindow,
noVscodeFolderWindow,
];
suite('WindowsUtils', () => {
test('New window without folder when no windows exist', () => {
assert.equal(findBestWindowOrFolder(options()), null);
assert.equal(findBestWindowOrFolder(options({
filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')
})), null);
assert.equal(findBestWindowOrFolder(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({
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({
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({
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt')
})), path.join(fixturesFolder, 'vscode_folder'));
assert.equal(findBestWindowOrFolder(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({
windows,
filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'),
newWindow: true
})), null);
});
test('Last active window', () => {
assert.equal(findBestWindowOrFolder(options({
windows
})), lastActiveWindow);
assert.equal(findBestWindowOrFolder(options({
windows,
filePath: path.join(fixturesFolder, 'no_vscode_folder2', 'file.txt')
})), lastActiveWindow);
assert.equal(findBestWindowOrFolder(options({
windows: [lastActiveWindow, noVscodeFolderWindow],
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
reuseWindow: true
})), lastActiveWindow);
assert.equal(findBestWindowOrFolder(options({
windows,
filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'),
context: OpenContext.API
})), lastActiveWindow);
});
test('Existing window with folder', () => {
assert.equal(findBestWindowOrFolder(options({
windows,
filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')
})), noVscodeFolderWindow);
assert.equal(findBestWindowOrFolder(options({
windows,
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt')
})), vscodeFolderWindow);
});
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({
windows: [window],
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')
})), window);
// check
assert.equal(findBestWindowOrFolder(options({
windows: [window],
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder2', 'subfolder', 'file.txt')
})), path.join(fixturesFolder, 'vscode_folder'));
});
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({
windows: [window, nestedFolderWindow],
filePath: path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt')
})), nestedFolderWindow);
});
test('VSCode folder wins over existing window if more specific', () => {
const window = { lastFocusTime: 1, openedWorkspacePath: path.join(fixturesFolder, 'vscode_folder') };
assert.equal(findBestWindowOrFolder(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({
windows: [window],
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')
})), window);
});
test('More specific VSCode folder wins', () => {
assert.equal(findBestWindowOrFolder(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({
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
userHome: path.join(fixturesFolder, 'vscode_folder')
})), null);
assert.equal(findBestWindowOrFolder(options({
filePath: path.join(fixturesFolder, 'vscode_home_folder', 'file.txt'),
userHome: path.join(fixturesFolder, 'vscode_home_folder')
})), path.join(fixturesFolder, 'vscode_home_folder'));
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册