diff --git a/src/vs/base/browser/dnd.ts b/src/vs/base/browser/dnd.ts index a0d3778527a970c9a11cc7901a06e2d834cf0864..462b711b4b79e4982c0c6286b165e8d7f81cabbd 100644 --- a/src/vs/base/browser/dnd.ts +++ b/src/vs/base/browser/dnd.ts @@ -42,8 +42,13 @@ export class DelayedDragHandler { } } -export function extractResources(e: DragEvent, externalOnly?: boolean): URI[] { - const resources: URI[] = []; +export interface IDraggedResource { + resource: URI; + isExternal: boolean; +} + +export function extractResources(e: DragEvent, externalOnly?: boolean): IDraggedResource[] { + const resources: IDraggedResource[] = []; if (e.dataTransfer.types.length > 0) { // Check for in-app DND @@ -51,7 +56,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): URI[] { const rawData = e.dataTransfer.getData(e.dataTransfer.types[0]); if (rawData) { try { - resources.push(URI.parse(rawData)); + resources.push({ resource: URI.parse(rawData), isExternal: false }); } catch (error) { // Invalid URI } @@ -63,7 +68,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): URI[] { for (let i = 0; i < e.dataTransfer.files.length; i++) { if (e.dataTransfer.files[i] && e.dataTransfer.files[i].path) { try { - resources.push(URI.file(e.dataTransfer.files[i].path)); + resources.push({ resource: URI.file(e.dataTransfer.files[i].path), isExternal: true }); } catch (error) { // Invalid URI } diff --git a/src/vs/code/electron-main/launch.ts b/src/vs/code/electron-main/launch.ts index e61f029cde0a537bb4bce97c747c68f10d950c09..71670e049be48f6d2cd8ea92bec5010749b03059 100644 --- a/src/vs/code/electron-main/launch.ts +++ b/src/vs/code/electron-main/launch.ts @@ -14,6 +14,7 @@ import { IURLService } from 'vs/platform/url/common/url'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { ParsedArgs } from 'vs/platform/environment/node/argv'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { once } from 'vs/base/common/event'; export const ID = 'launchService'; export const ILaunchService = createDecorator(ID); @@ -111,10 +112,9 @@ export class LaunchService implements ILaunchService { const windowId = usedWindows[0].id; return new TPromise((c, e) => { - - const unbind = this.windowsService.onClose(id => { + const onceWindowClose = once(this.windowsService.onWindowClose); + onceWindowClose(id => { if (id === windowId) { - unbind(); c(null); } }); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index a431316e98fae0de66f13ec6a371e39bbb1d6dd6..a991fba12cff21be106641a9068ed34d236624be 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -5,10 +5,8 @@ 'use strict'; -import * as nls from 'vs/nls'; import { app, ipcMain as ipc } from 'electron'; import { assign } from 'vs/base/common/objects'; -import { trim } from 'vs/base/common/strings'; import * as platform from 'vs/base/common/platform'; import { parseMainProcessArgv, ParsedArgs } from 'vs/platform/environment/node/argv'; import { mkdirp } from 'vs/base/node/pfs'; @@ -44,7 +42,6 @@ import { ConfigurationService } from 'vs/platform/configuration/node/configurati import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/node/requestService'; import { generateUuid } from 'vs/base/common/uuid'; -import { getPathLabel } from 'vs/base/common/labels'; import { IURLService } from 'vs/platform/url/common/url'; import { URLChannel } from 'vs/platform/url/common/urlIpc'; import { URLService } from 'vs/platform/url/electron-main/urlService'; @@ -57,7 +54,6 @@ import product from 'vs/platform/product'; import pkg from 'vs/platform/package'; import * as fs from 'original-fs'; import * as cp from 'child_process'; -import * as path from 'path'; function quit(accessor: ServicesAccessor, error?: Error); function quit(accessor: ServicesAccessor, message?: string); @@ -245,12 +241,6 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo const menu = instantiationService2.createInstance(VSCodeMenu); menu.ready(); - // Install JumpList on Windows (keep updated when windows open) - if (platform.isWindows) { - updateJumpList(windowsMainService, logService); - windowsMainService.onOpen(() => updateJumpList(windowsMainService, logService)); - } - // Open our first window if (environmentService.args['new-window'] && environmentService.args._.length === 0) { windowsMainService.open({ cli: environmentService.args, forceNewWindow: true, forceEmpty: true }); // new window if "-n" was used without paths @@ -262,65 +252,6 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo }); } -// TODO@Joao TODO@Ben shouldn't this be inside windows service instead? -function updateJumpList(windowsMainService: IWindowsMainService, logService: ILogService): void { - const jumpList: Electron.JumpListCategory[] = []; - - // Tasks - jumpList.push({ - type: 'tasks', - items: [ - { - type: 'task', - title: nls.localize('newWindow', "New Window"), - description: nls.localize('newWindowDesc', "Opens a new window"), - program: process.execPath, - args: '-n', // force new window - iconPath: process.execPath, - iconIndex: 0 - } - ] - }); - - // Recent Folders - if (windowsMainService.getRecentPathsList().folders.length > 0) { - - // The user might have meanwhile removed items from the jump list and we have to respect that - // so we need to update our list of recent paths with the choice of the user to not add them again - // Also: Windows will not show our custom category at all if there is any entry which was removed - // by the user! See https://github.com/Microsoft/vscode/issues/15052 - windowsMainService.removeFromRecentPathsList(app.getJumpListSettings().removedItems.map(r => trim(r.args, '"'))); - - // Add entries - jumpList.push({ - type: 'custom', - name: nls.localize('recentFolders', "Recent Folders"), - items: windowsMainService.getRecentPathsList().folders.slice(0, 7 /* limit number of entries here */).map(folder => { - return { - type: 'task', - title: path.basename(folder) || folder, // use the base name to show shorter entries in the list - description: nls.localize('folderDesc', "{0} {1}", path.basename(folder), getPathLabel(path.dirname(folder))), - program: process.execPath, - args: `"${folder}"`, // open folder (use quotes to support paths with whitespaces) - iconPath: 'explorer.exe', // simulate folder icon - iconIndex: 0 - }; - }).filter(i => !!i) - }); - } - - // Recent - jumpList.push({ - type: 'recent' // this enables to show files in the "recent" category - }); - - try { - app.setJumpList(jumpList); - } catch (error) { - logService.log('#setJumpList', error); // since setJumpList is relatively new API, make sure to guard for errors - } -} - function setupIPC(accessor: ServicesAccessor): TPromise { const logService = accessor.get(ILogService); const environmentService = accessor.get(IEnvironmentService); diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index 34661e106e2e2e24f842192c348dcf22e24fc9f4..c636a3730c1762e24edd3836f41b85194b18c03d 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -11,7 +11,7 @@ import * as arrays from 'vs/base/common/arrays'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ipcMain as ipc, app, shell, dialog, Menu, MenuItem } from 'electron'; import { IWindowsMainService } from 'vs/code/electron-main/windows'; -import { IPath, VSCodeWindow } from 'vs/code/electron-main/window'; +import { VSCodeWindow } from 'vs/code/electron-main/window'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService } from 'vs/code/electron-main/storage'; import { IFilesConfiguration, AutoSaveConfiguration } from 'vs/platform/files/common/files'; @@ -19,6 +19,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService, State as UpdateState } from 'vs/platform/update/common/update'; import { Keybinding } from 'vs/base/common/keybinding'; import product from 'vs/platform/product'; +import { RunOnceScheduler } from 'vs/base/common/async'; interface IResolvedKeybinding { id: string; @@ -49,6 +50,8 @@ export class VSCodeMenu { private isQuitting: boolean; private appMenuInstalled: boolean; + private menuUpdater: RunOnceScheduler; + private actionIdKeybindingRequests: string[]; private mapLastKnownKeybindingToActionId: { [id: string]: string; }; private mapResolvedKeybindingToActionId: { [id: string]: string; }; @@ -67,6 +70,8 @@ export class VSCodeMenu { this.mapResolvedKeybindingToActionId = Object.create(null); this.mapLastKnownKeybindingToActionId = this.storageService.getItem<{ [id: string]: string; }>(VSCodeMenu.lastKnownKeybindingsMapStorageKey) || Object.create(null); + this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0); + this.onConfigurationUpdated(this.configurationService.getConfiguration()); } @@ -82,12 +87,12 @@ export class VSCodeMenu { this.isQuitting = true; }); - // Listen to "open" & "close" event from window service - this.windowsService.onOpen(paths => this.onOpen(paths)); - this.windowsService.onClose(_ => this.onClose(this.windowsService.getWindowCount())); + // Listen to some events from window service + this.windowsService.onRecentPathsChange(paths => this.updateMenu()); + this.windowsService.onWindowClose(_ => this.onClose(this.windowsService.getWindowCount())); // Resolve keybindings when any first workbench is loaded - this.windowsService.onReady(win => this.resolveKeybindings(win)); + this.windowsService.onWindowReady(win => this.resolveKeybindings(win)); // Listen to resolved keybindings ipc.on('vscode:keybindingsResolved', (event, rawKeybindings) => { @@ -172,6 +177,10 @@ export class VSCodeMenu { } private updateMenu(): void { + this.menuUpdater.schedule(); // buffer multiple attempts to update the menu + } + + private doUpdateMenu(): void { // Due to limitations in Electron, it is not possible to update menu items dynamically. The suggested // workaround from Electron is to set the application menu again. @@ -187,10 +196,6 @@ export class VSCodeMenu { } } - private onOpen(path: IPath): void { - this.updateMenu(); - } - private onClose(remainingWindowCount: number): void { if (remainingWindowCount === 0 && platform.isMacintosh) { this.updateMenu(); @@ -431,7 +436,7 @@ export class VSCodeMenu { if (folders.length || files.length) { openRecentMenu.append(__separator__()); - openRecentMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miClearItems', comment: ['&& denotes a mnemonic'] }, "&&Clear Items")), click: () => { this.windowsService.clearRecentPathsList(); this.updateMenu(); } })); + openRecentMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miClearItems', comment: ['&& denotes a mnemonic'] }, "&&Clear Items")), click: () => this.windowsService.clearRecentPathsList() })); } } @@ -442,7 +447,6 @@ export class VSCodeMenu { const success = !!this.windowsService.open({ cli: this.environmentService.args, pathsToOpen: [path], forceNewWindow: openInNewWindow }); if (!success) { this.windowsService.removeFromRecentPathsList(path); - this.updateMenu(); } } }); diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 82c65255c4ed554baf0645410bd06ca419646c82..ec8ea84e0f8164ed926d6a60ef7bc43f4446ff27 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -14,7 +14,7 @@ import * as paths from 'vs/base/common/paths'; import * as types from 'vs/base/common/types'; import * as arrays from 'vs/base/common/arrays'; import { assign, mixin } from 'vs/base/common/objects'; -import { EventEmitter } from 'events'; +import { trim } from 'vs/base/common/strings'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IStorageService } from 'vs/code/electron-main/storage'; import { IPath, VSCodeWindow, ReadyState, IWindowConfiguration, IWindowState as ISingleWindowState, defaultWindowState, IWindowSettings } from 'vs/code/electron-main/window'; @@ -23,19 +23,14 @@ import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/electro import { ILifecycleService } from 'vs/code/electron-main/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/code/electron-main/log'; +import { getPathLabel } from 'vs/base/common/labels'; import { IWindowEventService } from 'vs/code/common/windows'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import CommonEvent, { Emitter } from 'vs/base/common/event'; +import CommonEvent, { Emitter, once } from 'vs/base/common/event'; import product from 'vs/platform/product'; import { ParsedArgs } from 'vs/platform/environment/node/argv'; -const EventTypes = { - OPEN: 'open', - CLOSE: 'close', - READY: 'ready' -}; - enum WindowError { UNRESPONSIVE, CRASHED @@ -91,13 +86,12 @@ export const IWindowsMainService = createDecorator('windows export interface IWindowsMainService { _serviceBrand: any; - // TODO make proper events // events - onOpen(clb: (path: IPath) => void): () => void; - onReady(clb: (win: VSCodeWindow) => void): () => void; - onClose(clb: (id: number) => void): () => void; + onWindowReady: CommonEvent; + onWindowClose: CommonEvent; onNewWindowOpen: CommonEvent; onWindowFocus: CommonEvent; + onRecentPathsChange: CommonEvent; // methods ready(initialUserEnv: platform.IProcessEnvironment): void; @@ -118,11 +112,13 @@ export interface IWindowsMainService { getWindowById(windowId: number): VSCodeWindow; getWindows(): VSCodeWindow[]; getWindowCount(): number; + addToRecentPathsList(paths: { path: string; isFile?: boolean; }[]): void; getRecentPathsList(workspacePath?: string, filesToOpen?: IPath[]): IRecentPathsList; removeFromRecentPathsList(path: string); removeFromRecentPathsList(path: string[]); clearRecentPathsList(): void; toggleMenuBar(windowId: number): void; + updateWindowsJumpList(): void; } export class WindowsManager implements IWindowsMainService, IWindowEventService { @@ -137,7 +133,6 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService private static WINDOWS: VSCodeWindow[] = []; - private eventEmitter = new EventEmitter(); private initialUserEnv: platform.IProcessEnvironment; private windowsState: IWindowsState; @@ -147,6 +142,15 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService private _onNewWindow = new Emitter(); onNewWindowOpen: CommonEvent = this._onNewWindow.event; + private _onRecentPathsChange = new Emitter(); + onRecentPathsChange: CommonEvent = this._onRecentPathsChange.event; + + private _onWindowReady = new Emitter(); + onWindowReady: CommonEvent = this._onWindowReady.event; + + private _onWindowClose = new Emitter(); + onWindowClose: CommonEvent = this._onWindowClose.event; + constructor( @IInstantiationService private instantiationService: IInstantiationService, @ILogService private logService: ILogService, @@ -157,29 +161,13 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService @ITelemetryService private telemetryService: ITelemetryService ) { } - onOpen(clb: (path: IPath) => void): () => void { - this.eventEmitter.addListener(EventTypes.OPEN, clb); - - return () => this.eventEmitter.removeListener(EventTypes.OPEN, clb); - } - - onReady(clb: (win: VSCodeWindow) => void): () => void { - this.eventEmitter.addListener(EventTypes.READY, clb); - - return () => this.eventEmitter.removeListener(EventTypes.READY, clb); - } - - onClose(clb: (id: number) => void): () => void { - this.eventEmitter.addListener(EventTypes.CLOSE, clb); - - return () => this.eventEmitter.removeListener(EventTypes.CLOSE, clb); - } - public ready(initialUserEnv: platform.IProcessEnvironment): void { this.registerListeners(); this.initialUserEnv = initialUserEnv; this.windowsState = this.storageService.getItem(WindowsManager.windowsStateStorageKey) || { openedFolders: [] }; + + this.updateWindowsJumpList(); } private registerListeners(): void { @@ -223,7 +211,7 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService win.setReady(); // Event - this.eventEmitter.emit(EventTypes.READY, win); + this._onWindowReady.fire(win); } }); @@ -272,7 +260,8 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService }); let loggedStartupTimes = false; - this.onReady(window => { + const onceWindowReady = once(this.onWindowReady); + onceWindowReady(window => { if (loggedStartupTimes) { return; // only for the first window } @@ -281,6 +270,9 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService this.logStartupTimes(window); }); + + // Update jump list when recent paths change + this.onRecentPathsChange(() => this.updateWindowsJumpList()); } private logStartupTimes(window: VSCodeWindow): void { @@ -486,39 +478,47 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService // Remember in recent document list (unless this opens for extension development) // Also do not add paths when files are opened for diffing, only if opened individually if (!usedWindows.some(w => w.isPluginDevelopmentHost) && !openConfig.cli.diff) { + const recentPaths: { path: string; isFile?: boolean; }[] = []; + iPathsToOpen.forEach(iPath => { if (iPath.filePath || iPath.workspacePath) { app.addRecentDocument(iPath.filePath || iPath.workspacePath); - this.addToRecentPathsList(iPath.filePath || iPath.workspacePath, !!iPath.filePath); + recentPaths.push({ path: iPath.filePath || iPath.workspacePath, isFile: !!iPath.filePath }); } }); - } - // Emit events - iPathsToOpen.forEach(iPath => this.eventEmitter.emit(EventTypes.OPEN, iPath)); + if (recentPaths.length) { + this.addToRecentPathsList(recentPaths); + } + } return arrays.distinct(usedWindows); } - private addToRecentPathsList(path?: string, isFile?: boolean): void { - if (!path) { + public addToRecentPathsList(paths: { path: string; isFile?: boolean; }[]): void { + if (!paths || !paths.length) { return; } const mru = this.getRecentPathsList(); - if (isFile) { - mru.files.unshift(path); - mru.files = arrays.distinct(mru.files, (f) => platform.isLinux ? f : f.toLowerCase()); - } else { - mru.folders.unshift(path); - mru.folders = arrays.distinct(mru.folders, (f) => platform.isLinux ? f : f.toLowerCase()); - } + paths.forEach(p => { + const {path, isFile} = p; - // Make sure its bounded - mru.folders = mru.folders.slice(0, WindowsManager.MAX_TOTAL_RECENT_ENTRIES); - mru.files = mru.files.slice(0, WindowsManager.MAX_TOTAL_RECENT_ENTRIES); + if (isFile) { + mru.files.unshift(path); + mru.files = arrays.distinct(mru.files, (f) => platform.isLinux ? f : f.toLowerCase()); + } else { + mru.folders.unshift(path); + mru.folders = arrays.distinct(mru.folders, (f) => platform.isLinux ? f : f.toLowerCase()); + } + + // Make sure its bounded + mru.folders = mru.folders.slice(0, WindowsManager.MAX_TOTAL_RECENT_ENTRIES); + mru.files = mru.files.slice(0, WindowsManager.MAX_TOTAL_RECENT_ENTRIES); + }); this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru); + this._onRecentPathsChange.fire(); } public removeFromRecentPathsList(path: string): void; @@ -550,12 +550,16 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService if (update) { this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru); + this._onRecentPathsChange.fire(); } } public clearRecentPathsList(): void { this.storageService.setItem(WindowsManager.recentPathsListStorageKey, { folders: [], files: [] }); app.clearRecentDocuments(); + + // Event + this._onRecentPathsChange.fire(); } public getRecentPathsList(workspacePath?: string, filesToOpen?: IPath[]): IRecentPathsList { @@ -1117,7 +1121,7 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService WindowsManager.WINDOWS.splice(index, 1); // Emit - this.eventEmitter.emit(EventTypes.CLOSE, win.id); + this._onWindowClose.fire(win.id); } private isPathEqual(pathA: string, pathB: string): boolean { @@ -1144,7 +1148,7 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService return pathA === pathB; } - toggleMenuBar(windowId: number): void { + public toggleMenuBar(windowId: number): void { // Update in settings const menuBarHidden = this.storageService.getItem(VSCodeWindow.menuBarHiddenKey, false); const newMenuBarHidden = !menuBarHidden; @@ -1161,4 +1165,66 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService } } } -} + + public updateWindowsJumpList(): void { + if (!platform.isWindows) { + return; // only on windows + } + + const jumpList: Electron.JumpListCategory[] = []; + + // Tasks + jumpList.push({ + type: 'tasks', + items: [ + { + type: 'task', + title: nls.localize('newWindow', "New Window"), + description: nls.localize('newWindowDesc', "Opens a new window"), + program: process.execPath, + args: '-n', // force new window + iconPath: process.execPath, + iconIndex: 0 + } + ] + }); + + // Recent Folders + if (this.getRecentPathsList().folders.length > 0) { + + // The user might have meanwhile removed items from the jump list and we have to respect that + // so we need to update our list of recent paths with the choice of the user to not add them again + // Also: Windows will not show our custom category at all if there is any entry which was removed + // by the user! See https://github.com/Microsoft/vscode/issues/15052 + this.removeFromRecentPathsList(app.getJumpListSettings().removedItems.map(r => trim(r.args, '"'))); + + // Add entries + jumpList.push({ + type: 'custom', + name: nls.localize('recentFolders', "Recent Folders"), + items: this.getRecentPathsList().folders.slice(0, 7 /* limit number of entries here */).map(folder => { + return { + type: 'task', + title: path.basename(folder) || folder, // use the base name to show shorter entries in the list + description: nls.localize('folderDesc', "{0} {1}", path.basename(folder), getPathLabel(path.dirname(folder))), + program: process.execPath, + args: `"${folder}"`, // open folder (use quotes to support paths with whitespaces) + iconPath: 'explorer.exe', // simulate folder icon + iconIndex: 0 + }; + }).filter(i => !!i) + }); + } + + // Recent + jumpList.push({ + type: 'recent' // this enables to show files in the "recent" category + }); + + try { + app.setJumpList(jumpList); + } catch (error) { + this.logService.log('#setJumpList', error); // since setJumpList is relatively new API, make sure to guard for errors + } + } +} \ No newline at end of file diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index bb6fbaeb83aad2c53729001ae32b775271c2842b..f6683796ef3041b203ea31f881b769602f25ebef 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -24,6 +24,7 @@ export interface IWindowsService { closeFolder(windowId: number): TPromise; toggleFullScreen(windowId: number): TPromise; setRepresentedFilename(windowId: number, fileName: string): TPromise; + addToRecentlyOpen(paths: { path: string, isFile?: boolean }[]): TPromise; getRecentlyOpen(windowId: number): TPromise<{ files: string[]; folders: string[]; }>; focusWindow(windowId: number): TPromise; isMaximized(windowId: number): TPromise; @@ -67,6 +68,7 @@ export interface IWindowService { closeFolder(): TPromise; toggleFullScreen(): TPromise; setRepresentedFilename(fileName: string): TPromise; + addToRecentlyOpen(paths: { path: string, isFile?: boolean }[]): TPromise; getRecentlyOpen(): TPromise<{ files: string[]; folders: string[]; }>; focusWindow(): TPromise; setDocumentEdited(flag: boolean): TPromise; diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 5a4222746b3f5183fdfff78144e648575726ad3c..8ef9c2f4c337c39a6c55d796561f5c6b529173c8 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -18,6 +18,7 @@ export interface IWindowsChannel extends IChannel { call(command: 'closeFolder', arg: number): TPromise; call(command: 'toggleFullScreen', arg: number): TPromise; call(command: 'setRepresentedFilename', arg: [number, string]): TPromise; + call(command: 'addToRecentlyOpen', arg: { path: string, isFile?: boolean }[]): TPromise; call(command: 'getRecentlyOpen', arg: number): TPromise<{ files: string[]; folders: string[]; }>; call(command: 'focusWindow', arg: number): TPromise; call(command: 'isMaximized', arg: number): TPromise; @@ -52,6 +53,7 @@ export class WindowsChannel implements IWindowsChannel { case 'closeFolder': return this.service.closeFolder(arg); case 'toggleFullScreen': return this.service.toggleFullScreen(arg); case 'setRepresentedFilename': return this.service.setRepresentedFilename(arg[0], arg[1]); + case 'addToRecentlyOpen': return this.service.addToRecentlyOpen(arg); case 'getRecentlyOpen': return this.service.getRecentlyOpen(arg); case 'focusWindow': return this.service.focusWindow(arg); case 'isMaximized': return this.service.isMaximized(arg); @@ -114,6 +116,10 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('setRepresentedFilename', [windowId, fileName]); } + addToRecentlyOpen(paths: { path: string, isFile?: boolean }[]): TPromise { + return this.channel.call('addToRecentlyOpen', paths); + } + getRecentlyOpen(windowId: number): TPromise<{ files: string[]; folders: string[]; }> { return this.channel.call('getRecentlyOpen', windowId); } diff --git a/src/vs/platform/windows/electron-browser/windowService.ts b/src/vs/platform/windows/electron-browser/windowService.ts index 1e17608a41d57a98d468a7ce750200f84f07034d..7c080b909e36b1a3fdee38ed6ce521f5b0d556ca 100644 --- a/src/vs/platform/windows/electron-browser/windowService.ts +++ b/src/vs/platform/windows/electron-browser/windowService.ts @@ -57,6 +57,10 @@ export class WindowService implements IWindowService { return this.windowsService.setRepresentedFilename(this.windowId, fileName); } + addToRecentlyOpen(paths: { path: string, isFile?: boolean }[]): TPromise { + return this.windowsService.addToRecentlyOpen(paths); + } + getRecentlyOpen(): TPromise<{ files: string[]; folders: string[]; }> { return this.windowsService.getRecentlyOpen(this.windowId); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index b0fa255ccc19e132c158c3b32a24082561868ea0..14e71712ea783c4fa60d694f5a4cdd8ec7de115f 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -102,6 +102,12 @@ export class WindowsService implements IWindowsService { return TPromise.as(null); } + addToRecentlyOpen(paths: { path: string, isFile?: boolean }[]): TPromise { + this.windowsMainService.addToRecentPathsList(paths); + + return TPromise.as(null); + } + getRecentlyOpen(windowId: number): TPromise<{ files: string[]; folders: string[]; }> { const vscodeWindow = this.windowsMainService.getWindowById(windowId); diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditorControl.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditorControl.ts index c92369e61a854489e688585cf10e678c667770b0..3b93b28f19518e9acc3a64ba67107866cc28697b 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditorControl.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditorControl.ts @@ -15,7 +15,6 @@ import { Sash, ISashEvent, IVerticalSashLayoutProvider, IHorizontalSashLayoutPro import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import DOM = require('vs/base/browser/dom'); -import URI from 'vs/base/common/uri'; import errors = require('vs/base/common/errors'); import { RunOnceScheduler } from 'vs/base/common/async'; import { isMacintosh } from 'vs/base/common/platform'; @@ -35,7 +34,7 @@ import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { NoTabsTitleControl } from 'vs/workbench/browser/parts/editor/noTabsTitleControl'; import { IEditorStacksModel, IStacksModelChangeEvent, IWorkbenchEditorConfiguration, IEditorGroup, EditorOptions, TextEditorOptions, IEditorIdentifier } from 'vs/workbench/common/editor'; import { ITitleAreaControl } from 'vs/workbench/browser/parts/editor/titleControl'; -import { extractResources } from 'vs/base/browser/dnd'; +import { extractResources, IDraggedResource } from 'vs/base/browser/dnd'; import { IWindowService } from 'vs/platform/windows/common/windows'; export enum Rochade { @@ -923,7 +922,7 @@ export class SideBySideEditorControl implements ISideBySideEditorControl, IVerti const stacks = this.editorGroupService.getStacksModel(); let overlay: Builder; - let draggedResources: URI[]; + let draggedResources: IDraggedResource[]; function cleanUp(): void { draggedResources = void 0; @@ -1005,8 +1004,21 @@ export class SideBySideEditorControl implements ISideBySideEditorControl, IVerti // Check for URI transfer else { if (droppedResources.length) { + + // Add external ones to recently open list + const externalResources = droppedResources.filter(d => d.isExternal).map(d => d.resource); + if (externalResources.length) { + $this.windowService.addToRecentlyOpen(externalResources.map(resource => { + return { + path: resource.fsPath, + isFile: true + }; + })); + } + + // Open in Editor $this.windowService.focusWindow() - .then(() => editorService.openEditors(droppedResources.map(resource => { return { input: { resource, options: { pinned: true } }, position: splitEditor ? freeGroup : position }; }))) + .then(() => editorService.openEditors(droppedResources.map(d => { return { input: { resource: d.resource, options: { pinned: true } }, position: splitEditor ? freeGroup : position }; }))) .then(() => { if (splitEditor && splitTo !== freeGroup) { groupService.moveGroup(freeGroup, splitTo); @@ -1158,7 +1170,7 @@ export class SideBySideEditorControl implements ISideBySideEditorControl, IVerti // Upon first drag, detect the dragged resources and only take valid ones if (!draggedResources) { - draggedResources = extractResources(e).filter(r => r.scheme === 'file' || r.scheme === 'untitled'); + draggedResources = extractResources(e).filter(r => r.resource.scheme === 'file' || r.resource.scheme === 'untitled'); } if (!draggedResources.length && !TitleControl.getDraggedEditor()) { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index b59abdf4bdad806f48d565c5430091d94d94c610..37dfc55ec1f1964cc0789986bae40b337e5201c3 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -573,15 +573,27 @@ export class TabsTitleControl extends TitleControl { } private handleExternalDrop(e: DragEvent, targetPosition: Position, targetIndex: number): void { - const resources = extractResources(e).filter(r => r.scheme === 'file' || r.scheme === 'untitled'); + const resources = extractResources(e).filter(d => d.resource.scheme === 'file' || d.resource.scheme === 'untitled'); - // Open resources if found + // Handle resources if (resources.length) { DOM.EventHelper.stop(e, true); - this.editorService.openEditors(resources.map(resource => { + // Add external ones to recently open list + const externalResources = resources.filter(d => d.isExternal).map(d => d.resource); + if (externalResources.length) { + this.windowService.addToRecentlyOpen(externalResources.map(resource => { + return { + path: resource.fsPath, + isFile: true + }; + })); + } + + // Open in Editor + this.editorService.openEditors(resources.map(d => { return { - input: { resource, options: { pinned: true, index: targetIndex } }, + input: { resource: d.resource, options: { pinned: true, index: targetIndex } }, position: targetPosition }; })).then(() => { diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 684c834a0ea0c9766334b6e27fa2af818942474e..64555c4774e5cdb69521a4b29ae2591983770437 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -10,7 +10,7 @@ import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { stat } from 'vs/base/node/pfs'; import DOM = require('vs/base/browser/dom'); -import DND = require('vs/base/browser/dnd'); +import { extractResources } from 'vs/base/browser/dnd'; import { Builder, $ } from 'vs/base/browser/builder'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { asFileEditorInput } from 'vs/workbench/common/editor'; @@ -72,7 +72,7 @@ export class ElectronWindow { DOM.EventHelper.stop(e); if (!draggedExternalResources) { - draggedExternalResources = DND.extractResources(e, true /* external only */); + draggedExternalResources = extractResources(e, true /* external only */).map(d => d.resource); // Find out if folders are dragged and show the appropiate feedback then this.includesFolder(draggedExternalResources).done(includesFolder => {