Skip to content

Commit 8519764

Browse files
author
Benjamin Pasero
committed
Allow to open *.code-workspace file as text file (fixes microsoft#31008)
1 parent 98b273c commit 8519764

10 files changed

Lines changed: 83 additions & 59 deletions

File tree

src/vs/base/common/objects.ts

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
* Copyright (c) Microsoft Corporation. All rights reserved.
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
5+
56
'use strict';
67

7-
import * as Types from 'vs/base/common/types';
8+
import { isObject, isUndefinedOrNull, isArray } from 'vs/base/common/types';
89

910
export function clone<T>(obj: T): T {
1011
if (!obj || typeof obj !== 'object') {
@@ -14,7 +15,7 @@ export function clone<T>(obj: T): T {
1415
// See https://github.com/Microsoft/TypeScript/issues/10990
1516
return obj as any;
1617
}
17-
var result = (Array.isArray(obj)) ? <any>[] : <any>{};
18+
const result = (Array.isArray(obj)) ? <any>[] : <any>{};
1819
Object.keys(obj).forEach((key) => {
1920
if (obj[key] && typeof obj[key] === 'object') {
2021
result[key] = clone(obj[key]);
@@ -29,7 +30,7 @@ export function deepClone<T>(obj: T): T {
2930
if (!obj || typeof obj !== 'object') {
3031
return obj;
3132
}
32-
var result = (Array.isArray(obj)) ? <any>[] : <any>{};
33+
const result = (Array.isArray(obj)) ? <any>[] : <any>{};
3334
Object.getOwnPropertyNames(obj).forEach((key) => {
3435
if (obj[key] && typeof obj[key] === 'object') {
3536
result[key] = deepClone(obj[key]);
@@ -40,37 +41,37 @@ export function deepClone<T>(obj: T): T {
4041
return result;
4142
}
4243

43-
var hasOwnProperty = Object.prototype.hasOwnProperty;
44+
const hasOwnProperty = Object.prototype.hasOwnProperty;
4445

4546
export function cloneAndChange(obj: any, changer: (orig: any) => any): any {
4647
return _cloneAndChange(obj, changer, []);
4748
}
4849

4950
function _cloneAndChange(obj: any, changer: (orig: any) => any, encounteredObjects: any[]): any {
50-
if (Types.isUndefinedOrNull(obj)) {
51+
if (isUndefinedOrNull(obj)) {
5152
return obj;
5253
}
5354

54-
var changed = changer(obj);
55+
const changed = changer(obj);
5556
if (typeof changed !== 'undefined') {
5657
return changed;
5758
}
5859

59-
if (Types.isArray(obj)) {
60-
var r1: any[] = [];
61-
for (var i1 = 0; i1 < obj.length; i1++) {
60+
if (isArray(obj)) {
61+
const r1: any[] = [];
62+
for (let i1 = 0; i1 < obj.length; i1++) {
6263
r1.push(_cloneAndChange(obj[i1], changer, encounteredObjects));
6364
}
6465
return r1;
6566
}
6667

67-
if (Types.isObject(obj)) {
68+
if (isObject(obj)) {
6869
if (encounteredObjects.indexOf(obj) >= 0) {
6970
throw new Error('Cannot clone recursive data-structure');
7071
}
7172
encounteredObjects.push(obj);
72-
var r2 = {};
73-
for (var i2 in obj) {
73+
const r2 = {};
74+
for (let i2 in obj) {
7475
if (hasOwnProperty.call(obj, i2)) {
7576
r2[i2] = _cloneAndChange(obj[i2], changer, encounteredObjects);
7677
}
@@ -87,15 +88,15 @@ function _cloneAndChange(obj: any, changer: (orig: any) => any, encounteredObjec
8788
* if existing properties on the destination should be overwritten or not. Defaults to true (overwrite).
8889
*/
8990
export function mixin(destination: any, source: any, overwrite: boolean = true): any {
90-
if (!Types.isObject(destination)) {
91+
if (!isObject(destination)) {
9192
return source;
9293
}
9394

94-
if (Types.isObject(source)) {
95+
if (isObject(source)) {
9596
Object.keys(source).forEach((key) => {
9697
if (key in destination) {
9798
if (overwrite) {
98-
if (Types.isObject(destination[key]) && Types.isObject(source[key])) {
99+
if (isObject(destination[key]) && isObject(source[key])) {
99100
mixin(destination[key], source[key], overwrite);
100101
} else {
101102
destination[key] = source[key];
@@ -135,8 +136,8 @@ export function equals(one: any, other: any): boolean {
135136
return false;
136137
}
137138

138-
var i: number,
139-
key: string;
139+
let i: number;
140+
let key: string;
140141

141142
if (Array.isArray(one)) {
142143
if (one.length !== other.length) {
@@ -148,13 +149,13 @@ export function equals(one: any, other: any): boolean {
148149
}
149150
}
150151
} else {
151-
var oneKeys: string[] = [];
152+
const oneKeys: string[] = [];
152153

153154
for (key in one) {
154155
oneKeys.push(key);
155156
}
156157
oneKeys.sort();
157-
var otherKeys: string[] = [];
158+
const otherKeys: string[] = [];
158159
for (key in other) {
159160
otherKeys.push(key);
160161
}
@@ -178,8 +179,8 @@ export function ensureProperty(obj: any, property: string, defaultValue: any) {
178179
}
179180

180181
export function arrayToHash(array: any[]) {
181-
var result: any = {};
182-
for (var i = 0; i < array.length; ++i) {
182+
const result: any = {};
183+
for (let i = 0; i < array.length; ++i) {
183184
result[array[i]] = true;
184185
}
185186
return result;
@@ -193,7 +194,7 @@ export function createKeywordMatcher(arr: string[], caseInsensitive: boolean = f
193194
if (caseInsensitive) {
194195
arr = arr.map(function (x) { return x.toLowerCase(); });
195196
}
196-
var hash = arrayToHash(arr);
197+
const hash = arrayToHash(arr);
197198
if (caseInsensitive) {
198199
return function (word) {
199200
return hash[word.toLowerCase()] !== undefined && hash.hasOwnProperty(word.toLowerCase());
@@ -211,19 +212,18 @@ export function createKeywordMatcher(arr: string[], caseInsensitive: boolean = f
211212
* to call this method before the constructor definition.
212213
*/
213214
export function derive(baseClass: any, derivedClass: any): void {
214-
215-
for (var prop in baseClass) {
215+
for (let prop in baseClass) {
216216
if (baseClass.hasOwnProperty(prop)) {
217217
derivedClass[prop] = baseClass[prop];
218218
}
219219
}
220220

221221
derivedClass = derivedClass || function () { };
222-
var basePrototype = baseClass.prototype;
223-
var derivedPrototype = derivedClass.prototype;
222+
const basePrototype = baseClass.prototype;
223+
const derivedPrototype = derivedClass.prototype;
224224
derivedClass.prototype = Object.create(basePrototype);
225225

226-
for (var prop in derivedPrototype) {
226+
for (let prop in derivedPrototype) {
227227
if (derivedPrototype.hasOwnProperty(prop)) {
228228
// handle getters and setters properly
229229
Object.defineProperty(derivedClass.prototype, prop, Object.getOwnPropertyDescriptor(derivedPrototype, prop));
@@ -240,10 +240,9 @@ export function derive(baseClass: any, derivedClass: any): void {
240240
* "Uncaught TypeError: Converting circular structure to JSON"
241241
*/
242242
export function safeStringify(obj: any): string {
243-
var seen: any[] = [];
243+
const seen: any[] = [];
244244
return JSON.stringify(obj, (key, value) => {
245-
246-
if (Types.isObject(value) || Array.isArray(value)) {
245+
if (isObject(value) || Array.isArray(value)) {
247246
if (seen.indexOf(value) !== -1) {
248247
return '[Circular]';
249248
} else {

src/vs/code/electron-main/menus.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ export class CodeMenu {
467467
openRecentMenu.append(__separator__());
468468

469469
for (let i = 0; i < CodeMenu.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) {
470-
openRecentMenu.append(this.createOpenRecentMenuItem(workspaces[i], 'openRecentWorkspace'));
470+
openRecentMenu.append(this.createOpenRecentMenuItem(workspaces[i], 'openRecentWorkspace', false));
471471
}
472472
}
473473

@@ -476,7 +476,7 @@ export class CodeMenu {
476476
openRecentMenu.append(__separator__());
477477

478478
for (let i = 0; i < CodeMenu.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) {
479-
openRecentMenu.append(this.createOpenRecentMenuItem(files[i], 'openRecentFile'));
479+
openRecentMenu.append(this.createOpenRecentMenuItem(files[i], 'openRecentFile', true));
480480
}
481481
}
482482

@@ -488,10 +488,10 @@ export class CodeMenu {
488488
}
489489
}
490490

491-
private createOpenRecentMenuItem(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier, commandId: string): Electron.MenuItem {
491+
private createOpenRecentMenuItem(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | string, commandId: string, isFile: boolean): Electron.MenuItem {
492492
let label: string;
493493
let path: string;
494-
if (isSingleFolderWorkspaceIdentifier(workspace)) {
494+
if (isSingleFolderWorkspaceIdentifier(workspace) || typeof workspace === 'string') {
495495
label = this.unmnemonicLabel(tildify(workspace, this.environmentService.userHome));
496496
path = workspace;
497497
} else {
@@ -503,7 +503,13 @@ export class CodeMenu {
503503
label,
504504
click: (menuItem, win, event) => {
505505
const openInNewWindow = this.isOptionClick(event);
506-
const success = this.windowsService.open({ context: OpenContext.MENU, cli: this.environmentService.args, pathsToOpen: [path], forceNewWindow: openInNewWindow }).length > 0;
506+
const success = this.windowsService.open({
507+
context: OpenContext.MENU,
508+
cli: this.environmentService.args,
509+
pathsToOpen: [path], forceNewWindow: openInNewWindow,
510+
forceOpenWorkspaceAsFile: isFile
511+
}).length > 0;
512+
507513
if (!success) {
508514
this.historyService.removeFromRecentlyOpened([isSingleFolderWorkspaceIdentifier(workspace) ? workspace : workspace.configPath]);
509515
}

src/vs/code/electron-main/windows.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as path from 'path';
99
import * as fs from 'original-fs';
1010
import { localize } from "vs/nls";
1111
import * as arrays from 'vs/base/common/arrays';
12-
import { assign, mixin } from 'vs/base/common/objects';
12+
import { assign, mixin, equals } from 'vs/base/common/objects';
1313
import { IBackupMainService } from 'vs/platform/backup/common/backup';
1414
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
1515
import { IStorageService } from 'vs/platform/storage/node/storage';
@@ -639,7 +639,7 @@ export class WindowsManager implements IWindowsMainService {
639639

640640
// Extract paths: from API
641641
if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) {
642-
windowsToOpen = this.doExtractPathsFromAPI(openConfig.pathsToOpen, openConfig.cli && openConfig.cli.goto);
642+
windowsToOpen = this.doExtractPathsFromAPI(openConfig);
643643
}
644644

645645
// Check for force empty
@@ -660,9 +660,9 @@ export class WindowsManager implements IWindowsMainService {
660660
return windowsToOpen;
661661
}
662662

663-
private doExtractPathsFromAPI(paths: string[], gotoLineMode: boolean): IPath[] {
664-
let pathsToOpen = paths.map(pathToOpen => {
665-
const path = this.parsePath(pathToOpen, false, gotoLineMode);
663+
private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPath[] {
664+
let pathsToOpen = openConfig.pathsToOpen.map(pathToOpen => {
665+
const path = this.parsePath(pathToOpen, { gotoLineMode: openConfig.cli && openConfig.cli.goto, forceOpenWorkspaceAsFile: openConfig.forceOpenWorkspaceAsFile });
666666

667667
// Warn if the requested path to open does not exist
668668
if (!path) {
@@ -693,7 +693,7 @@ export class WindowsManager implements IWindowsMainService {
693693
}
694694

695695
private doExtractPathsFromCLI(cli: ParsedArgs): IPath[] {
696-
const pathsToOpen = arrays.coalesce(cli._.map(candidate => this.parsePath(candidate, true /* ignoreFileNotFound */, cli.goto)));
696+
const pathsToOpen = arrays.coalesce(cli._.map(candidate => this.parsePath(candidate, { ignoreFileNotFound: true, gotoLineMode: cli.goto })));
697697
if (pathsToOpen.length > 0) {
698698
return pathsToOpen;
699699
}
@@ -824,13 +824,15 @@ export class WindowsManager implements IWindowsMainService {
824824
return restoreWindows;
825825
}
826826

827-
private parsePath(anyPath: string, ignoreFileNotFound?: boolean, gotoLineMode?: boolean): IWindowToOpen {
827+
private parsePath(anyPath: string, options?: { ignoreFileNotFound?: boolean, gotoLineMode?: boolean, forceOpenWorkspaceAsFile?: boolean; }): IWindowToOpen {
828828
if (!anyPath) {
829829
return null;
830830
}
831831

832832
let parsedPath: IPathWithLineAndColumn;
833-
if (gotoLineMode) {
833+
834+
const gotoLineMode = options && options.gotoLineMode;
835+
if (options && options.gotoLineMode) {
834836
parsedPath = parseLineAndColumnAware(anyPath);
835837
anyPath = parsedPath.path;
836838
}
@@ -841,10 +843,12 @@ export class WindowsManager implements IWindowsMainService {
841843
if (candidateStat) {
842844
if (candidateStat.isFile()) {
843845

844-
// Workspace
845-
const workspace = this.workspacesService.resolveWorkspaceSync(candidate);
846-
if (workspace) {
847-
return { workspace: { id: workspace.id, configPath: candidate } };
846+
// Workspace (unless disabled via flag)
847+
if (!options || !options.forceOpenWorkspaceAsFile) {
848+
const workspace = this.workspacesService.resolveWorkspaceSync(candidate);
849+
if (workspace) {
850+
return { workspace: { id: workspace.id, configPath: candidate } };
851+
}
848852
}
849853

850854
// File
@@ -863,7 +867,7 @@ export class WindowsManager implements IWindowsMainService {
863867
} catch (error) {
864868
this.historyService.removeFromRecentlyOpened([candidate]); // since file does not seem to exist anymore, remove from recent
865869

866-
if (ignoreFileNotFound) {
870+
if (options && options.ignoreFileNotFound) {
867871
return { filePath: candidate, createFilePath: true }; // assume this is a file that does not yet exist
868872
}
869873
}
@@ -1573,7 +1577,13 @@ class FileDialog {
15731577

15741578
// Open
15751579
if (numberOfPaths) {
1576-
this.windowsMainService.open({ context: OpenContext.DIALOG, cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options.forceNewWindow });
1580+
this.windowsMainService.open({
1581+
context: OpenContext.DIALOG,
1582+
cli: this.environmentService.args,
1583+
pathsToOpen: paths,
1584+
forceNewWindow: options.forceNewWindow,
1585+
forceOpenWorkspaceAsFile: options.dialogOptions && !equals(options.dialogOptions.filters, WORKSPACE_FILTER)
1586+
});
15771587
}
15781588
});
15791589
}

src/vs/platform/windows/common/windows.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export interface IWindowsService {
6464
toggleSharedProcess(): TPromise<void>;
6565

6666
// Global methods
67-
openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean }): TPromise<void>;
67+
openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean; }): TPromise<void>;
6868
openNewWindow(): TPromise<void>;
6969
showWindow(windowId: number): TPromise<void>;
7070
getWindows(): TPromise<{ id: number; workspace?: IWorkspaceIdentifier; folderPath?: string; title: string; filename?: string; }[]>;

src/vs/platform/windows/common/windowsIpc.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export interface IWindowsChannel extends IChannel {
3838
call(command: 'onWindowTitleDoubleClick', arg: number): TPromise<void>;
3939
call(command: 'setDocumentEdited', arg: [number, boolean]): TPromise<void>;
4040
call(command: 'quit'): TPromise<void>;
41-
call(command: 'openWindow', arg: [string[], { forceNewWindow?: boolean, forceReuseWindow?: boolean }]): TPromise<void>;
41+
call(command: 'openWindow', arg: [string[], { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }]): TPromise<void>;
4242
call(command: 'openNewWindow'): TPromise<void>;
4343
call(command: 'showWindow', arg: number): TPromise<void>;
4444
call(command: 'getWindows'): TPromise<{ id: number; workspace?: IWorkspaceIdentifier; folderPath?: string; title: string; filename?: string; }[]>;
@@ -228,7 +228,7 @@ export class WindowsChannelClient implements IWindowsService {
228228
return this.channel.call('toggleSharedProcess');
229229
}
230230

231-
openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean }): TPromise<void> {
231+
openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): TPromise<void> {
232232
return this.channel.call('openWindow', [paths, options]);
233233
}
234234

src/vs/platform/windows/electron-main/windows.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export interface IOpenConfiguration {
8888
forceReuseWindow?: boolean;
8989
forceEmpty?: boolean;
9090
diffMode?: boolean;
91+
forceOpenWorkspaceAsFile?: boolean;
9192
initialStartup?: boolean;
9293
}
9394

src/vs/platform/windows/electron-main/windowsService.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,20 @@ export class WindowsService implements IWindowsService, IDisposable {
261261
return TPromise.as(null);
262262
}
263263

264-
openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean }): TPromise<void> {
264+
openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): TPromise<void> {
265265
if (!paths || !paths.length) {
266266
return TPromise.as(null);
267267
}
268268

269-
this.windowsMainService.open({ context: OpenContext.API, cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options && options.forceNewWindow, forceReuseWindow: options && options.forceReuseWindow });
269+
this.windowsMainService.open({
270+
context: OpenContext.API,
271+
cli: this.environmentService.args,
272+
pathsToOpen: paths,
273+
forceNewWindow: options && options.forceNewWindow,
274+
forceReuseWindow: options && options.forceReuseWindow,
275+
forceOpenWorkspaceAsFile: options && options.forceOpenWorkspaceAsFile
276+
});
277+
270278
return TPromise.as(null);
271279
}
272280

0 commit comments

Comments
 (0)