diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 5b9ef7f0c7a7702b13db387c093e9f57c134a3c6..cde42b4947b91efce4702f9c9e13a9b9056d035b 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -38,7 +38,8 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.6.2' } + { name: 'ms-vscode.node-debug', version: '1.6.5' }, + { name: 'ms-vscode.node-debug2', version: '0.0.1' } ]; const vscodeEntryPoints = _.flatten([ diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index 628a9dff8445541005d4b55b3f9694cbd090bd5a..14eb03518ff8326ccb789d9aae81b9ec5a0ba9b3 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -151,14 +151,14 @@ gulp.task('clean-vscode-linux-arm-rpm', util.rimraf('.build/linux/rpm/armhf')); gulp.task('vscode-linux-ia32-prepare-deb', ['clean-vscode-linux-ia32-deb', 'vscode-linux-ia32-min'], prepareDebPackage('ia32')); gulp.task('vscode-linux-x64-prepare-deb', ['clean-vscode-linux-x64-deb', 'vscode-linux-x64-min'], prepareDebPackage('x64')); -gulp.task('vscode-linux-arm-prepare-deb', ['clean-vscode-linux-arm-deb', 'vscode-linux-arm-min'], prepareDebPackage('armhf')); +gulp.task('vscode-linux-arm-prepare-deb', ['clean-vscode-linux-arm-deb', 'vscode-linux-arm-min'], prepareDebPackage('arm')); gulp.task('vscode-linux-ia32-build-deb', ['vscode-linux-ia32-prepare-deb'], buildDebPackage('ia32')); gulp.task('vscode-linux-x64-build-deb', ['vscode-linux-x64-prepare-deb'], buildDebPackage('x64')); -gulp.task('vscode-linux-arm-build-deb', ['vscode-linux-arm-prepare-deb'], buildDebPackage('armhf')); +gulp.task('vscode-linux-arm-build-deb', ['vscode-linux-arm-prepare-deb'], buildDebPackage('arm')); gulp.task('vscode-linux-ia32-prepare-rpm', ['clean-vscode-linux-ia32-rpm', 'vscode-linux-ia32-min'], prepareRpmPackage('ia32')); gulp.task('vscode-linux-x64-prepare-rpm', ['clean-vscode-linux-x64-rpm', 'vscode-linux-x64-min'], prepareRpmPackage('x64')); -gulp.task('vscode-linux-arm-prepare-rpm', ['clean-vscode-linux-arm-rpm', 'vscode-linux-arm-min'], prepareRpmPackage('armhf')); +gulp.task('vscode-linux-arm-prepare-rpm', ['clean-vscode-linux-arm-rpm', 'vscode-linux-arm-min'], prepareRpmPackage('arm')); gulp.task('vscode-linux-ia32-build-rpm', ['vscode-linux-ia32-prepare-rpm'], buildRpmPackage('ia32')); gulp.task('vscode-linux-x64-build-rpm', ['vscode-linux-x64-prepare-rpm'], buildRpmPackage('x64')); -gulp.task('vscode-linux-arm-build-rpm', ['vscode-linux-arm-prepare-rpm'], buildRpmPackage('armhf')); +gulp.task('vscode-linux-arm-build-rpm', ['vscode-linux-arm-prepare-rpm'], buildRpmPackage('arm')); diff --git a/resources/win32/bin/cat.exe b/resources/win32/bin/cat.exe index f564baa192a0c7af76e09d32f1c2b1164bdee9a3..e5490f2a78b1a27a7603155500a01a91b10b23e4 100644 Binary files a/resources/win32/bin/cat.exe and b/resources/win32/bin/cat.exe differ diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 7315a2005c49520c3e839a58fa68b02bdd572770..3a15129ab3bfbd48e5751bcfa0b02a5c518a0061 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -6,7 +6,6 @@ 'use strict'; import * as nls from 'vs/nls'; -import * as fs from 'original-fs'; import { app, ipcMain as ipc } from 'electron'; import { assign } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; @@ -39,11 +38,15 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/node/requestService'; -import * as cp from 'child_process'; import { generateUuid } from 'vs/base/common/uuid'; +import { getPathLabel } from 'vs/base/common/labels'; import { URLChannel } from 'vs/platform/url/common/urlIpc'; import { URLService } from 'vs/platform/url/electron-main/urlService'; +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); function quit(accessor: ServicesAccessor, arg?: any) { @@ -193,25 +196,54 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: IProce // Install JumpList on Windows if (platform.isWindows) { - app.setJumpList([ - { - type: 'tasks', - items: [ - { + 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 + const folders = windowsService.getRecentPathsList().folders; + if (folders.length > 0) { + jumpList.push({ + type: 'custom', + name: 'Recent Folders', + items: windowsService.getRecentPathsList().folders.slice(0, 7 /* limit number of entries here */).map(folder => { + return { type: 'task', - title: nls.localize('newWindow', "New Window"), - description: nls.localize('newWindowDesc', "Opens a new window"), + title: getPathLabel(folder), + description: nls.localize('folderDesc', "{0} {1}", path.basename(folder), getPathLabel(path.dirname(folder))), program: process.execPath, - args: '-n', // force new window + args: folder, // open folder, iconPath: process.execPath, iconIndex: 0 - } - ] - }, - { - type: 'recent' // this enables to show files in the "recent" category - } - ]); + }; + }) + }); + } + + // 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 + } } // Setup auto update diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index ee4f3f9c4f5d88a5f3c515b4a026d28b556c14aa..abe96a7af33ad90d0d40f445e83cf35a068aaeb9 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -103,6 +103,10 @@ export interface IExtensionIdentity { publisher: string; } +export interface IGalleryExtensionProperties { + dependencies?: string[]; +} + export interface IGalleryExtensionAssets { manifest: string; readme: string; @@ -126,6 +130,7 @@ export interface IGalleryExtension { rating: number; ratingCount: number; assets: IGalleryExtensionAssets; + properties: IGalleryExtensionProperties; downloadHeaders: { [key: string]: string; }; } @@ -184,6 +189,8 @@ export interface IExtensionGalleryService { query(options?: IQueryOptions): TPromise>; download(extension: IGalleryExtension): TPromise; getAsset(url: string): TPromise; + loadCompatibleVersion(extension: IGalleryExtension): TPromise; + getAllDependencies(extension: IGalleryExtension): TPromise; } export interface InstallExtensionEvent { diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index f85f1312d4f567cf056d598d76b2a8848e8dd4ab..d9e23852c7250f9012feedc251905ab9558b9468 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -7,6 +7,8 @@ import { localize } from 'vs/nls'; import { tmpdir } from 'os'; import * as path from 'path'; import { TPromise } from 'vs/base/common/winjs.base'; +import { distinct } from 'vs/base/common/arrays'; +import { ArraySet } from 'vs/base/common/set'; import { IGalleryExtension, IExtensionGalleryService, IQueryOptions, SortBy, SortOrder, IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionTelemetry'; import { isUndefined } from 'vs/base/common/types'; @@ -26,11 +28,17 @@ interface IRawGalleryExtensionFile { source: string; } +interface IRawGalleryExtensionProperty { + key: string; + value: string; +} + interface IRawGalleryExtensionVersion { version: string; lastUpdated: string; assetUri: string; files: IRawGalleryExtensionFile[]; + properties: IRawGalleryExtensionProperty[]; } interface IRawGalleryExtensionStatistics { @@ -90,7 +98,11 @@ const AssetType = { Details: 'Microsoft.VisualStudio.Services.Content.Details', Manifest: 'Microsoft.VisualStudio.Code.Manifest', VSIX: 'Microsoft.VisualStudio.Services.VSIXPackage', - License: 'Microsoft.VisualStudio.Services.Content.License' + License: 'Microsoft.VisualStudio.Services.Content.License', +}; + +const PropertyType = { + Dependency: 'Microsoft.VisualStudio.Code.ExtensionDependencies' }; interface ICriterium { @@ -178,7 +190,13 @@ function getAssetSource(files: IRawGalleryExtensionFile[], type: string): string const result = files.filter(f => f.assetType === type)[0]; return result && result.source; } - +function getDependencies(properties: IRawGalleryExtensionProperty[]): string[] { + const values = properties.filter(p => p.key === PropertyType.Dependency); + if (values.length && values[0].value) { + return values[0].value.split(','); + } + return []; +} function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUrl: string, downloadHeaders: { [key: string]: string; }): IGalleryExtension { const [version] = galleryExtension.versions; @@ -203,6 +221,8 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr license: getAssetSource(version.files, AssetType.License) }; + const dependencies = version.properties ? getDependencies(version.properties) : void 0; + return { id: galleryExtension.extensionId, name: galleryExtension.extensionName, @@ -217,6 +237,9 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr rating: getStatistic(galleryExtension.statistics, 'averagerating'), ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'), assets, + properties: { + dependencies + }, downloadHeaders }; } @@ -337,8 +360,35 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } download(extension: IGalleryExtension): TPromise { + return this.loadCompatibleVersion(extension).then(extension => { + const url = extension.assets.download; + const zipPath = path.join(tmpdir(), extension.id); + const data = getGalleryExtensionTelemetryData(extension); + const startTime = new Date().getTime(); + const log = duration => this.telemetryService.publicLog('galleryService:downloadVSIX', assign(data, { duration })); + + return this.getCommonHeaders() + .then(headers => this._getAsset({ url, headers })) + .then(context => download(zipPath, context)) + .then(() => log(new Date().getTime() - startTime)) + .then(() => zipPath); + }); + } + + getAsset(url: string): TPromise { + return this._getAsset({ url }); + } + + getAllDependencies(extension: IGalleryExtension): TPromise { + return this.loadCompatibleVersion(extension) + .then(compatible => this.getDependenciesReccursively(compatible.properties.dependencies, [extension])) + .then(dependencies => dependencies.slice(1)); + } + + loadCompatibleVersion(extension: IGalleryExtension): TPromise { + // TODO:sandy: Check if the given version is compatible const query = new Query() - .withFlags(Flags.IncludeVersions, Flags.IncludeFiles) + .withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties) .withPage(1, 1) .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code') .withAssetTypes(AssetType.Manifest, AssetType.VSIX) @@ -346,29 +396,64 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return this.queryGallery(query).then(({ galleryExtensions }) => { const [rawExtension] = galleryExtensions; - if (!rawExtension) { return TPromise.wrapError(new Error(localize('notFound', "Extension not found"))); } - - return this.getLastValidExtensionVersion(rawExtension, rawExtension.versions).then(rawVersion => { - const url = `${ getAssetSource(rawVersion.files, AssetType.VSIX) }?install=true`; - const zipPath = path.join(tmpdir(), extension.id); - const data = getGalleryExtensionTelemetryData(extension); - const startTime = new Date().getTime(); - const log = duration => this.telemetryService.publicLog('galleryService:downloadVSIX', assign(data, { duration })); - - return this.getCommonHeaders() - .then(headers => this._getAsset({ url, headers })) - .then(context => download(zipPath, context)) - .then(() => log(new Date().getTime() - startTime)) - .then(() => zipPath); - }); + return this.getLastValidExtensionVersion(rawExtension, rawExtension.versions) + .then(rawVersion => { + extension.properties.dependencies = getDependencies(rawVersion.properties); + extension.assets.download = `${ getAssetSource(rawVersion.files, AssetType.VSIX) }?install=true`; + return extension; + }); }); } - getAsset(url: string): TPromise { - return this._getAsset({ url }); + private loadDependencies(extensionNames: string[]): TPromise { + let query = new Query() + .withFlags(Flags.IncludeVersions, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles, Flags.IncludeVersionProperties) + .withPage(1, extensionNames.length) + .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code') + .withAssetTypes(AssetType.Icon, AssetType.License, AssetType.Details, AssetType.Manifest, AssetType.VSIX); + query = extensionNames.reduce((query, name) => query.withFilter(FilterType.ExtensionName, name), query); + + return this.queryGallery(query) + .then(result => this.getCommonHeaders() + .then(downloadHeaders => { + const dependencies = []; + const ids = []; + for (const galleryExtension of result.galleryExtensions) { + if (ids.indexOf(galleryExtension.extensionId) === -1) { + dependencies.push(toExtension(galleryExtension, this.extensionsGalleryUrl, downloadHeaders)); + ids.push(galleryExtension.extensionId); + } + } + return dependencies; + }) + ); + } + + private getDependenciesReccursively(toGet: string[], result: IGalleryExtension[]): TPromise { + if (!toGet || !toGet.length) { + return TPromise.wrap(result); + } + toGet = result.length ? toGet.filter(e => !ExtensionGalleryService.hasExtensionByName(result, e)) : toGet; + if (!toGet.length) { + return TPromise.wrap(result); + } + + + return this.loadDependencies(toGet) + .then(loadedDependencies => { + const dependenciesSet = new ArraySet(); + for (const dep of loadedDependencies) { + if (dep.properties.dependencies) { + dep.properties.dependencies.forEach(d => dependenciesSet.set(d)); + } + } + result = distinct(result.concat(loadedDependencies), d => d.id); + const dependencies = dependenciesSet.elements.filter(d => !ExtensionGalleryService.hasExtensionByName(result, d)); + return this.getDependenciesReccursively(dependencies, result); + }); } /** @@ -416,4 +501,13 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return version; }); } + + private static hasExtensionByName(extensions: IGalleryExtension[], name: string): boolean { + for (const extension of extensions) { + if (`${extension.publisher}.${extension.name}` === name) { + return true; + } + } + return false; + } } \ No newline at end of file diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index b15547ce52a67674b506ca3cb8cda4203b9e5b23..24e520e72f4f9a96bb00e1c5a467fb3266d066ad 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -8,6 +8,7 @@ import nls = require('vs/nls'); import * as path from 'path'; import * as pfs from 'vs/base/node/pfs'; +import * as errors from 'vs/base/common/errors'; import { assign } from 'vs/base/common/objects'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { flatten } from 'vs/base/common/arrays'; @@ -24,6 +25,7 @@ import Event, { Emitter } from 'vs/base/common/event'; import * as semver from 'semver'; import { groupBy, values } from 'vs/base/common/collections'; import URI from 'vs/base/common/uri'; +import { IChoiceService, Severity } from 'vs/platform/message/common/message'; const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions')); @@ -106,6 +108,7 @@ export class ExtensionManagementService implements IExtensionManagementService { constructor( @IEnvironmentService private environmentService: IEnvironmentService, + @IChoiceService private choiceService: IChoiceService, @IExtensionGalleryService private galleryService: IExtensionGalleryService ) { this.extensionsPath = environmentService.extensionsPath; @@ -135,30 +138,97 @@ export class ExtensionManagementService implements IExtensionManagementService { }); } - installFromGallery(extension: IGalleryExtension): TPromise { + installFromGallery(extension: IGalleryExtension, checkDependecies: boolean = true): TPromise { const id = getExtensionId(extension, extension.version); return this.isObsolete(id).then(isObsolete => { if (isObsolete) { return TPromise.wrapError(new Error(nls.localize('restartCode', "Please restart Code before reinstalling {0}.", extension.displayName || extension.name))); } - this._onInstallExtension.fire({ id, gallery: extension }); + return this.galleryService.loadCompatibleVersion(extension) + .then(compatibleVersion => this.installCompatibleVersion(compatibleVersion, checkDependecies)) + .then( + local => this._onDidInstallExtension.fire({ id, local, gallery: extension }), + error => { + this._onDidInstallExtension.fire({ id, gallery: extension, error }); + return TPromise.wrapError(error); + } + ); + }); + } + + private installCompatibleVersion(extension: IGalleryExtension, checkDependecies: boolean): TPromise { + const dependencies = checkDependecies ? this.getDependenciesToInstall(extension) : []; + if (!dependencies.length) { + return this.downloadAndInstall(extension); + } + + const message = nls.localize('installDependecies', "This extension has dependencies. Would you like to install them along with it?"); + const options = [ + nls.localize('installWithDependenices', "Install With Dependencies"), + nls.localize('installWithoutDependenices', "Install only this"), + nls.localize('close', "Close") + ]; + return this.choiceService.choose(Severity.Info, message, options) + .then(value => { + switch (value) { + case 0: + return this.installWithDependencies(extension); + case 1: + return this.downloadAndInstall(extension); + default: + return TPromise.wrapError(errors.canceled()); + } + }); + } + + private getDependenciesToInstall(extension: IGalleryExtension): string[] { + const extensionName = `${extension.publisher}.${extension.name}`; + return extension.properties.dependencies ? extension.properties.dependencies.filter(name => name !== extensionName) : []; + } + + private installWithDependencies(extension: IGalleryExtension): TPromise { + return this.galleryService.getAllDependencies(extension) + .then(allDependencies => this.filterOutInstalled(allDependencies)) + .then(toInstall => this.bulkInstallWithDependencies(extension, toInstall)); + } + + private bulkInstallWithDependencies(extension: IGalleryExtension, dependecies: IGalleryExtension[]): TPromise { + return this.downloadAndInstall(extension) + .then(localExtension => TPromise.join(dependecies.map((dep) => this.installFromGallery(dep, false))) + .then(() => localExtension, error => this.rollback(localExtension, dependecies).then(() => error))); + } + + private rollback(localExtension: ILocalExtension, dependecies: IGalleryExtension[]): TPromise { + return this.uninstall(localExtension) + .then(() => this.filterOutUnInstalled(dependecies)) + .then(installed => TPromise.join(installed.map((i) => this.uninstall(i)))) + .then(() => null); + } + + private filterOutInstalled(extensions: IGalleryExtension[]): TPromise { + return this.getInstalled().then(local => { + return extensions.filter(extension => local.every(local => local.id !== extension.id)); + }); + } + + private filterOutUnInstalled(extensions: IGalleryExtension[]): TPromise { + return this.getInstalled().then(installed => { + return installed.filter(local => extensions.every(extension => extension.id === local.id)); + }); + } - const metadata = { + private downloadAndInstall(extension: IGalleryExtension): TPromise { + const id = getExtensionId(extension, extension.version); + const metadata = { id: extension.id, publisherId: extension.publisherId, publisherDisplayName: extension.publisherDisplayName }; - - return this.galleryService.download(extension) - .then(zipPath => validate(zipPath).then(() => zipPath)) - .then(zipPath => this.installExtension(zipPath, id, metadata)) - .then( - local => this._onDidInstallExtension.fire({ id, local, gallery: extension }), - error => { this._onDidInstallExtension.fire({ id, gallery: extension, error }); return TPromise.wrapError(error); } - ); - }); + return this.galleryService.download(extension) + .then(zipPath => validate(zipPath).then(() => zipPath)) + .then(zipPath => this.installExtension(zipPath, id, metadata)); } private installExtension(zipPath: string, id: string, metadata: IGalleryMetadata = null): TPromise {