mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 313ede61cbad8f9dc748907b3384e059ddddb79a (#7436)
* Merge from vscode 313ede61cbad8f9dc748907b3384e059ddddb79a * fix strict null checks
This commit is contained in:
@@ -8,6 +8,7 @@ import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth
|
||||
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
const SERVICE_NAME = 'VS Code';
|
||||
const ACCOUNT = 'MyAccount';
|
||||
@@ -23,9 +24,10 @@ export class AuthTokenService extends Disposable implements IAuthTokenService {
|
||||
constructor(
|
||||
@ICredentialsService private readonly credentialsService: ICredentialsService,
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
if (productService.settingsSyncStoreUrl) {
|
||||
if (productService.settingsSyncStoreUrl && configurationService.getValue('configurationSync.enableAuth')) {
|
||||
this._status = AuthTokenStatus.Inactive;
|
||||
this.getToken().then(token => {
|
||||
if (token) {
|
||||
|
||||
211
src/vs/platform/dialogs/electron-main/dialogs.ts
Normal file
211
src/vs/platform/dialogs/electron-main/dialogs.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, dialog, FileFilter, BrowserWindow } from 'electron';
|
||||
import { Queue } from 'vs/base/common/async';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { dirname } from 'vs/base/common/path';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
import { exists } from 'vs/base/node/pfs';
|
||||
import { INativeOpenDialogOptions, MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue } from 'vs/platform/dialogs/node/dialogs';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { localize } from 'vs/nls';
|
||||
import { WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
|
||||
export const IDialogMainService = createDecorator<IDialogMainService>('dialogMainService');
|
||||
|
||||
export interface IDialogMainService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
pickFileFolder(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise<string[] | undefined>;
|
||||
pickFolder(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise<string[] | undefined>;
|
||||
pickFile(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise<string[] | undefined>;
|
||||
pickWorkspace(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise<string[] | undefined>;
|
||||
|
||||
showMessageBox(options: MessageBoxOptions, window?: BrowserWindow): Promise<MessageBoxReturnValue>;
|
||||
showSaveDialog(options: SaveDialogOptions, window?: BrowserWindow): Promise<SaveDialogReturnValue>;
|
||||
showOpenDialog(options: OpenDialogOptions, window?: BrowserWindow): Promise<OpenDialogReturnValue>;
|
||||
}
|
||||
|
||||
interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions {
|
||||
pickFolders?: boolean;
|
||||
pickFiles?: boolean;
|
||||
|
||||
title: string;
|
||||
buttonLabel?: string;
|
||||
filters?: FileFilter[];
|
||||
}
|
||||
|
||||
export class DialogMainService implements IDialogMainService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private static readonly workingDirPickerStorageKey = 'pickerWorkingDir';
|
||||
|
||||
private readonly mapWindowToDialogQueue: Map<number, Queue<void>>;
|
||||
private readonly noWindowDialogQueue: Queue<void>;
|
||||
|
||||
constructor(
|
||||
@IStateService private readonly stateService: IStateService
|
||||
) {
|
||||
this.mapWindowToDialogQueue = new Map<number, Queue<void>>();
|
||||
this.noWindowDialogQueue = new Queue<void>();
|
||||
}
|
||||
|
||||
pickFileFolder(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise<string[] | undefined> {
|
||||
return this.doPick({ ...options, pickFolders: true, pickFiles: true, title: localize('open', "Open") }, window);
|
||||
}
|
||||
|
||||
pickFolder(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise<string[] | undefined> {
|
||||
return this.doPick({ ...options, pickFolders: true, title: localize('openFolder', "Open Folder") }, window);
|
||||
}
|
||||
|
||||
pickFile(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise<string[] | undefined> {
|
||||
return this.doPick({ ...options, pickFiles: true, title: localize('openFile', "Open File") }, window);
|
||||
}
|
||||
|
||||
pickWorkspace(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise<string[] | undefined> {
|
||||
const title = localize('openWorkspaceTitle', "Open Workspace");
|
||||
const buttonLabel = mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open"));
|
||||
const filters = WORKSPACE_FILTER;
|
||||
|
||||
return this.doPick({ ...options, pickFiles: true, title, filters, buttonLabel }, window);
|
||||
}
|
||||
|
||||
private async doPick(options: IInternalNativeOpenDialogOptions, window?: BrowserWindow): Promise<string[] | undefined> {
|
||||
|
||||
// Ensure dialog options
|
||||
const dialogOptions: OpenDialogOptions = {
|
||||
title: options.title,
|
||||
buttonLabel: options.buttonLabel,
|
||||
filters: options.filters
|
||||
};
|
||||
|
||||
// Ensure defaultPath
|
||||
dialogOptions.defaultPath = options.defaultPath || this.stateService.getItem<string>(DialogMainService.workingDirPickerStorageKey);
|
||||
|
||||
|
||||
// Ensure properties
|
||||
if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') {
|
||||
dialogOptions.properties = undefined; // let it override based on the booleans
|
||||
|
||||
if (options.pickFiles && options.pickFolders) {
|
||||
dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!dialogOptions.properties) {
|
||||
dialogOptions.properties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
|
||||
}
|
||||
|
||||
if (isMacintosh) {
|
||||
dialogOptions.properties.push('treatPackageAsDirectory'); // always drill into .app files
|
||||
}
|
||||
|
||||
// Show Dialog
|
||||
const windowToUse = window || BrowserWindow.getFocusedWindow();
|
||||
|
||||
const result = await this.showOpenDialog(dialogOptions, withNullAsUndefined(windowToUse));
|
||||
if (result && result.filePaths && result.filePaths.length > 0) {
|
||||
|
||||
// Remember path in storage for next time
|
||||
this.stateService.setItem(DialogMainService.workingDirPickerStorageKey, dirname(result.filePaths[0]));
|
||||
|
||||
return result.filePaths;
|
||||
}
|
||||
|
||||
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
|
||||
private getDialogQueue(window?: BrowserWindow): Queue<any> {
|
||||
if (!window) {
|
||||
return this.noWindowDialogQueue;
|
||||
}
|
||||
|
||||
let windowDialogQueue = this.mapWindowToDialogQueue.get(window.id);
|
||||
if (!windowDialogQueue) {
|
||||
windowDialogQueue = new Queue<any>();
|
||||
this.mapWindowToDialogQueue.set(window.id, windowDialogQueue);
|
||||
}
|
||||
|
||||
return windowDialogQueue;
|
||||
}
|
||||
|
||||
showMessageBox(options: MessageBoxOptions, window?: BrowserWindow): Promise<MessageBoxReturnValue> {
|
||||
return this.getDialogQueue(window).queue(async () => {
|
||||
return new Promise(resolve => {
|
||||
if (window) {
|
||||
return dialog.showMessageBox(window, options, (response, checkboxChecked) => resolve({ response, checkboxChecked }));
|
||||
}
|
||||
|
||||
return dialog.showMessageBox(options);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
showSaveDialog(options: SaveDialogOptions, window?: BrowserWindow): Promise<SaveDialogReturnValue> {
|
||||
|
||||
function normalizePath(path: string | undefined): string | undefined {
|
||||
if (path && isMacintosh) {
|
||||
path = normalizeNFC(path); // normalize paths returned from the OS
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
return this.getDialogQueue(window).queue(async () => {
|
||||
return new Promise<SaveDialogReturnValue>(resolve => {
|
||||
if (window) {
|
||||
dialog.showSaveDialog(window, options, filePath => resolve({ filePath }));
|
||||
} else {
|
||||
dialog.showSaveDialog(options, filePath => resolve({ filePath }));
|
||||
}
|
||||
}).then(result => {
|
||||
result.filePath = normalizePath(result.filePath);
|
||||
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
showOpenDialog(options: OpenDialogOptions, window?: BrowserWindow): Promise<OpenDialogReturnValue> {
|
||||
|
||||
function normalizePaths(paths: string[] | undefined): string[] | undefined {
|
||||
if (paths && paths.length > 0 && isMacintosh) {
|
||||
paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
return this.getDialogQueue(window).queue(async () => {
|
||||
|
||||
// Ensure the path exists (if provided)
|
||||
if (options.defaultPath) {
|
||||
const pathExists = await exists(options.defaultPath);
|
||||
if (!pathExists) {
|
||||
options.defaultPath = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Show dialog
|
||||
return new Promise<OpenDialogReturnValue>(resolve => {
|
||||
if (window) {
|
||||
dialog.showOpenDialog(window, options, filePaths => resolve({ filePaths }));
|
||||
} else {
|
||||
dialog.showOpenDialog(options, filePaths => resolve({ filePaths }));
|
||||
}
|
||||
}).then(result => {
|
||||
result.filePaths = normalizePaths(result.filePaths);
|
||||
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -13,3 +13,16 @@ export interface INativeOpenDialogOptions {
|
||||
telemetryEventName?: string;
|
||||
telemetryExtraData?: ITelemetryData;
|
||||
}
|
||||
|
||||
export interface MessageBoxReturnValue {
|
||||
response: number;
|
||||
checkboxChecked: boolean;
|
||||
}
|
||||
|
||||
export interface SaveDialogReturnValue {
|
||||
filePath?: string;
|
||||
}
|
||||
|
||||
export interface OpenDialogReturnValue {
|
||||
filePaths?: string[];
|
||||
}
|
||||
|
||||
@@ -5,16 +5,17 @@
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, CrashReporterStartOptions, crashReporter, Menu, BrowserWindow, app } from 'electron';
|
||||
import { INativeOpenInWindowOptions } from 'vs/platform/windows/node/window';
|
||||
import { MessageBoxOptions, shell, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, CrashReporterStartOptions, crashReporter, Menu, BrowserWindow, app } from 'electron';
|
||||
import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window';
|
||||
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { IOpenedWindow, OpenContext, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows';
|
||||
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs';
|
||||
import { INativeOpenDialogOptions, MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue } from 'vs/platform/dialogs/node/dialogs';
|
||||
import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { IElectronService } from 'vs/platform/electron/node/electron';
|
||||
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { AddFirstParameterToFunctions } from 'vs/base/common/types';
|
||||
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
|
||||
|
||||
export class ElectronMainService implements AddFirstParameterToFunctions<IElectronService, Promise<any> /* only methods, not events */, number /* window ID */> {
|
||||
|
||||
@@ -22,6 +23,7 @@ export class ElectronMainService implements AddFirstParameterToFunctions<IElectr
|
||||
|
||||
constructor(
|
||||
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
|
||||
@IDialogMainService private readonly dialogMainService: IDialogMainService,
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService
|
||||
) {
|
||||
@@ -61,7 +63,7 @@ export class ElectronMainService implements AddFirstParameterToFunctions<IElectr
|
||||
}
|
||||
|
||||
async getActiveWindowId(windowId: number): Promise<number | undefined> {
|
||||
const activeWindow = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
|
||||
const activeWindow = BrowserWindow.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
|
||||
if (activeWindow) {
|
||||
return activeWindow.id;
|
||||
}
|
||||
@@ -69,11 +71,17 @@ export class ElectronMainService implements AddFirstParameterToFunctions<IElectr
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async openEmptyWindow(windowId: number, options?: IOpenEmptyWindowOptions): Promise<void> {
|
||||
this.windowsMainService.openEmptyWindow(OpenContext.API, options);
|
||||
openWindow(windowId: number, options?: IOpenEmptyWindowOptions): Promise<void>;
|
||||
openWindow(windowId: number, toOpen: IWindowOpenable[], options?: INativeOpenWindowOptions): Promise<void>;
|
||||
openWindow(windowId: number, arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: INativeOpenWindowOptions): Promise<void> {
|
||||
if (Array.isArray(arg1)) {
|
||||
return this.doOpenWindow(windowId, arg1, arg2);
|
||||
}
|
||||
|
||||
return this.doOpenEmptyWindow(windowId, arg1);
|
||||
}
|
||||
|
||||
async openInWindow(windowId: number, toOpen: IWindowOpenable[], options: INativeOpenInWindowOptions = Object.create(null)): Promise<void> {
|
||||
private async doOpenWindow(windowId: number, toOpen: IWindowOpenable[], options: INativeOpenWindowOptions = Object.create(null)): Promise<void> {
|
||||
if (toOpen.length > 0) {
|
||||
this.windowsMainService.open({
|
||||
context: OpenContext.API,
|
||||
@@ -91,6 +99,10 @@ export class ElectronMainService implements AddFirstParameterToFunctions<IElectr
|
||||
}
|
||||
}
|
||||
|
||||
private async doOpenEmptyWindow(windowId: number, options?: IOpenEmptyWindowOptions): Promise<void> {
|
||||
this.windowsMainService.openEmptyWindow(OpenContext.API, options);
|
||||
}
|
||||
|
||||
async toggleFullScreen(windowId: number): Promise<void> {
|
||||
const window = this.windowsMainService.getWindowById(windowId);
|
||||
if (window) {
|
||||
@@ -164,15 +176,24 @@ export class ElectronMainService implements AddFirstParameterToFunctions<IElectr
|
||||
//#region Dialog
|
||||
|
||||
async showMessageBox(windowId: number, options: MessageBoxOptions): Promise<MessageBoxReturnValue> {
|
||||
return this.windowsMainService.showMessageBox(options, this.windowsMainService.getWindowById(windowId));
|
||||
return this.dialogMainService.showMessageBox(options, this.toBrowserWindow(windowId));
|
||||
}
|
||||
|
||||
async showSaveDialog(windowId: number, options: SaveDialogOptions): Promise<SaveDialogReturnValue> {
|
||||
return this.windowsMainService.showSaveDialog(options, this.windowsMainService.getWindowById(windowId));
|
||||
return this.dialogMainService.showSaveDialog(options, this.toBrowserWindow(windowId));
|
||||
}
|
||||
|
||||
async showOpenDialog(windowId: number, options: OpenDialogOptions): Promise<OpenDialogReturnValue> {
|
||||
return this.windowsMainService.showOpenDialog(options, this.windowsMainService.getWindowById(windowId));
|
||||
return this.dialogMainService.showOpenDialog(options, this.toBrowserWindow(windowId));
|
||||
}
|
||||
|
||||
private toBrowserWindow(windowId: number): BrowserWindow | undefined {
|
||||
const window = this.windowsMainService.getWindowById(windowId);
|
||||
if (window) {
|
||||
return window.win;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async pickFileFolderAndOpen(windowId: number, options: INativeOpenDialogOptions): Promise<void> {
|
||||
@@ -214,7 +235,9 @@ export class ElectronMainService implements AddFirstParameterToFunctions<IElectr
|
||||
}
|
||||
|
||||
async openExternal(windowId: number, url: string): Promise<boolean> {
|
||||
return this.windowsMainService.openExternal(url);
|
||||
shell.openExternal(url);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): Promise<void> {
|
||||
@@ -229,7 +252,7 @@ export class ElectronMainService implements AddFirstParameterToFunctions<IElectr
|
||||
//#region macOS Touchbar
|
||||
|
||||
async newWindowTab(): Promise<void> {
|
||||
this.windowsMainService.openNewTabbedWindow(OpenContext.API);
|
||||
this.windowsMainService.open({ context: OpenContext.API, cli: this.environmentService.args, forceNewTabbedWindow: true, forceEmpty: true });
|
||||
}
|
||||
|
||||
async showPreviousWindowTab(): Promise<void> {
|
||||
@@ -267,7 +290,7 @@ export class ElectronMainService implements AddFirstParameterToFunctions<IElectr
|
||||
}
|
||||
}
|
||||
|
||||
async closeWorkpsace(windowId: number): Promise<void> {
|
||||
async closeWorkspace(windowId: number): Promise<void> {
|
||||
const window = this.windowsMainService.getWindowById(windowId);
|
||||
if (window) {
|
||||
return this.windowsMainService.closeWorkspace(window);
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, CrashReporterStartOptions } from 'electron';
|
||||
import { MessageBoxOptions, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, CrashReporterStartOptions } from 'electron';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWindowOpenable, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows';
|
||||
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs';
|
||||
import { INativeOpenDialogOptions, MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue } from 'vs/platform/dialogs/node/dialogs';
|
||||
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { ParsedArgs } from 'vscode-minimist';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { INativeOpenInWindowOptions } from 'vs/platform/windows/node/window';
|
||||
import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window';
|
||||
|
||||
export const IElectronService = createDecorator<IElectronService>('electronService');
|
||||
|
||||
@@ -33,8 +33,8 @@ export interface IElectronService {
|
||||
getWindowCount(): Promise<number>;
|
||||
getActiveWindowId(): Promise<number | undefined>;
|
||||
|
||||
openEmptyWindow(options?: IOpenEmptyWindowOptions): Promise<void>;
|
||||
openInWindow(toOpen: IWindowOpenable[], options?: INativeOpenInWindowOptions): Promise<void>;
|
||||
openWindow(options?: IOpenEmptyWindowOptions): Promise<void>;
|
||||
openWindow(toOpen: IWindowOpenable[], options?: INativeOpenWindowOptions): Promise<void>;
|
||||
|
||||
toggleFullScreen(): Promise<void>;
|
||||
|
||||
@@ -76,7 +76,7 @@ export interface IElectronService {
|
||||
// Lifecycle
|
||||
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): Promise<void>;
|
||||
reload(): Promise<void>;
|
||||
closeWorkpsace(): Promise<void>;
|
||||
closeWorkspace(): Promise<void>;
|
||||
closeWindow(): Promise<void>;
|
||||
quit(): Promise<void>;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ISharedProcess } from 'vs/platform/windows/electron-main/windows';
|
||||
|
||||
export const ISharedProcessMainService = createDecorator<ISharedProcessMainService>('sharedProcessMainService');
|
||||
|
||||
@@ -15,6 +14,12 @@ export interface ISharedProcessMainService {
|
||||
whenSharedProcessReady(): Promise<void>;
|
||||
toggleSharedProcessWindow(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ISharedProcess {
|
||||
whenReady(): Promise<void>;
|
||||
toggle(): void;
|
||||
}
|
||||
|
||||
export class SharedProcessMainService implements ISharedProcessMainService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
@@ -7,15 +7,16 @@ import { localize } from 'vs/nls';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { IIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/node/issue';
|
||||
import { BrowserWindow, ipcMain, screen, dialog, IpcMainEvent, Display } from 'electron';
|
||||
import { BrowserWindow, ipcMain, screen, Event as IpcMainEvent, Display, shell } from 'electron';
|
||||
import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
|
||||
import { PerformanceInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWindowState, IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IWindowState } from 'vs/platform/windows/electron-main/windows';
|
||||
import { listProcesses } from 'vs/base/node/ps';
|
||||
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
|
||||
|
||||
const DEFAULT_BACKGROUND_COLOR = '#1E1E1E';
|
||||
|
||||
@@ -33,7 +34,7 @@ export class IssueMainService implements IIssueService {
|
||||
@ILaunchMainService private readonly launchMainService: ILaunchMainService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IDiagnosticsService private readonly diagnosticsService: IDiagnosticsService,
|
||||
@IWindowsMainService private readonly windowsMainService: IWindowsMainService
|
||||
@IDialogMainService private readonly dialogMainService: IDialogMainService
|
||||
) {
|
||||
this.registerListeners();
|
||||
}
|
||||
@@ -89,7 +90,7 @@ export class IssueMainService implements IIssueService {
|
||||
};
|
||||
|
||||
if (this._issueWindow) {
|
||||
dialog.showMessageBox(this._issueWindow, messageOptions)
|
||||
this.dialogMainService.showMessageBox(messageOptions, this._issueWindow)
|
||||
.then(result => {
|
||||
event.sender.send('vscode:issueReporterClipboardResponse', result.response === 0);
|
||||
});
|
||||
@@ -113,7 +114,7 @@ export class IssueMainService implements IIssueService {
|
||||
};
|
||||
|
||||
if (this._issueWindow) {
|
||||
dialog.showMessageBox(this._issueWindow, messageOptions)
|
||||
this.dialogMainService.showMessageBox(messageOptions, this._issueWindow)
|
||||
.then(result => {
|
||||
if (result.response === 0) {
|
||||
if (this._issueWindow) {
|
||||
@@ -146,7 +147,7 @@ export class IssueMainService implements IIssueService {
|
||||
});
|
||||
|
||||
ipcMain.on('vscode:openExternal', (_: unknown, arg: string) => {
|
||||
this.windowsMainService.openExternal(arg);
|
||||
shell.openExternal(arg);
|
||||
});
|
||||
|
||||
ipcMain.on('vscode:closeIssueReporter', (event: IpcMainEvent) => {
|
||||
|
||||
@@ -141,6 +141,7 @@ export class LaunchMainService implements ILaunchMainService {
|
||||
}
|
||||
}
|
||||
|
||||
// Open new Window
|
||||
if (openNewWindow) {
|
||||
usedWindows = this.windowsMainService.open({
|
||||
context,
|
||||
@@ -150,8 +151,18 @@ export class LaunchMainService implements ILaunchMainService {
|
||||
forceEmpty: true,
|
||||
waitMarkerFileURI
|
||||
});
|
||||
} else {
|
||||
usedWindows = [this.windowsMainService.focusLastActive(args, context)];
|
||||
}
|
||||
|
||||
// Focus existing window or open if none opened
|
||||
else {
|
||||
const lastActive = this.windowsMainService.getLastActiveWindow();
|
||||
if (lastActive) {
|
||||
lastActive.focus();
|
||||
|
||||
usedWindows = [lastActive];
|
||||
} else {
|
||||
usedWindows = this.windowsMainService.open({ context, cli: args, forceEmpty: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +227,7 @@ export class LaunchMainService implements ILaunchMainService {
|
||||
mainPID: process.pid,
|
||||
mainArguments: process.argv.slice(1),
|
||||
windows,
|
||||
screenReader: !!app.accessibilitySupportEnabled,
|
||||
screenReader: !!app.isAccessibilitySupportEnabled(),
|
||||
gpuFeatureStatus: app.getGPUFeatureStatus()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,4 +87,10 @@ export class BufferLogService extends AbstractLogService implements ILogService
|
||||
this._logger.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
if (this._logger) {
|
||||
this._logger.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,8 +78,7 @@ export class FileLogService extends AbstractLogService implements ILogService {
|
||||
}
|
||||
}
|
||||
|
||||
flush(): Promise<void> {
|
||||
return this.queue.queue(() => Promise.resolve());
|
||||
flush(): void {
|
||||
}
|
||||
|
||||
log(level: LogLevel, args: any[]): void {
|
||||
|
||||
@@ -41,6 +41,11 @@ export interface ILogger extends IDisposable {
|
||||
warn(message: string, ...args: any[]): void;
|
||||
error(message: string | Error, ...args: any[]): void;
|
||||
critical(message: string | Error, ...args: any[]): void;
|
||||
|
||||
/**
|
||||
* An operation to flush the contents. Can be synchronous.
|
||||
*/
|
||||
flush(): void;
|
||||
}
|
||||
|
||||
export interface ILogService extends ILogger {
|
||||
@@ -69,6 +74,7 @@ export abstract class AbstractLogService extends Disposable {
|
||||
getLevel(): LogLevel {
|
||||
return this.level;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ConsoleLogMainService extends AbstractLogService implements ILogService {
|
||||
@@ -145,6 +151,11 @@ export class ConsoleLogMainService extends AbstractLogService implements ILogSer
|
||||
dispose(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ConsoleLogService extends AbstractLogService implements ILogService {
|
||||
@@ -192,7 +203,13 @@ export class ConsoleLogService extends AbstractLogService implements ILogService
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void { }
|
||||
dispose(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
export class ConsoleLogInMainService extends AbstractLogService implements ILogService {
|
||||
@@ -240,7 +257,13 @@ export class ConsoleLogInMainService extends AbstractLogService implements ILogS
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void { }
|
||||
dispose(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiplexLogService extends AbstractLogService implements ILogService {
|
||||
@@ -296,6 +319,12 @@ export class MultiplexLogService extends AbstractLogService implements ILogServi
|
||||
}
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
for (const logService of this.logServices) {
|
||||
logService.flush();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
for (const logService of this.logServices) {
|
||||
logService.dispose();
|
||||
@@ -346,6 +375,10 @@ export class DelegatedLogService extends Disposable implements ILogService {
|
||||
critical(message: string | Error, ...args: any[]): void {
|
||||
this.logService.critical(message, ...args);
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
this.logService.flush();
|
||||
}
|
||||
}
|
||||
|
||||
export class NullLogService implements ILogService {
|
||||
@@ -360,6 +393,7 @@ export class NullLogService implements ILogService {
|
||||
error(message: string | Error, ...args: any[]): void { }
|
||||
critical(message: string | Error, ...args: any[]): void { }
|
||||
dispose(): void { }
|
||||
flush(): void { }
|
||||
}
|
||||
|
||||
export function getLogLevel(environmentService: IEnvironmentService): LogLevel {
|
||||
|
||||
@@ -32,7 +32,7 @@ export class LoggerService extends Disposable implements ILoggerService {
|
||||
if (resource.scheme === Schemas.file) {
|
||||
const baseName = basename(resource);
|
||||
const ext = extname(resource);
|
||||
logger = new SpdLogService(baseName.substring(0, baseName.length - ext.length), dirname(resource).path, this.logService.getLevel());
|
||||
logger = new SpdLogService(baseName.substring(0, baseName.length - ext.length), dirname(resource).fsPath, this.logService.getLevel());
|
||||
} else {
|
||||
logger = this.instantiationService.createInstance(FileLogService, basename(resource), resource, this.logService.getLevel());
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ interface ILog {
|
||||
message: string;
|
||||
}
|
||||
|
||||
function log(logger: spdlog.RotatingLogger, level: LogLevel, message: string, sync: boolean): void {
|
||||
function log(logger: spdlog.RotatingLogger, level: LogLevel, message: string): void {
|
||||
switch (level) {
|
||||
case LogLevel.Trace: logger.trace(message); break;
|
||||
case LogLevel.Debug: logger.debug(message); break;
|
||||
@@ -40,9 +40,6 @@ function log(logger: spdlog.RotatingLogger, level: LogLevel, message: string, sy
|
||||
case LogLevel.Critical: logger.critical(message); break;
|
||||
default: throw new Error('Invalid log level');
|
||||
}
|
||||
if (sync) {
|
||||
logger.flush();
|
||||
}
|
||||
}
|
||||
|
||||
export class SpdLogService extends AbstractLogService implements ILogService {
|
||||
@@ -53,7 +50,7 @@ export class SpdLogService extends AbstractLogService implements ILogService {
|
||||
private _loggerCreationPromise: Promise<void> | undefined = undefined;
|
||||
private _logger: spdlog.RotatingLogger | undefined;
|
||||
|
||||
constructor(private readonly name: string, private readonly logsFolder: string, level: LogLevel, private readonly sync: boolean = false) {
|
||||
constructor(private readonly name: string, private readonly logsFolder: string, level: LogLevel) {
|
||||
super();
|
||||
this.setLevel(level);
|
||||
this._createSpdLogLogger();
|
||||
@@ -72,7 +69,7 @@ export class SpdLogService extends AbstractLogService implements ILogService {
|
||||
this._logger = logger;
|
||||
this._logger.setLevel(this.getLevel());
|
||||
for (const { level, message } of this.buffer) {
|
||||
log(this._logger, level, message, this.sync);
|
||||
log(this._logger, level, message);
|
||||
}
|
||||
this.buffer = [];
|
||||
}
|
||||
@@ -83,7 +80,7 @@ export class SpdLogService extends AbstractLogService implements ILogService {
|
||||
|
||||
private _log(level: LogLevel, message: string): void {
|
||||
if (this._logger) {
|
||||
log(this._logger, level, message, this.sync);
|
||||
log(this._logger, level, message);
|
||||
} else if (this.getLevel() <= level) {
|
||||
this.buffer.push({ level, message });
|
||||
}
|
||||
@@ -132,6 +129,14 @@ export class SpdLogService extends AbstractLogService implements ILogService {
|
||||
}
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
if (this._logger) {
|
||||
this._logger.flush();
|
||||
} else if (this._loggerCreationPromise) {
|
||||
this._loggerCreationPromise.then(() => this.flush());
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this._logger) {
|
||||
this.disposeLogger();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as nls from 'vs/nls';
|
||||
import { isMacintosh, language } from 'vs/base/common/platform';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { app, shell, Menu, MenuItem, BrowserWindow, MenuItemConstructorOptions, WebContents, Event, KeyboardEvent } from 'electron';
|
||||
import { app, shell, Menu, MenuItem, BrowserWindow, MenuItemConstructorOptions, WebContents, Event, Event as KeyboardEvent } from 'electron';
|
||||
import { OpenContext, IRunActionInWindowRequest, getTitleBarStyle, IRunKeybindingInWindowRequest, IWindowOpenable } from 'vs/platform/windows/common/windows';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -368,13 +368,13 @@ export class Menubar {
|
||||
const servicesMenu = new Menu();
|
||||
const services = new MenuItem({ label: nls.localize('mServices', "Services"), role: 'services', submenu: servicesMenu });
|
||||
const hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", product.nameLong), role: 'hide', accelerator: 'Command+H' });
|
||||
const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideOthers', accelerator: 'Command+Alt+H' });
|
||||
const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideothers', accelerator: 'Command+Alt+H' });
|
||||
const showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' });
|
||||
const quit = new MenuItem(this.likeAction('workbench.action.quit', {
|
||||
label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => {
|
||||
if (
|
||||
this.windowsMainService.getWindowCount() === 0 || // allow to quit when no more windows are open
|
||||
!!this.windowsMainService.getFocusedWindow() || // allow to quit when window has focus (fix for https://github.com/Microsoft/vscode/issues/39191)
|
||||
this.windowsMainService.getWindowCount() === 0 || // allow to quit when no more windows are open
|
||||
!!BrowserWindow.getFocusedWindow() || // allow to quit when window has focus (fix for https://github.com/Microsoft/vscode/issues/39191)
|
||||
this.windowsMainService.getLastActiveWindow()!.isMinimized() // allow to quit when window has no focus but is minimized (https://github.com/Microsoft/vscode/issues/63000)
|
||||
) {
|
||||
this.windowsMainService.quit();
|
||||
@@ -557,7 +557,7 @@ export class Menubar {
|
||||
label: this.mnemonicLabel(nls.localize('miCheckForUpdates', "Check for &&Updates...")), click: () => setTimeout(() => {
|
||||
this.reportMenuActionTelemetry('CheckForUpdate');
|
||||
|
||||
const focusedWindow = this.windowsMainService.getFocusedWindow();
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
const context = focusedWindow ? { windowId: focusedWindow.id } : null;
|
||||
this.updateService.checkForUpdates(context);
|
||||
}, 0)
|
||||
@@ -697,15 +697,16 @@ export class Menubar {
|
||||
|
||||
private makeContextAwareClickHandler(click: () => void, contextSpecificHandlers: IMenuItemClickHandler): () => void {
|
||||
return () => {
|
||||
|
||||
// No Active Window
|
||||
const activeWindow = this.windowsMainService.getFocusedWindow();
|
||||
const activeWindow = BrowserWindow.getFocusedWindow();
|
||||
if (!activeWindow) {
|
||||
return contextSpecificHandlers.inNoWindow();
|
||||
}
|
||||
|
||||
// DevTools focused
|
||||
if (activeWindow.win.webContents.isDevToolsFocused()) {
|
||||
return contextSpecificHandlers.inDevTools(activeWindow.win.webContents.devToolsWebContents);
|
||||
if (activeWindow.webContents.isDevToolsFocused()) {
|
||||
return contextSpecificHandlers.inDevTools(activeWindow.webContents.devToolsWebContents);
|
||||
}
|
||||
|
||||
// Finally execute command in Window
|
||||
@@ -719,14 +720,15 @@ export class Menubar {
|
||||
// https://github.com/Microsoft/vscode/issues/11928
|
||||
// Still allow to run when the last active window is minimized though for
|
||||
// https://github.com/Microsoft/vscode/issues/63000
|
||||
let activeWindow = this.windowsMainService.getFocusedWindow();
|
||||
if (!activeWindow) {
|
||||
let activeBrowserWindow = BrowserWindow.getFocusedWindow();
|
||||
if (!activeBrowserWindow) {
|
||||
const lastActiveWindow = this.windowsMainService.getLastActiveWindow();
|
||||
if (lastActiveWindow && lastActiveWindow.isMinimized()) {
|
||||
activeWindow = lastActiveWindow;
|
||||
activeBrowserWindow = lastActiveWindow.win;
|
||||
}
|
||||
}
|
||||
|
||||
const activeWindow = activeBrowserWindow ? this.windowsMainService.getWindowById(activeBrowserWindow.id) : undefined;
|
||||
if (activeWindow) {
|
||||
this.logService.trace('menubar#runActionInRenderer', invocation);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address:
|
||||
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
|
||||
return undefined;
|
||||
}
|
||||
const localhostMatch = /^(localhost|127\.0\.0\.1):(\d+)$/.exec(uri.authority);
|
||||
const localhostMatch = /^(localhost|127\.0\.0\.1|0\.0\.0\.0):(\d+)$/.exec(uri.authority);
|
||||
if (!localhostMatch) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ class TestableLogService extends AbstractLogService implements ILogService {
|
||||
}
|
||||
|
||||
dispose(): void { }
|
||||
flush(): void { }
|
||||
}
|
||||
|
||||
suite('AIAdapter', () => {
|
||||
@@ -198,4 +199,4 @@ suite('AIAdapter', () => {
|
||||
}
|
||||
}]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,6 +63,12 @@ export function registerConfiguration(): IDisposable {
|
||||
$ref: ignoredSettingsSchemaId,
|
||||
additionalProperties: true,
|
||||
uniqueItems: true
|
||||
},
|
||||
'configurationSync.enableAuth': {
|
||||
'type': 'boolean',
|
||||
description: localize('configurationSync.enableAuth', "Enables authentication and requires VS Code restart when changed"),
|
||||
'default': false,
|
||||
'scope': ConfigurationScope.APPLICATION
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -44,4 +44,8 @@ export class UserDataSyncLogService extends AbstractLogService implements IUserD
|
||||
this.logger.critical(message, ...args);
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
this.logger.flush();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -131,27 +131,32 @@ export class UserDataAutoSync extends Disposable {
|
||||
@IAuthTokenService private readonly authTokenService: IAuthTokenService,
|
||||
) {
|
||||
super();
|
||||
this.updateEnablement();
|
||||
this.sync(true);
|
||||
this._register(Event.any<any>(authTokenService.onDidChangeStatus, userDataSyncService.onDidChangeStatus)(() => this.updateEnablement()));
|
||||
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('configurationSync.enable'))(() => this.updateEnablement()));
|
||||
this.updateEnablement(false);
|
||||
this._register(Event.any<any>(authTokenService.onDidChangeStatus, userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true)));
|
||||
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('configurationSync.enable'))(() => this.updateEnablement(true)));
|
||||
|
||||
// Sync immediately if there is a local change.
|
||||
this._register(Event.debounce(this.userDataSyncService.onDidChangeLocal, () => undefined, 500)(() => this.sync(false)));
|
||||
}
|
||||
|
||||
private updateEnablement(): void {
|
||||
private updateEnablement(stopIfDisabled: boolean): void {
|
||||
const enabled = this.isSyncEnabled();
|
||||
if (this.enabled !== enabled) {
|
||||
this.enabled = enabled;
|
||||
if (this.enabled) {
|
||||
this.userDataSyncLogService.info('Syncing configuration started');
|
||||
this.sync(true);
|
||||
} else {
|
||||
if (this.enabled === enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.enabled = enabled;
|
||||
if (this.enabled) {
|
||||
this.userDataSyncLogService.info('Syncing configuration started');
|
||||
this.sync(true);
|
||||
return;
|
||||
} else {
|
||||
if (stopIfDisabled) {
|
||||
this.userDataSyncService.stop();
|
||||
this.userDataSyncLogService.info('Syncing configuration stopped.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async sync(loop: boolean): Promise<void> {
|
||||
|
||||
@@ -19,15 +19,17 @@ export interface IOpenedWindow {
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
export interface IOpenInWindowOptions {
|
||||
forceNewWindow?: boolean;
|
||||
export interface IBaseOpenWindowsOptions {
|
||||
forceReuseWindow?: boolean;
|
||||
}
|
||||
|
||||
export interface IOpenWindowOptions extends IBaseOpenWindowsOptions {
|
||||
forceNewWindow?: boolean;
|
||||
|
||||
noRecentEntry?: boolean;
|
||||
}
|
||||
|
||||
export interface IOpenEmptyWindowOptions {
|
||||
reuse?: boolean;
|
||||
export interface IOpenEmptyWindowOptions extends IBaseOpenWindowsOptions {
|
||||
remoteAuthority?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ import { ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { IWorkspaceIdentifier, IEnterWorkspaceResult } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue, Rectangle, BrowserWindow, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions } from 'electron';
|
||||
import { Rectangle, BrowserWindow } from 'electron';
|
||||
|
||||
export interface IWindowState {
|
||||
width?: number;
|
||||
@@ -86,38 +86,34 @@ export interface IWindowsCountChangedEvent {
|
||||
}
|
||||
|
||||
export interface IWindowsMainService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
// events
|
||||
readonly onWindowReady: Event<ICodeWindow>;
|
||||
readonly onWindowsCountChanged: Event<IWindowsCountChangedEvent>;
|
||||
readonly onWindowClose: Event<number>;
|
||||
|
||||
// methods
|
||||
reload(win: ICodeWindow, cli?: ParsedArgs): void;
|
||||
enterWorkspace(win: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | undefined>;
|
||||
closeWorkspace(win: ICodeWindow): void;
|
||||
open(openConfig: IOpenConfiguration): ICodeWindow[];
|
||||
openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): void;
|
||||
openEmptyWindow(context: OpenContext, options?: IOpenEmptyWindowOptions): ICodeWindow[];
|
||||
openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): ICodeWindow[];
|
||||
|
||||
pickFileFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise<void>;
|
||||
pickFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise<void>;
|
||||
pickFileAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise<void>;
|
||||
pickWorkspaceAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise<void>;
|
||||
showMessageBox(options: MessageBoxOptions, win?: ICodeWindow): Promise<MessageBoxReturnValue>;
|
||||
showSaveDialog(options: SaveDialogOptions, win?: ICodeWindow): Promise<SaveDialogReturnValue>;
|
||||
showOpenDialog(options: OpenDialogOptions, win?: ICodeWindow): Promise<OpenDialogReturnValue>;
|
||||
focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow;
|
||||
getLastActiveWindow(): ICodeWindow | undefined;
|
||||
waitForWindowCloseOrLoad(windowId: number): Promise<void>;
|
||||
openEmptyWindow(context: OpenContext, options?: IOpenEmptyWindowOptions): ICodeWindow[];
|
||||
openNewTabbedWindow(context: OpenContext): ICodeWindow[];
|
||||
openExternal(url: string): Promise<boolean>;
|
||||
|
||||
sendToFocused(channel: string, ...args: any[]): void;
|
||||
sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void;
|
||||
getFocusedWindow(): ICodeWindow | undefined;
|
||||
|
||||
getLastActiveWindow(): ICodeWindow | undefined;
|
||||
|
||||
getWindowById(windowId: number): ICodeWindow | undefined;
|
||||
getWindows(): ICodeWindow[];
|
||||
getWindowCount(): number;
|
||||
|
||||
waitForWindowCloseOrLoad(windowId: number): Promise<void>;
|
||||
reload(win: ICodeWindow, cli?: ParsedArgs): void;
|
||||
closeWorkspace(win: ICodeWindow): void;
|
||||
quit(): void;
|
||||
}
|
||||
|
||||
@@ -139,8 +135,3 @@ export interface IOpenConfiguration {
|
||||
readonly initialStartup?: boolean;
|
||||
readonly noRecentEntry?: boolean;
|
||||
}
|
||||
|
||||
export interface ISharedProcess {
|
||||
whenReady(): Promise<void>;
|
||||
toggle(): void;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,136 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IOpenInWindowOptions } from 'vs/platform/windows/common/windows';
|
||||
import { OpenContext, IOpenWindowOptions } from 'vs/platform/windows/common/windows';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as extpath from 'vs/base/common/extpath';
|
||||
import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { isEqual, isEqualOrParent } from 'vs/base/common/resources';
|
||||
|
||||
export interface INativeOpenInWindowOptions extends IOpenInWindowOptions {
|
||||
export interface INativeOpenWindowOptions extends IOpenWindowOptions {
|
||||
diffMode?: boolean;
|
||||
addMode?: boolean;
|
||||
gotoLineMode?: boolean;
|
||||
waitMarkerFileURI?: URI;
|
||||
}
|
||||
|
||||
export interface IWindowContext {
|
||||
openedWorkspace?: IWorkspaceIdentifier;
|
||||
openedFolderUri?: URI;
|
||||
|
||||
extensionDevelopmentPath?: string[];
|
||||
lastFocusTime: number;
|
||||
}
|
||||
|
||||
export interface IBestWindowOrFolderOptions<W extends IWindowContext> {
|
||||
windows: W[];
|
||||
newWindow: boolean;
|
||||
context: OpenContext;
|
||||
fileUri?: URI;
|
||||
userHome?: string;
|
||||
codeSettingsFolder?: string;
|
||||
localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null;
|
||||
}
|
||||
|
||||
export function findBestWindowOrFolderForFile<W extends IWindowContext>({ windows, newWindow, context, fileUri, localWorkspaceResolver: workspaceResolver }: IBestWindowOrFolderOptions<W>): W | undefined {
|
||||
if (!newWindow && fileUri && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK)) {
|
||||
const windowOnFilePath = findWindowOnFilePath(windows, fileUri, workspaceResolver);
|
||||
if (windowOnFilePath) {
|
||||
return windowOnFilePath;
|
||||
}
|
||||
}
|
||||
return !newWindow ? getLastActiveWindow(windows) : undefined;
|
||||
}
|
||||
|
||||
function findWindowOnFilePath<W extends IWindowContext>(windows: W[], fileUri: URI, localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null): W | null {
|
||||
|
||||
// First check for windows with workspaces that have a parent folder of the provided path opened
|
||||
for (const window of windows) {
|
||||
const workspace = window.openedWorkspace;
|
||||
if (workspace) {
|
||||
const resolvedWorkspace = localWorkspaceResolver(workspace);
|
||||
if (resolvedWorkspace) {
|
||||
// workspace could be resolved: It's in the local file system
|
||||
if (resolvedWorkspace.folders.some(folder => isEqualOrParent(fileUri, folder.uri))) {
|
||||
return window;
|
||||
}
|
||||
} else {
|
||||
// use the config path instead
|
||||
if (isEqualOrParent(fileUri, workspace.configPath)) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then go with single folder windows that are parent of the provided file path
|
||||
const singleFolderWindowsOnFilePath = windows.filter(window => window.openedFolderUri && isEqualOrParent(fileUri, window.openedFolderUri));
|
||||
if (singleFolderWindowsOnFilePath.length) {
|
||||
return singleFolderWindowsOnFilePath.sort((a, b) => -(a.openedFolderUri!.path.length - b.openedFolderUri!.path.length))[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getLastActiveWindow<W extends IWindowContext>(windows: W[]): W | undefined {
|
||||
const lastFocusedDate = Math.max.apply(Math, windows.map(window => window.lastFocusTime));
|
||||
|
||||
return windows.filter(window => window.lastFocusTime === lastFocusedDate)[0];
|
||||
}
|
||||
|
||||
export function findWindowOnWorkspace<W extends IWindowContext>(windows: W[], workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)): W | null {
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
for (const window of windows) {
|
||||
// match on folder
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
if (window.openedFolderUri && isEqual(window.openedFolderUri, workspace)) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (isWorkspaceIdentifier(workspace)) {
|
||||
for (const window of windows) {
|
||||
// match on workspace
|
||||
if (window.openedWorkspace && window.openedWorkspace.id === workspace.id) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findWindowOnExtensionDevelopmentPath<W extends IWindowContext>(windows: W[], extensionDevelopmentPaths: string[]): W | null {
|
||||
|
||||
const matches = (uriString: string): boolean => {
|
||||
return extensionDevelopmentPaths.some(p => extpath.isEqual(p, uriString, !platform.isLinux /* ignorecase */));
|
||||
};
|
||||
|
||||
for (const window of windows) {
|
||||
// match on extension development path. The path can be one or more paths or uri strings, using paths.isEqual is not 100% correct but good enough
|
||||
const currPaths = window.extensionDevelopmentPath;
|
||||
if (currPaths && currPaths.some(p => matches(p))) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findWindowOnWorkspaceOrFolderUri<W extends IWindowContext>(windows: W[], uri: URI | undefined): W | null {
|
||||
if (!uri) {
|
||||
return null;
|
||||
}
|
||||
for (const window of windows) {
|
||||
// check for workspace config path
|
||||
if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, uri)) {
|
||||
return window;
|
||||
}
|
||||
|
||||
// check for folder path
|
||||
if (window.openedFolderUri && isEqual(window.openedFolderUri, uri)) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
128
src/vs/platform/windows/test/node/window.test.ts
Normal file
128
src/vs/platform/windows/test/node/window.test.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { IBestWindowOrFolderOptions, IWindowContext, findBestWindowOrFolderForFile } from 'vs/platform/windows/node/window';
|
||||
import { OpenContext } from 'vs/platform/windows/common/windows';
|
||||
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
|
||||
const fixturesFolder = getPathFromAmdModule(require, './fixtures');
|
||||
|
||||
const testWorkspace: IWorkspaceIdentifier = {
|
||||
id: Date.now().toString(),
|
||||
configPath: URI.file(path.join(fixturesFolder, 'workspaces.json'))
|
||||
};
|
||||
|
||||
const testWorkspaceFolders = toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }], testWorkspace.configPath);
|
||||
|
||||
function options(custom?: Partial<IBestWindowOrFolderOptions<IWindowContext>>): IBestWindowOrFolderOptions<IWindowContext> {
|
||||
return {
|
||||
windows: [],
|
||||
newWindow: false,
|
||||
context: OpenContext.CLI,
|
||||
codeSettingsFolder: '_vscode',
|
||||
localWorkspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: testWorkspaceFolders } : null; },
|
||||
...custom
|
||||
};
|
||||
}
|
||||
|
||||
const vscodeFolderWindow: IWindowContext = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder')) };
|
||||
const lastActiveWindow: IWindowContext = { lastFocusTime: 3, openedFolderUri: undefined };
|
||||
const noVscodeFolderWindow: IWindowContext = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) };
|
||||
const windows: IWindowContext[] = [
|
||||
vscodeFolderWindow,
|
||||
lastActiveWindow,
|
||||
noVscodeFolderWindow,
|
||||
];
|
||||
|
||||
suite('WindowsFinder', () => {
|
||||
|
||||
test('New window without folder when no windows exist', () => {
|
||||
assert.equal(findBestWindowOrFolderForFile(options()), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'))
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
|
||||
newWindow: true
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
|
||||
context: OpenContext.API
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt'))
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'new_folder', 'new_file.txt'))
|
||||
})), null);
|
||||
});
|
||||
|
||||
test('New window without folder when windows exist', () => {
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows,
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')),
|
||||
newWindow: true
|
||||
})), null);
|
||||
});
|
||||
|
||||
test('Last active window', () => {
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows
|
||||
})), lastActiveWindow);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows,
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder2', 'file.txt'))
|
||||
})), lastActiveWindow);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [lastActiveWindow, noVscodeFolderWindow],
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
|
||||
})), lastActiveWindow);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows,
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')),
|
||||
context: OpenContext.API
|
||||
})), lastActiveWindow);
|
||||
});
|
||||
|
||||
test('Existing window with folder', () => {
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows,
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'))
|
||||
})), noVscodeFolderWindow);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows,
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt'))
|
||||
})), vscodeFolderWindow);
|
||||
const window: IWindowContext = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder')) };
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window],
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt'))
|
||||
})), window);
|
||||
});
|
||||
|
||||
test('More specific existing window wins', () => {
|
||||
const window: IWindowContext = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) };
|
||||
const nestedFolderWindow: IWindowContext = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder')) };
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window, nestedFolderWindow],
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt'))
|
||||
})), nestedFolderWindow);
|
||||
});
|
||||
|
||||
test('Workspace folder wins', () => {
|
||||
const window: IWindowContext = { lastFocusTime: 1, openedWorkspace: testWorkspace };
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window],
|
||||
fileUri: URI.file(path.join(fixturesFolder, 'vscode_workspace_2_folder', 'nested_vscode_folder', 'subfolder', 'file.txt'))
|
||||
})), window);
|
||||
});
|
||||
});
|
||||
@@ -31,7 +31,7 @@ export interface IWorkspacesService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
// Management
|
||||
enterWorkspace(path: URI): Promise<IEnterWorkspaceResult | undefined>;
|
||||
enterWorkspace(path: URI): Promise<IEnterWorkspaceResult | null>;
|
||||
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier>;
|
||||
deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void>;
|
||||
getWorkspaceIdentifier(workspacePath: URI): Promise<IWorkspaceIdentifier>;
|
||||
|
||||
@@ -23,6 +23,7 @@ import { getSimpleWorkspaceLabel } from 'vs/platform/label/common/label';
|
||||
import { exists } from 'vs/base/node/pfs';
|
||||
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export const IWorkspacesHistoryMainService = createDecorator<IWorkspacesHistoryMainService>('workspacesHistoryMainService');
|
||||
|
||||
@@ -40,7 +41,7 @@ export interface IWorkspacesHistoryMainService {
|
||||
updateWindowsJumpList(): void;
|
||||
}
|
||||
|
||||
export class WorkspacesHistoryMainService implements IWorkspacesHistoryMainService {
|
||||
export class WorkspacesHistoryMainService extends Disposable implements IWorkspacesHistoryMainService {
|
||||
|
||||
private static readonly MAX_TOTAL_RECENT_ENTRIES = 100;
|
||||
|
||||
@@ -60,18 +61,27 @@ export class WorkspacesHistoryMainService implements IWorkspacesHistoryMainServi
|
||||
private readonly _onRecentlyOpenedChange = new Emitter<void>();
|
||||
readonly onRecentlyOpenedChange: CommonEvent<void> = this._onRecentlyOpenedChange.event;
|
||||
|
||||
private macOSRecentDocumentsUpdater: ThrottledDelayer<void>;
|
||||
private macOSRecentDocumentsUpdater = this._register(new ThrottledDelayer<void>(800));
|
||||
|
||||
constructor(
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@ILifecycleMainService lifecycleMainService: ILifecycleMainService
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService
|
||||
) {
|
||||
this.macOSRecentDocumentsUpdater = new ThrottledDelayer<void>(800);
|
||||
super();
|
||||
|
||||
lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList());
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Install window jump list after opening window
|
||||
this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList());
|
||||
|
||||
// Add to history when entering workspace
|
||||
this._register(this.workspacesMainService.onWorkspaceEntered(event => this.addRecentlyOpened([{ workspace: event.workspace }])));
|
||||
}
|
||||
|
||||
private handleWindowsJumpList(): void {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder, IEnterWorkspaceResult } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { join, dirname } from 'vs/base/common/path';
|
||||
import { mkdirp, writeFile, rimrafSync, readdirSync, writeFileSync } from 'vs/base/node/pfs';
|
||||
@@ -17,31 +17,43 @@ import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { originalFSPath, isEqualOrParent, joinPath } from 'vs/base/common/resources';
|
||||
import { originalFSPath, isEqualOrParent, joinPath, isEqual, basename } from 'vs/base/common/resources';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { localize } from 'vs/nls';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { MessageBoxOptions, BrowserWindow } from 'electron';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
|
||||
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
|
||||
import { findWindowOnWorkspace } from 'vs/platform/windows/node/window';
|
||||
|
||||
export const IWorkspacesMainService = createDecorator<IWorkspacesMainService>('workspacesMainService');
|
||||
|
||||
export interface IWorkspaceEnteredEvent {
|
||||
window: ICodeWindow;
|
||||
workspace: IWorkspaceIdentifier;
|
||||
}
|
||||
|
||||
export interface IWorkspacesMainService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
readonly onUntitledWorkspaceDeleted: Event<IWorkspaceIdentifier>;
|
||||
readonly onWorkspaceEntered: Event<IWorkspaceEnteredEvent>;
|
||||
|
||||
enterWorkspace(intoWindow: ICodeWindow, openedWindows: ICodeWindow[], path: URI): Promise<IEnterWorkspaceResult | null>;
|
||||
|
||||
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier>;
|
||||
createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier;
|
||||
|
||||
resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | null;
|
||||
|
||||
isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean;
|
||||
|
||||
deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void>;
|
||||
deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void;
|
||||
|
||||
getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[];
|
||||
isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean;
|
||||
|
||||
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier>;
|
||||
|
||||
deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void>;
|
||||
|
||||
resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | null;
|
||||
getWorkspaceIdentifier(workspacePath: URI): Promise<IWorkspaceIdentifier>;
|
||||
}
|
||||
|
||||
@@ -59,9 +71,14 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain
|
||||
private readonly _onUntitledWorkspaceDeleted = this._register(new Emitter<IWorkspaceIdentifier>());
|
||||
readonly onUntitledWorkspaceDeleted: Event<IWorkspaceIdentifier> = this._onUntitledWorkspaceDeleted.event;
|
||||
|
||||
private readonly _onWorkspaceEntered = this._register(new Emitter<IWorkspaceEnteredEvent>());
|
||||
readonly onWorkspaceEntered: Event<IWorkspaceEnteredEvent> = this._onWorkspaceEntered.event;
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IBackupMainService private readonly backupMainService: IBackupMainService,
|
||||
@IDialogMainService private readonly dialogMainService: IDialogMainService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -236,6 +253,74 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain
|
||||
}
|
||||
return untitledWorkspaces;
|
||||
}
|
||||
|
||||
async enterWorkspace(window: ICodeWindow, windows: ICodeWindow[], path: URI): Promise<IEnterWorkspaceResult | null> {
|
||||
if (!window || !window.win || !window.isReady) {
|
||||
return null; // return early if the window is not ready or disposed
|
||||
}
|
||||
|
||||
const isValid = await this.isValidTargetWorkspacePath(window, windows, path);
|
||||
if (!isValid) {
|
||||
return null; // return early if the workspace is not valid
|
||||
}
|
||||
|
||||
const result = this.doEnterWorkspace(window, getWorkspaceIdentifier(path));
|
||||
|
||||
// Emit as event
|
||||
this._onWorkspaceEntered.fire({ window, workspace: result.workspace });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async isValidTargetWorkspacePath(window: ICodeWindow, windows: ICodeWindow[], path?: URI): Promise<boolean> {
|
||||
if (!path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, path)) {
|
||||
return false; // window is already opened on a workspace with that path
|
||||
}
|
||||
|
||||
// Prevent overwriting a workspace that is currently opened in another window
|
||||
if (findWindowOnWorkspace(windows, getWorkspaceIdentifier(path))) {
|
||||
const options: MessageBoxOptions = {
|
||||
title: product.nameLong,
|
||||
type: 'info',
|
||||
buttons: [localize('ok', "OK")],
|
||||
message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
|
||||
detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
|
||||
noLink: true
|
||||
};
|
||||
|
||||
await this.dialogMainService.showMessageBox(options, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // OK
|
||||
}
|
||||
|
||||
private doEnterWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
|
||||
window.focus();
|
||||
|
||||
// Register window for backups and migrate current backups over
|
||||
let backupPath: string | undefined;
|
||||
if (!window.config.extensionDevelopmentPath) {
|
||||
backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath);
|
||||
}
|
||||
|
||||
// if the window was opened on an untitled workspace, delete it.
|
||||
if (window.openedWorkspace && this.isUntitledWorkspace(window.openedWorkspace)) {
|
||||
this.deleteUntitledWorkspaceSync(window.openedWorkspace);
|
||||
}
|
||||
|
||||
// Update window configuration properly based on transition to workspace
|
||||
window.config.folderUri = undefined;
|
||||
window.config.workspace = workspace;
|
||||
window.config.backupPath = backupPath;
|
||||
|
||||
return { workspace, backupPath };
|
||||
}
|
||||
}
|
||||
|
||||
function getWorkspaceId(configPath: URI): string {
|
||||
|
||||
@@ -23,13 +23,13 @@ export class WorkspacesService implements AddFirstParameterToFunctions<IWorkspac
|
||||
|
||||
//#region Workspace Management
|
||||
|
||||
async enterWorkspace(windowId: number, path: URI): Promise<IEnterWorkspaceResult | undefined> {
|
||||
async enterWorkspace(windowId: number, path: URI): Promise<IEnterWorkspaceResult | null> {
|
||||
const window = this.windowsMainService.getWindowById(windowId);
|
||||
if (window) {
|
||||
return this.windowsMainService.enterWorkspace(window, path);
|
||||
return this.workspacesMainService.enterWorkspace(window, this.windowsMainService.getWindows(), path);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
createUntitledWorkspace(windowId: number, folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier> {
|
||||
|
||||
@@ -18,6 +18,7 @@ import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { normalizeDriveLetter } from 'vs/base/common/labels';
|
||||
import { dirname, joinPath } from 'vs/base/common/resources';
|
||||
import { TestBackupMainService, TestDialogMainService } from 'vs/workbench/test/workbenchTestServices';
|
||||
|
||||
suite('WorkspacesMainService', () => {
|
||||
const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'workspacesservice');
|
||||
@@ -43,7 +44,7 @@ suite('WorkspacesMainService', () => {
|
||||
let service: WorkspacesMainService;
|
||||
|
||||
setup(async () => {
|
||||
service = new WorkspacesMainService(environmentService, logService);
|
||||
service = new WorkspacesMainService(environmentService, logService, new TestBackupMainService(), new TestDialogMainService());
|
||||
|
||||
// Delete any existing backups completely and then re-create it.
|
||||
await pfs.rimraf(untitledWorkspacesHomePath, pfs.RimRafMode.MOVE);
|
||||
|
||||
Reference in New Issue
Block a user